知乎上有一个热门问题:你的编程能力从什么时候开始突飞猛进?
初看到这个问题,我的嘴角微微上扬。记忆闪回到了 2013 年,那一年,命运给我了一点点正反馈,我有点喜爱编程了。
这篇文章,我想和大家聊聊勇哥读书,看源码,重构,解决线上问题的那些事。
1. 初心
2011 年,我服务于一家互联网彩票公司。坦白的讲,抉择程序员这个职业,仅仅是为了生存。
那个时候,我对 缓存 , 音讯队列 , 分布式,JVM 只知其一; 不知其二,背了一些八股文,只是能十分纯熟的应用 ibatis,velocity,编写简略的业务代码。
我负责的是用户核心零碎,提供用户注册,查问,批改等根底性能。所有的服务都以 HTTP 接口模式提供,数据传输格局是 XML。
尽管工作看起来简略,我那个时候也不懂设计模式,写的业务代码十分臃肿,难以保护。
也产生了我的人生第一次重大 BUG,我负责的用户核心在上线后隔一段时间变会内存溢出。我站在运维同学那里,看着他调整 tomcat jvm 参数,手足无措。
起初发现我在应用 ibatis 的时候,应用相似的 SQLMap,前端又没有验证,数据库执行了全表查问,从而导致 JVM OOM。
select
*
from t_lottery_user t
where 1 = 1
<isNotEmpty prepend="AND" property="userName">
user_name = #userName#
</isNotEmpty>
<isNotEmpty prepend="AND" property="id">
id = #id#
</isNotEmpty>
....
这次生产环境事变之后,心田始终有一个声音折磨着我:“遇到技术问题的时候,能不能从容一点,不慌乱。其他人能够做到的事,为什么我做不到。”
于是我开始疯狂的买书读书,毕玄老师的《分布式 Java 利用》这本书对我影响至深。通过这本书,我深刻学习了 JVM 内存模型,外围汇合类原理,并发包等知识点,特地是 jstack,jmap 等 JVM 命令,边看书,边入手实际。
同时为了扩大视线,我在 javaeye 和 开源中国两大社区外面疯狂找热门帖子学习,一个帖子上下文都我都会反反复复的看几十遍以上。
也时不时找运维同学,或者 DBA 聊,因为和他们聊,能够从另外一个视角扫视公司零碎存在的问题,他们的一句话有时候能够给我一些灵感。
2. 第一次重构
通过 2011-2012 两年的学习,2013 年彩票业务迎来了小暴发,我也迎来了 技术人生第一次重构。
算奖服务是十分外围的服务,算奖服务蕴含若干子服务,其中竞彩算奖是用 C# 版本开发的零碎。原来彩票订单量少的时候,算奖服务还算稳固,但一旦量级增大,C# 版算奖服务就会 hang 住,算奖工夫从半个小时变成两三小时,重大影响订单的返奖。
我过后满脑子都是想着去争一口气,去证实本人已不是两年前的弱鸡,于是 被动请缨重构算奖零碎。但领导说实话也是将信将疑,过后状况比拟紧急,于是也就批准了。
技术团队比拟草莽,没有对立的根底框架,每次新建我的项目都是依照研发的爱好搭架子。因为原来有接触过京东的根底框架,于是我参考京东框架第一次搭建本人的作品。
算奖整体逻辑比较简单,服务接管到竞彩赛事编号,查问彩票子订单,通过赛事后果判断彩票是否中奖,并批改子订单中奖信息,最初发送中奖信息到音讯队列,最初调度核心来返奖。
至今都还记得开始写代码是双休日,两天没日没夜不知疲倦的编码,指尖敲击键盘,代码显示在屏幕上,行云流水,这种感觉美妙而又微妙。
两天就重构完了,怎么验证正确?在测试环境简略跑了一遍,发现没有任何问题。领导也感觉不堪设想,但这个就能上线吗?我心外面也直打鼓,每天的竞彩算奖波及到大几十万人民币,要是算错了,那影响也很大,我也要承当相应的责任。
领导也没有给我领导倡议,于是我突发奇想:” 生产环境不是有一两年的订单算奖历史数据吗?重构版本计算的后果和生产环境计算的后果做个比照,不就能够验证正确率吗?”。
于是,我将代码做了一些微调,将最初对数据的写操作去掉,比照重构版本计算的金额和 c# 版本计算的金额,若金额有差别,订单数据写入到文本中,发送邮件告警。
让我惊喜的是:在近千万的历史订单里,重构版本的计算结果十分精准,只呈现了两例计算异样,并且计算速度十分快(快靠近 10 倍)。修复完 BUG 后,和 C# 版本并行运行二十天左右后,计算结果都精准无误。于是,领导批准了下线老零碎,上线算奖重构版本。
这一次胜利重构带给我自信:在这个行业,我是能够生存上来的。
技术层面:我本人搭建了我的项目,接触了音讯队列做为音讯总线的架构模式,也意识到利用根底框架的重要性。同时,我也隐约感觉:“代码写进去是绝对容易的,验证代码的正确性同样十分考验工程能力”。
3. 浏览源码
2013 年,是我浏览源码的终点 , 浏览了 Druid , Cobar , Xmemcached 等源码。
3.1 数据源连接池 Druid
算奖零碎重构之后,有一个小插曲。我发现每天的第一次申请,数据库连贯有问题,于是我向 Druid 的作者温少写了一封邮件。
温少给我回复了邮件,我马上打开源码,发现我配置数据库连接池的心跳有问题。外围点在于须要连接池每隔一段时间发送心跳包到 oracle 服务器,因为数据库为了节俭资源, 每隔一段时间会敞开掉长期没有读写的连贯。所以客户端必须每隔一段时间发送心跳包到服务端。
这次简略的探寻源码给了我短暂的激励,也让我更加关注技术背地的原理。
- 精力层面:向他人求教问题是会上瘾的;
- 技能层面:了解连接池的实现原理;
- 架构层面:客户端和服务端申请须要思考心跳。
3.2 数据库中间件 Cobar
还是在 2013 年,接触到 cobar 带给我的震撼几乎变本加厉。
过后互联网大潮奔涌而来 , 各大互联网公司的数据爆炸般的增长 , 我曾在 javaeye 上看到淘宝订单技术人员分享分库分表的帖子 , 如获至宝 , 想从字里行间探寻分库分表的解决方案 , 惋惜受限于篇幅 , 文字总归是文字 , 总感觉隔靴搔痒。没曾想到 , cobar 开源了, 我至今都还记得用 navicat 配置 cobar 的信息,就可用像连单个 mysql 一样,而且数据会平均的散布到多个数据库中,这几乎像魔法一样。对于我过后孱弱的技术思维来讲 , 几乎就像是三体里水滴遇到人类舰队般,降维打击。
因为对分库分表原理的渴求 , 我没有好的学习办法 , 大概花了 3 个月的工夫,我把整个 cobar 的外围代码抄了一次。真的是智商不够 , 膂力来凑。但光有膂力是真的不够的,常常会陷入狐疑,怎么这也看不懂,那也看不懂。边抄代码边学习如同提高得没有那么显著。那好 , 总得找一个突破口吧。网络通讯是十分重要的一环。
过后的我做了一个决定,我要把 cobar 的网络通讯层剥离进去 , 去深刻理解应用 原生 nio 实现通信的模式。剥离的过程同样很苦楚 , 但我有指标了 , 不至于像没头的苍蝇,起初也就有了人生第一个 github 我的项目。
追 cobar 的过程中, 如同我和阿里的大牛面对面交换,尽管我资质驽钝,但这位大牛谆谆教诲 , 对我急躁解答, 买通我的任督二脉。由是感谢。说是我生命中最重要的开源我的项目也不为过。
“你想学啊?我教你“。
4. 实战:知行合一
2013 年下半年,我参加了很多零碎的重构,解决了很多生产环境的问题。我常常思考:怎么将我学到货色用到实在场景中,也做了很多乏味的尝试。
4.1 多级缓存
彩票零碎里有一个赛事剖析服务,页面非常复杂,采取的计划是 nginx 页面动态化的策略,通过定时工作每天将赛事剖析数据写入到 磁盘中 NFS 共享目录,配置 Nginx 拜访。
nginx 页面动态化的优缺点都非常明显。
- 动态页面访问速度极快,性能十分好;
- 保护老本高,定时工作定期生成相干的页面,更新常常提早,而且代码是 N 年前的古董,是用 Php 写的。
领导决定用 Java 重构,外围的指标是:性能。
看过红薯哥写了一篇文章:oschina 上的一种双缓存思路。
https://www.oschina.net/quest…
我大胆的采纳了 Ehcache + memcached 的双缓存架构。重构过程也很顺利,上线后性能也还不错,保护起来也绝对简略。
4.2 救火队员
彩票零碎的业务量增长极快,生产环境常常遇到一些莫名其妙的问题。每次遇到问题,我 把每个问题当作我本人的问题,全力以赴的去解决,变成了救火队员。
产生了很多的故事,举两个例子。
▍ 调度核心生产不了
某一天双色球投注截止,调度核心无奈从音讯队列中生产数据。音讯总线处于只能发,不能收的状态下。整个技术团队都处于极度的焦虑状态,“要是出不了票,那可是几百万的损失呀,要是用户中了两个双色球?那可是千万呀”。大家急得像热锅上的蚂蚁。
这也是整个技术团队第一次遇到生产沉积的状况,大家都没有教训。
首先想到的是多部署几台调度核心服务,部署实现之后,调度核心生产了几千条音讯后还是 Hang 住了。这时,架构师只能采纳 重启 的策略。你没有看错,就是重启大法。说起来真的很羞愧,但过后真的只能采纳这种形式。
调度核心重启后,生产了一两万后又 Hang 住了。只能又重启一次。来来回回继续 20 屡次,像挤牙膏一样。而且随着出票截止工夫的邻近,这种思维上的缓和和恐惧感更加强烈。终于,通过 1 小时的手工一直重启,音讯终于生产完了。
我过后正好在读毕玄老师的《分布式 java 利用根底与实际》,猜测是不是线程阻塞了,于是我用 Jstack 命令查看堆栈状况。果然不出所料,线程都阻塞在提交数据的办法上。
咱们马上和 DBA 沟通,发现 oracle 数据库执行了十分多的大事务,每次大的事务执行都须要 30 分钟以上,导致调度核心的调度出票线程阻塞了。
技术部起初采取了如下的计划躲避沉积问题:
- 生产者发送音讯的时候,将超大的音讯拆分成多批次的音讯,缩小调度核心执行大事务的几率;
- 数据源配置参数,如果事务执行超过肯定时长,主动抛异样,回滚。
▍比分直播页面卡顿
共事开发了比分直播的零碎,所有的申请都是从缓存中获取后间接响应。惯例状况下,从缓存中查问数据十分快,但在线用户略微多一点,整个零碎就会特地卡。
通过 jstat 命令发现 GC 频率极高,几次申请就将新生代占满了,而且 CPU 的耗费都在 GC 线程上。初步判断是缓存值过大导致的,果不其然,缓存大小在 300k 到 500k 左右。
解决过程还比拟挫折,分为两个步骤:
- 批改新生代大小,从原来的 2G 批改成 4G,并精简缓存数据大小 (从均匀 300k 左右降为 80k 左右);
- 把缓存拆成两个局部,第一局部是全量数据,第二局部是增量数据(数据量很小)。页面第一次申请拉取全量数据,当比分有变动的时候,通过 websocket 推送增量数据。
通过这次优化,我了解到:缓存尽管能够晋升整体速度,然而在高并发场景下,缓存对象大小仍然是须要关注的点,稍不留神就会产生事变。另外咱们也须要正当地管制读取策略,最大水平缩小 GC 的频率 , 从而晋升整体性能。
5. 写到最初
我特地喜爱毕淑敏对于命运的解释:
慢慢地,我终于发现,命运是我怯懦时的盾牌,每当我叫嚷命运不公最响的时候,正式我准备逃遁的前奏。命运就像是一只筐,我把本人对本人的姑息,原谅以及所有的懈怠都一股脑儿地塞进去而后蒙上一块宿命的轻纱,我背着它缓缓往前走,心里有一份自欺欺人的坦然。
当我抉择程序员这个职业,最开始是为了生存,我并不聪明,学什么都很慢,但我偏偏想证实本人,只能一直去学习,命运貌似给我一丢丢回馈,让我有了一点点满足感。
于是,2013 年,那个青年找到了属于他的世界。
喜爱一个人,眼神是藏不住的,喜爱编程,眼神同样是藏不住的。