事故回顾
背景介绍
有一个服务F, 下游接了很多个其它服务,S1, S2,...。所有请求通过 F 到达下游服务 Si, Si 处理请求后结果通过 F 返回给用户。 F 根据不同的请求类型将请求发送至下游的一个或多个服务模块。我们有新的服务要接入,因此在 F 的下游新接入了一个服务 Sk,该 Sk 与其它服务没任交集,也就是 Sk 与其它下游服务之间不会产生任何影响。但是就是新增加的这个服务 Sk 上线出了大问题了。
事故回放
- 添加新服 Sk 后开发与测试阶段一切正常,开发环境与测试环境都正常工作;
- 上线至首个机房首台时,验证功能时发现其服务正常但新添加的服务 Sk 没有返回任何数据,原有服务一切正常;
- 登录至首台查看日志,日志显示好像调度了下游其它服务【事实上并非如此】;
- 查检 conf 文件中 Sk 的配置,发现确认 Sk 的配置正常,与线下一致;
- 怀疑上线线版本并非测试版本,进行测试环境 bin 文件与上线 bin 文件的MD5值对比;
- 二进制文件 md5 值一致,说明提测版本与上线版本相同,此时进入懵逼状态,不知道从何查起;
- 向老大汇报后,老大指示将首台的 data, conf 等目录依次拉取到线下进行对比。用线上目录覆盖线下目录查看服务状态;
- 替换了线上的conf目录后复现线上问题,而 conf 中唯一的区别:线上配置的下游个数比线下的下游服务个数多。这是因为,线下为了方便将有一些不必要的下游服务注释掉了。
- 我们注释掉其中一个服务(非Sk),此时发现新服务正常了,这就奇怪了。事实上当时根本没有意识到会是服务个数导致的,现在想来在这一环节就应该发现下游服务数目这个问题。
- 回去查代码,发现框架定义最多允许 16 个下游服务,而我们新添加的服务是第17个服务,导致新的服务加载失败。但是代码里并没有输出任何日志,导致没有意识到数量的限制,从而这样被坑了一个下午。
- 删除一个暂时不用的下游服务再次上线,最终一切正常。
总结
回顾这个过程,主要有以下几点经验值得铭记。
- 通过对比查找问题。
当出现这种在某些场景下正常,其它场景下异常的情况。最简单有效的方式就是两种场景进行 DIFF 比较。本次事件发生时,我是处理彻底懵逼状态,完全不知道为什么,找不到原因也不知道从何下手找原因。幸好老大及时让我进行线上与线下的对比,从而才能一步一步地找到问题的所在。
对比过程中有两点要注意:一个应用或者说服务,除了运行的代码外,其配置文件,所需有的其它资源文件都是它的一部分,不能只关注在代码本身。我在思考的时候就一直纠结于思考代码哪儿出错了,而没有把注意力从代码上移开关注配置文件。其次,不要把一切想的理所当然。这次查找问题的过程中, 我就理所当然的以为新加一个服务就新加一个配置,并且配置是正确的,那就没什么问题。但是我却忽略了资源数的影响,从而定位问题在conf中后却立刻意识到资源数量的限制。 - 尽量保持线下与线上的一致。
其如果线下与线上配置一致的话,问题在开发阶段就应该能发现了。就是因为我在开发环境里面删除了许多用不上的服务导致没问题没有暴露出来。实际上,在给QA部署测试环境的时候,这个问题就暴露过一次。当时QA从线下拉下服务,并用提测的版本覆盖线上拉下来的程序(仅bin文件 )时,我就发现程序启动就不能调度我的下游服务。但是当时我觉得很可能是QA环境有问题,就草率地把 F 的其它一些下游服务配置也给删除了。这样服务就OK,最后导致上线的时候才又一次暴露出这个问题。虽然很多时候由于很多人并发开发,不断地有人上线,要想保证线上与线上完全一致也是很难的,但是提测的时候还是尽量保证其一致性,否则再出这种坑,很难找。 - 代码加载服务的坑
不得不说 F 服务代码里面的坑了。F 在加载下游服务配置时, 直接依次遍历配置中的所有下游服务配置,并加载到一个数组中,这个加载循环使用的是配置的个数做限制。而数组的长度也就是允许的最大服务数是固定的,所以啊当配置的服务个数超过这个限制时就出现了内存泄露的问题,不过在本此事故中并没有因此出core。但是, 框架没有任何提示信息说明配置的下游服务超出允许的最大个数了,导致了这个问题这么难查。正确的写法应该是使用数组长度做循环结束条件,发现配置个数超了过后直接报错,这样也就容易追查问题。
网友评论