稳定性的海因里希法则
引子-多维空间
我们生活在三维空间。看一本书的时候,每一页和下一页的内容不相同。每一页相当于一个二维空间。生活在这个空间并不知道下面要发生什么。但是生活在三维空间的我们却清楚的意识到下面一页的内容是定好的,基本不会改变的。
而生活在四维空间,多出一个时空的概念。他们能看到过去和未来。对三维空间的我们来说,就像平时所说的先知。
生活在五维空间,能够看到各个因素的变化随着时间产生不同的变化的过程。对一个人来说,就是他的各种可能性。就像《复联3》里奇异博士通过时间宝石做的事情。
以上都是别人想过的事情。四维空间的坐标轴是时间,而五维空间的坐标轴是什么呢?我在想,可以是对世界规律的掌控。它把各个因素串联起来。对规律的掌控决定命运。
如果说四维空间多出来的一维是时空的话,五维空间多出来的一维就是命运。每个人都有对规律的掌控,所以会在做一件事情的时候很确信,比如知道自己的努力终究会有回报。就好像同样的输入到同一段程序肯定得到同样的输出一样。
稳定性是一直是高速发展中服务的一个难题。发展变化会引入更多的因素,最终得到的真理是:是程序就会发生故障、故障不可避免。而把这件事情放在五维空间里看,我们可以控制问题发生的各个因素,来更好的把控整个过程,影响最终的结果。
OK,做了一个比今天主题更晦涩的引子。今天主题是讨论稳定性的一个因素:解决问题要彻底。对应的规律就是“海因里希法则”。
海因里希法则和稳定性
海因里希法则是美国著名安全工程师海因里希提出的300:29:1法则。意思是:当一个企业有300起隐患或违章,非常可能要发生29起轻伤或故障,另外还有一起重伤、死亡事故。
对于稳定性方面的启发是:如果能在发生第一个隐患时找到事情的根因,就可以避免后续的故障和事故。
具体事例
场景
一个服务在每天固定的时间点会发出服务不响应的报警。
任务
需要找到问题的原因并解决。
行动
首先分析到自己掌握的信息太少。服务不响应的直接原因是什么呢?
造成服务不响应的原因有:
1. CPU使用完程序无法进入执行状态
2. 程序在做别的事情,自己停止响应外部请求
3. 根本没有资源进行服务的响应,比如硬件损坏、断网
想找到这个直接原因需要监控信息作为抓手。那程序要解决的第一个问题:我们的监控是否够完善。
完善监控
小米开源的falcon监控在客户端可以使用falcon-agent采集单机的包括cpu、内存、磁盘、IO在内的各种数据和指标。另外,还支持proxy-gateway进行自己附加各种指标的上报。
对于Java程序来说,java.lang.management包里提供了jvm的cpu、younggc、fullgc和jdbc的情况等。事实上,很多公司都利用这个指标上报,可以覆盖大多数场景对监控的需求。所以,默认是不需要在线上开启gc日志打印的。
在这个场景下,如果我们通过监控观察到,在问题时间点,一个两分钟内有超过3次的fullgc,并且有一次fullgc时间超过1s。可确定fullgc问题是服务不响应的直接原因。因为fullgc有一个过程是stop the world来执行垃圾收集。符合服务不响应直接原因的第2种情况。
查明原理
问题的直接现象:fullgc找到了。下一步是找到fullgc的原因。一般问题解决思路有两个方向,需要配合使用:
1. 梳理任务
比如问题点都在干什么事情。之前写过《一个请求过来都经过了什么》的文章,之所以要梳理这些,就是要了解所有的流程,在遇到问题的时候,知道从哪些来排查。
再比如确定是代码逻辑引起,比如现在这种情况,则可以直接review代码。
2. 查看数据
比如日志、监控等。
梳理任务和查看数据的时候,都要对各个环节的原理很清楚。比如这个问题要用到Java内存管理、程序的业务逻辑。
很多大厂在面试时会一层一层的问,问到很底层。很多人抱怨说问的这些技术在实际工作中用不到。实际上确实能用到,用在排查问题的时候。有可能最后问题的原因是JVM的一个bug。
在这个问题中,从代码review时看到有全量将数据库表中的数据和外部其他地方的全量数据加载到内存中。猜测:数据量太大,新生代存放不下,直接进入老年代造成fullgc。有猜测,就算是显而易见的事情,也需要数据支撑。
为什么显而易见的事情也需要数据支撑呢?因为所谓显而易见,有可能这个基础是不对的。比如在程序中,理所当然的利用异步、多线程来提高程序的执行速度。如果需要做这个优化,我建议按按照最普通的串行执行,得出数据,和并行比较后再看是否真的提高了执行速度。
其实,串行执行是个很高大上的设计方法,叫做:无锁化的串行设计。Netty就是采用这个理念,尽量少的采用线程间通信。因为共享资源的并发处理不当,会带来严重的锁竞争,最终会导致性能下降。
回到问题,基于需要数据支撑的原因,我们需要查看对象的大小和可用内存的大小。
查看对象大小:
1. 可添加日志,这些对象的大小打印出来再次运行。打印对象大小,lucene-core里有提供RamUsageEstimator类直接使用。java内部查看内存大小的方法,私有,需要hack。
2. 可借助工具,比如jvisualvm等,分析内存dump文件。
查看可用内存大小:
jmap -heap命令可查看目前jvm的实际配置。jmap -histo:live可查看内存切面信息。在上面已经做了查看对象大小处理,还要查看内存切面信息是为了估算其他正常对象的大小,从内存估算中除去。因为在jmap -histo:live看到[C(char型)的大小、java.lang.String的大小一般是打印日志产生的、[I(整数型)一般是上报监控数据产生的正常数据。却是占比最大的。
结果
这个问题最终通过对代码逻辑进行调整,将全量数据分批处理,同时增大新生代比例解决了问题。
总结
本文通过一个实例阐述了怎么通过海因里希法则提升系统的稳定性。
举的例子比较简单。就是一个逻辑不合理(使用全量数据处理)造成的fullgc问题。里面有两个要点:完善监控和查明原理。在排查过程中,就算每一步是显而易见的,也需要找到数据的支撑和公理的支持。深度排查产生的TODO,触类旁通的对所有服务进行同样的治理,是有效避免问题再次发生及扩大的手段。
这种手段很多公司是通过制度来保证的,就是平时常听到的casestudy、事件复盘嘛。