自愈:问题自动发现与修复

2020-03-17  本文已影响0人  tiankongus

分享一个颇为曲折的故事。

一、背景

早在2016年的时候,我实现了一个监控系统,自动检查数据平台各节点的基础数据是否一致。

可是,这个仅仅是监控系统,用于检验缓存实时更新功能的正确性。

2019年春节前夕,部门提出要做一个万能的通用的自愈系统。

当时各种脑暴讨论,讨论到最后,发现要做到万能与通用,这个自愈系统就需要与业务无关,也就变成了一个状态机模式的调度系统。

而当时周围还没有任何一个自愈相关的实践,大家不仅希望万能通用,还希望与业务有关系,后来大部分人都有新的项目了,这件事便不了了之了。

年前的时候,我们团队的服务遇到一个问题,然后做了一个实实在在的自动发现与自动修复系统,为自动修复积攒了不少经验,下面分享给大家。

二、几年后出问题了

image

还是上面的数据平台,基础数据通过内部设计的一套通知机制,几乎做到数据完全一致。

而对于非基础数据,比如第三方储存或服务提供的数据,无法走内部这一套通知机制。
这部分数据修改后,生效时间会比较久。

为了加速第三方服务的生效时间,第三方服务也复用了内部的通知机制。

但是这样有一个问题。
缓存服务收到更新通知后,会去第三方服务拉最新数据,此时第三方服务有很小的概率返回旧数据。

这导致第三方服务数据不一致问题小概率性出现。

……

巧妙的是,以前底层cms的很多计算逻辑都是通过各种脚本定时完成的。
这使得每计算一个数据,都会触发一次写操作。

这种多次写恰好修复了这个不一致问题。

因为第一次写的时候,第三方服务会有小概率计算出旧数据。
几秒后第二次写的时候,第三方服务依赖的下游是旧数据的概率就非常小了。
实际情况时,会写很多很多次,所以概率被无限缩小。

PS:对于后面的重复写,大家可以理解为第三方服务计算的新数据没变化,但是缓存认为有变化,再次去拉取第三方服务。

就这样第三方服务运行了好几年,几乎没出现什么问题。

……

不幸的是,春节的前几周,底层cms升级改造正式上线,所有计算逻辑只会写一次。

这使得第三方服务问题暴露出来,被无数运营投诉。

让底层cms暂时回滚是行不通的。
对数据系统的架构进行重构,使这个第三方服务支持快速更新,短期内也没那个时间。
所以做一个自愈系统就显得非常有必要了。

三、自愈系统架构

简单思考下,自愈系统大概分为三大模块:数据输入模块、数据拉取模块、数据对比修复模块。

如下图

image

数据输入模块一般是从消息队列接收消息。
这里可能还需要对输入的数据进行过滤标准化等预处理逻辑。
最终将需要监控的数据放入任务队列。

由于不同任务需要等待不同的时间才能启动检查。
任务队列可以是一个按处理时间排序的列表。

数据拉取模块每次从任务队列顶部检查是否有到达时间的任务。
有了取出,先拉取基准数据(认为是正确的),再拉待校验的数据(可能需要拉很多接口的数据)。
当然,这里与数据输入一样,需要对拉取的结果进行过滤与标准化。

之后就是对比数据是否一致,不一致了调用修复接口进行修复。

上面就是一个自愈系统简化后的模型。

四、加强版自愈

年前的时候,让一个同事做了这样一个系统。

那个版本为了快速测试流程,很多参数是 hardcode 的。
我简单的 codeview 了架构流程,看着没啥问题。

后面我提出一个要求:这些参数需要配置化。
于是相关参数被改成配置文件读取后,就直接发布上线了。

上线后的一个月内,运营也都没有来反馈问题了。

……

可是,半个月前,运营突然又大面积反馈这个问题了。

我心中有一个很大的疑惑。
如果自愈系统有问题,一个月前就应该不断的遇到问题。
如果自愈系统没问题,这些问题就应该被自动发现自动修复。
难道仅仅是概率问题?

于是我同时要到 自愈系统和 第三方系统的代码,进行 codereview
然后发现第三方系统存在两个问题,自愈系统存在一个过滤问题。

将问题反馈给相关负责人后,第三方系统的问题被修复了一个,自愈系统的过滤问题也被修复了。

但是运营依旧在投诉,这说明问题依旧存在。

此时,我们正处于组织架构调整期。
第三方系统 和 自愈系统的负责人都去做其他新项目去了。

问题还是需要解决,于是我开始接手这两个服务了。

……

接手后需要做两件事情。

第一件事是修复第三方系统遗留的那个已知问题。
第二件事是分析自愈系统为啥没有发现问题、修复问题。

由于数据节点众多,目前自愈系统检查节点数据的逻辑是抽样拉取的。
分析了之前有问题的数据,如果数据有问题,是必现的。

难道刚开始那几秒,数据在反复变化?
于是我猜想,一次抽样可能发现不了问题,全部计算量又太大。

一种不错的方法是有策略的多次检查。

最常见的策略有:等差策略、指数策略。

等差策略就是每隔多少秒触发一次检查。
比如第5、10、15、20、25、30秒检查。

指数策略就是每次间隔时间翻倍。
比如第5、10、20、40、80、160秒检查。

我对这两个策略都不是很满意,因为时间间隔的太近了。

于是我引入了阶乘策略,即相乘的因子每次加一。
比如第5、10、30、120、600、3600秒检查。

算法确定后,就是代码实现了。

将算法封装在一个对象内后,实现还算简单,很快我就上线了。

image

当我分析策略的正确性时,我惊呆了。
数据拉取模块竟然有一个隐藏很深的BUG,使得结果永远都被认为是一致的。

自此,我前面提到的疑惑算是得到了解释,确实是概率问题。自愈系统从来没正常执行过。

问题修复后,运营果然几乎不反馈问题了。
后来他们又反馈了一个问题,分析之后,发现是新功能不在监控范围之内,我补充进去后,然后到现在为止再也没收到反馈了。

五、回顾

回顾一下这个自愈系统,整个流程大概确定了。

大概如下:

1、MQ 输入任务、过滤、标准化
2、有策略的进入任务队列
3、调度任务拉取基准数据与对比数据,结果标准化
4、任务结果对比,不一致时进行自愈修复

疑惑解开之前,我引入了有策略的多轮检查机制来确保问题被发现与修复。

而最终的问题竟然是一个隐藏的BUG。

这个问题属于逻辑BUG,只能引入单元测试来发现。

由于涉及到各种接口网络调用,还需要使用 MOCK 钩子来解决依赖。

单元测试和MOCK钩子我在个别项目中用到过,后面有时间了,也引入到这个项目来。

到时候再给大家分享一下单元测试的实践与MOCK钩子的实践。

《完》

-EOF-

上一篇下一篇

猜你喜欢

热点阅读