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

作者: | 更新日期:

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

本文首发于公众号:天空的代码世界,微信号:tiankonguse

一、背景

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

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

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

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

而当时周围还没有任何一个自愈相关的实践,大家一直在讨论通用性,后来大部分人都有新的项目了,这个就不了了之了。

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

二、几年后出问题了

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

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

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

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

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

……

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

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

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

这样第三方服务的数据可以认为是完全一致的了。

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

……

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

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

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

三、自愈系统架构

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

如下图

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

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

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

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

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

四、加强版自愈

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

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

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

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

……

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

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

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

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

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

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

我只好开始接手这两个服务了。

……

接手后需要做两件事情。

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

由于数据节点众多,目前自愈系统发现问题的逻辑是抽样拉取的。
于是我猜想,一次抽样可能发现不了问题,全部抽样计算量有太大。

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

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

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

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

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

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

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

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

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

自此,我前面提到的疑惑算是得到了解释。

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

五、回顾

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

大概如下:

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

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

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

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

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

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

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

《完》

-EOF-

本文公众号:天空的代码世界
个人微信号:tiankonguse
公众号ID:tiankonguse-code

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越