最近读陈年先生的文章"凑热闹的公司都会烟消云散",读了十几遍,意犹未尽,感慨颇深。
我来小米三年了,三年前我会滔滔不绝的跟别人讲,如何带领几十个人,建立一套多么完善的质量保证体系,如何用制度让所有人都遵守软件开发的流程和秩序,等等云云;还要告诉大家,某某大公司都是这样干的,某某外企比这个干的还大,借以佐证我的思想是何等的正确和伟大。
现在想想,那时的我,SB!
2014年,我很平静的度过了,做了一年多的推送服务,写年终总结的时候,恰逢读到陈年先生的那篇文章,于是我想我一年做了这么多事情,有哪件事情可以拿出来炫耀,就像那件白衬衣呢?
于是我把所有做过的事情都列出来,然后一个一个划掉,这个事情做得太糙,这个事情做的太乱,这个,嗯,很一般;当你划掉几十件事情的时候,你的心就凉了,拔凉拔凉的;陈年先生在半层楼里放了所有的产品,却没有一个可以拿出手给雷总看的时候,也许就是这种感觉吧。
想了几天之后,我终于想到一件小事情,也许这件事情太平凡了,以至于成为了不过大脑的生活习惯;直到昨天跟某两个互联网大公司的服务器工程师吹牛的时候,我才发现,当我把这个牛吹完的时候,他们看我的眼神中似乎有了一丝异样的光芒。
我们花了一年多的时间只做了一套服务的监控,大的改版超过了10次,N多次的代码重构,推倒重写,各种尝试,各种跳坑,解决各种瓶颈,看各种密密麻麻的log,分析各种情况,处理各种异常,等等云云
其实监控原理很简单,在测试机上跑一组case,调一下线上服务的api,判断一下服务返回的结果对不对,对了就pass,错了就报警,就这么简单,没什么难的。但是我们前前后后做了一年多。。。直到今天,改动还在继续。
1.最初的方案是这样的,E2E的测试,用一个手机接到测试机器上,然后用脚本定时驱动安卓程序,向手机里安装一个推送客户端sdk demo,然后跑android的case,给客户端发消息,看客户端是否能收到,如果收到就从手机里读出来判断正确与否。这个代码很快就写好了,测试通过,嗯不错,开始跑吧。跑了一个晚上就傻眼了,由于手机通过wifi连接网络,各种网络不稳定所导致的误报,直接让报警不断,失去了报警的意义。怎么办?换一套环境
2.既然wifi环境不好,那就直接在网络接口接一个路由器,这个路由器只提供这一台手机的网络连接,这个总该稳定了吧。跑了两个晚上,嗯,误报是少点,但是总误报数量占的比例还是很大,报警搞得大家都很紧张,怎么办?再换一套方案。
3.使用android虚拟机,这个东西直接跑到测试机器上,完全不需要wifi,这总可以了吧?实践了几天,哭了,一个是虚拟机内存占用之恐怖,连续跑几天你试一试,另外性能也有问题,读取服务器的返回值是很慢的,需要长时间的sleep,最后就是不稳定,各种不稳定。
4.取交集,手机跑case网络是瓶颈,模拟器跑case稳定是瓶颈,能不能两个一起跑,如果同一条case都fail,才报警,只有一个fail,不报警。这个方案不错吧,取了一个交集;真到写代码的时候就完蛋了,两个监控一个跑的快,一个跑的慢,你还得写一个控制程序去定期分析两边的log,乱的一塌糊涂。
抛弃android吧,我们监控的是服务,干嘛要带上客户端,尽管E2E的监控更让人感觉靠谱吧,但是这不是最根本的东西
5.直接连服务器,客户端的原理是通过push demo调用push client sdk,client sdk最终是需要调用smack包来完成xmpp消息传输的;服务端工程师做E2E测试的时候,会通过一个中间层直接调用smack连接服务器;ok有方案了,把服务端工程师用到的中间层改改,然后套用客户端smack,再模拟客户端发出的请求,最上层接上test case,就应该能工作了;不久,这个移花接木的东西开始跑了,效果还不错,明显误报少了很多,很好,把前面那些不靠谱的监控全都干掉,把这货换上,然后张灯结彩,准备喝庆功酒,但是跑了几周,问题来了,长连接不像短连接,不成功就retry,长连接一旦断了,重新bind是有问题的,并且如何检测连接是否断掉呢?
6.最初的怀疑是smack不稳定,因为看到了smack的call stack,问题应该出在smack上,smack是第三方类库,用于xmpp的连接,但是我们使用的xmpp不是标准的xmpp协议,是经过我们改造过的xmpp,smack于是也被改造过了;查了一下maven库,上面有好几个smack的版本,换个别的用用,问问消息组的同事,各种尝试之后的结论是,这不是版本问题,别的版本不支持push服务,怎么办?改造smack吧
7.先找到smack的源代码,然后各种看,各种debug,然后整理了一下smack的行为规律:a.如果网络断了,smack发消息前,自己会检测到网络断了,然后发一个<present>强制断开网络连接;b.如果客户端等待收服务的消息,那就会超时,smack感知不到网络断开了。中间层是没法对重新bind做处理的;那就只能改smack,在收发消息的时候,先检查网络情况,如果断了就重新bind,代码改完了,找android的大牛军哥去看,没出5分钟就被拍回来了,这种改动会影响到连接的状态,这个太底层,最好别动。。。这次尝试又失败了,默默的在中间层加了一些重新bind的处理逻辑,效果也不是很好
8.重新回到原点,重新一个一个log去分析,慢慢的会发现,网络不稳定是一回事;中间层的不稳定,开始慢慢的浮出水面,中间层有大约20多个package,代码里有各种继承各种重写,很多静态的变量你如果不debug,根本都不知道哪里改变的,有时候改变一点点东西,上层的case就跑不起来。怎么办?凉拌,重写吧,按照自己的需求。这个花掉了几周时间,在某一个后半夜,所有case都可以在重写的中间层上跑起来了,那一刻根本没有什么兴奋,只想回家睡觉。重写后的只有2个package,大约7-8个文件,是的,上层的case其实用到的就这点。试着跑了几周,这个世界清净了,监控明显稳定多了,但是新的瓶颈又出现了。
9.中间层稳定了,就发现上层的case写的好挫,各种不靠谱,重写吧。。。又是几周,靠谱点了。
10.又遇到问题了,一次我们看监控很正常,但是某一个开发者说他们的app收不到push了。这次事故调查的结论是服务端对这个app的配置有问题。这次事故导致的是我们不再用单一的测试账号跑case,改用各个app的账号跑case,建立新的appinfo的列表,然后一个app一个app的跑。
11.又遇到问题了,当时我们有100左右的xmq,我们遇到了这样一个问题,由于当时的升级技术问题,导致了某次升级之后,有两个xmq的服务有问题,但是监控是随机落到各个xmq上,当时在很长的时间里并没有报警;这是一次事故,怎么让以后不出现这个问题呢?我们做了paritition监控,让每次的case都只落到一个xmq上,轮询所有的xmq;上线,跑了几个月,确实也发现了一些问题。
12.随着xmq的数量越来越多,轮询的时间越来越长,新的问题就产生了,如何快速知道某个xmq有问题了呢?我们考虑了多线程处理,两个方案,一个是自己写多线程的代码,另一个是使用多线程run case的工具;两个方案都做了几周,遇到了很多问题,最主要的是这是长连接,sleep会对所有线程有影响,如果某一条等消息时间过长,也会导致其它线程的case挂掉,这是跟短连接所不一样的;另一个是log如何处理,多线程的工具会把所有log打到一起,包括call stack,出了问题分析起来非常困难;最后就是稳定性,多线程受到网络的影响更重;最终我们没有采取多线程的方案。
13.case的区分度,多线程不行了,那就让case跑的更快,那么就可以更快的遍历所有的xmq;于是新的方案就产生了,首先分为p0,p1,p2的级别,p0白天跑,是用户最常用的功能,先保证这些功能不挂,p1晚上跑,这些是用户不是太常用的功能,晚点发现问题影响度相对小些,p2是剩下所有case,由于全跑下来时间太长,就几个小时跑一遍。另外一个维度是partition的监控按照xmq的顺序遍历,app的case随机落到xmq上,用以区分。
14.再次出现问题,用户看推送的统计报表的时候反应,我们昨天没有发topic消息,为什么报表显示有topic呢?查了一下,那是监控账号发的信息。啊,这次从监控上搞不定了。求助了服务器的开发,他们给监控提供了Extra字段,如果设置成test字段,统计报表就会过滤掉测试的消息数据,避免了用户的抱怨。
15.以前的xmq是用erlang语言写的,后来想换成了java语言,由于影响很重,大家都很谨慎;我们尝试了把xmq下发包中每一个字段都检查一遍,这时监控的问题就看出来了,以前只检查了一些关键的字段,没有全都检查;所以case都pass了,但是xmq下发的包,用户使用的时候,还是有问题的,因为你搞不清用户到底会用哪个字段,于是在灰度升级中就发现了测试的漏洞,前前后后折腾了好久,xmq下发的包一层包一层,拆了好几天。最后java xmq都上完线了,case中的各种检查也都加上了,xmq发出来的包结构也搞清楚了。
16.log的处理,报警邮件发出来,需要快速的定位,log的结构是否清晰,关键内容都显示,不重要的内容都删去,说起来容易,做起来不简单。三个人定义了log的格式标准,然后分分case,改吧,这个又改了一阵子
17.sd的机房满了,没有地方增加新的机器,服务端又没有多机房的方案,这时候消息会有各种延迟,监控一直在报警;开发很着急,硬件没有了,就只能改软件代码,让服务性能更好,于是引入一些bug,监控又报警;那段日子相当痛苦,我们唯一能做的就是增加sleep时间,关闭一些验证,只对重要字段验证,有些api没有返回包,都不在check。后来多机房方案出来了,迁移服务到lg,监控再连lg的服务,再报警,再处理,这个反复折腾了几周才搞定。
18.迁移到了lg,网络发生了变化,新的问题又产生了,大量网络问题导致的fail又卷土重来,这次我们放弃了五彩城机房的监控机器,在zc申请了新的监控机器,运维的兄弟在底层网络做了处理,让zc连lg接近于在lg直连,跑了一阵子,效果不错
19.随着case越来越多,监控种类越来越多,如何定时启动这些监控是个问题,最初是crontab,这个方法简单易行,但是服务挂掉你也不知道;后来使用了jenkins,这个东西麻烦的地方在于部署和自己没有办法发出报警邮件,也不能对历史的数据进行分析,而这些是我们需要的;权衡再三,我们的监控系统需要一个控制服务,这个服务应该定时run不同的监控,收集log,存数据库,发报警邮件和米聊,定期做数据分析,监控运行的过程中出了异常,应该有报警。立元断断续续的花了近2个月,在工作的间隙和业余时间写了这个控制服务,现在每天早晨会有头一天的几份数据分析报告发出来,根据这些统计数据,还是能看出服务的一些问题的。
20.经历了上面种种改造,现在监控已经逐步稳定下来了,服务的功能问题会很快的报出来,现在最大的问题还是网络问题,最近的一次定位有2点,a.可能是zc到lg的网络问题,b.FE-GW的前端可能有些问题。于是我们又增加了bind监控,遍历连接所有的xmq。对监控的改造还在继续。。。
上面只是举一些例子,事实上,遇到的问题要比这些多的多。
监控的敏感性和误报率是一组天生的矛盾,敏感性越高,误报率也越高;敏感性越低,误报率也越低;我们需要找到其中的平衡点,这才是最难的地方。
如果你问我现在的推送监控怎么样了,我不敢说它做的有多好,只是我晚上能安心的睡觉了。
最后我想说一下做监控这件事情的感受,
1.监控不是一个人的事情,这个工程关联很多团队,可能需要客户端开发,服务器开发,运维工程师和测试工程师,甚至其它团队共同协作才能找到问题的根源,才能解决很多问题。就像每一个团队就是一组齿轮,大家都把自己的那部分打磨好了,放到一起才能协同转动,最终拼接成一个精确的瑞士钟表。
2.科学界的一个不成文的定论是,基础研究聪明人是做不来的,只有天性笨拙一点的人,坚持每天做下去,做了20年30年,最终才能证明某些东西。监控是很基础的东西,不可能一蹴而就,今天晚上打冲锋,明天早晨就能实现共产主义;真的需要一些人踏下心来,认认真真的做上一年两年,也许才能做出点什么。
3.做监控是件小事情,做好监控是件大事情;大小取决于你看事物的颗粒度,看得越细致,事情就越多,做的事情就越大;看的越粗糙,事情就越小,做的事情就越小。这世界上没有什么高大上的东西,只是别人踩过的坑比你多了几个数量级,于是人家就形成了技术壁垒,这是你抄不来的,只有自己走一遍,一次次的从坑里爬出来,才会感受深刻。
4.面试中,我们经常看到面试人说自己在很短时间内,做了多少多少事情;却很少见到有人说,这些年我只做了一件事情。我想起了小野二郎,米其林三星大厨,今年90岁了,据说捏了近60年的寿司,被世人称之为寿司之神,米其林指南对他捏的寿司评价是:值得花一辈子排队等待的美味。人家花一生做一件事情,我们花一年做一件事情好不好?然后给面试官讲讲这件事情的细节,看看他的反应,嗯,应该很有意思。
今年是2015年了,我也在想今年做点什么小事?如果你想做一件很小的事情,我们可以聊一聊;如果你想做天大的事情,哥,我智商低,比我脑袋大的天,我想不明白。我只想做好一件白衬衫。?