本文为大家讲述一次线上生产系统事故的解决经验,代表线上生产系统JVMFullGC可能导致的严重故障。介绍业务场景,先简单说一下线上生产系统的一个背景,由于文章只是作为案例,所以弱化了大量的业务背景。简单来说,这是一个分布式系统。系统A需要通过网络请求将一个非常核心、关键的数据传输给另一个系统B。其实这里考虑了一个问题。如果A系统刚把核心数据传给B系统,B系统就莫名其妙的崩溃了,会不会造成数据丢失?因此,在这个分布式系统的架构设计中,非常经典的Quorum算法。简单来说,这个算法就是系统B必须部署奇数个节点,比如至少部署3台机器,或者5台机器,7台机器等等。那么系统A每向系统传输一条数据,就必须向系统B部署的所有机器发送一个请求,同时向系统B部署的所有机器发送一条数据。确定A写入的数据系统A到系统B成功,要求系统A必须在指定的时间范围内成功传输到系统B所在的机器超过Quorum的数量。例如,假设系统B部署了3台机器,那么它的Quorum数为:3/2+1=2,也就是说系统B的Quorum数为:所有机器数/2+1。因此,系统A需要判断一个核心数据是否写入成功。如果B系统一共部署了3台机器,那么A系统必须在规定的时间内收到B系统所在的2台机器的写成功响应。这时系统A可以认为这条数据已经成功写入系统B,这就是所谓的Quorum机制。也就是说,在分布式架构下,在系统之间传输数据时,一个系统必须保证它传输给另一个系统的数据不会丢失。机器响应写入成功。这种机制其实在很多分布式系统和中间件系统中都有广泛的应用。我们的在线分布式系统也是使用这种Quorum机制在两个系统之间传输数据。给大家放一张图,我们来看看这个架构是什么样子的:如上图所示,图中很清楚地展示了一条数据在系统A和系统B之间传输时的Quorum机制。下面我们用代码来给大家看看上面的Quorum写机制在代码层面是什么样子的。PS:因为这个机制其实涉及到很多底层的网络传输、通信、容错、优化,所以下面的代码做了很大的简化,只表达了一个核心意思。上面是大大简化的代码,但是核心意思表达的很清楚。大家可以仔细看两遍,其实非常容易理解。这段代码的意思很简单。说白了就是异步启动线程向系统B的所有机器发送数据,同时进入一个while循环等待系统B的机器Quorum数量返回响应结果。如果超过指定的超时时间还没有收到预期数量的机器的结果,则判定系统B部署的集群出现故障,则系统A直接退出,相当于系统A宕机。整个代码,就是这个意思!问题是光看代码不难,但问题是当你在线运行的时候,并没有你写代码时想的那么简单。有一次,在线生产系统运行过程中,系统整体负载非常稳定。本来应该没什么问题的,突然收到告警,说系统A突然宕机了。然后开始检查,左检查右检查,发现B系统集群都是好的,应该没有问题。然后查看系统A,发现系统A的其他部分没有问题。***结合系统A自身的日志和系统A的JVMFullGC的垃圾回收日志,可以找出具体的故障原因.定位问题的原因其实很简单,就是系统A在线运行一段时间后,偶尔会长时间进行StoptheWorld的JVMFullGC,即大规模垃圾回收。但是,此时会导致系统A内部大量的工作线程冻结,不再工作。直到JVMFullGC结束,工作线程才会恢复运行。我们看下面的代码片段:但是系统A这种莫名其妙的宕机是不正确的,因为如果没有JVMFullGC,上面的if语句是不成立的。会暂停1秒进入下一个while循环,然后就可以收到系统B返回的Quorum数量的结果,这个while循环可以中断继续运行。结果JVMFullGC卡了几十秒,莫名触发if判断执行,A系统莫名退出关闭。所以,在线JVMFullGC导致的系统长时间卡顿,真是导致系统运行不稳定的隐形杀手之一!解决问题至于上面代码稳定性的优化,也很简单。我们只需要在代码中添加一些东西来监控上面代码中是否发生了JVMFullGC。如果发生JVMFullGC,就自动延长expireTime。例如以下代码的改进:通过上述代码的改进,可以有效优化线上系统的稳定性,保证在JVMFullGC发生时,不会因为异常宕机而随机退出。
