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