问答 高可靠系统的重启, 快照恢复,事件回放测试

esnake0 · 2021年08月06日 · 最后由 esnake0 回复于 2021年08月31日 · 3368 次阅读

高可靠系统的重启, 快照恢复,事件回放测试。

目前能想到的,就是在回放前记录数据库状态,指定不同时间打的快照进行回放,比对回放之后的数据库状态。

请问工程师们是否有相关经验,或者哪里可以找到参考书籍/文档?

若有兴趣可私信交流,请你喝咖啡~

共收到 7 条回复 时间 点赞

这个问题看得有点一头雾水。。。这个场景是想测试啥,想测试多节点重启是否平滑,还是什么?

某个服务,主要功能是从上游消费事件消息,内存计算,异步落库。

定时(每 x 小时)和定量(每消费 y 个事件)进行内存快照,快照存储在网盘。
重启过程为

  1. 加载最近一次快照到内存, 2.回放此次快照之后的所有事件,使内存状态恢复到重启之前的状态 3.回放完成后,立即打一个最新快照,同时把内存数据再刷库一次

问题:
如何测试这个重启机制?

我的方案:
测试一:

  1. 在重启前打一个快照 A
  2. 让系统消费各种事件(包含所有可能的事件类型)
  3. 查询数据库相关表全量数据,写入文件 1
  4. 指定从 A 快照, 重启回放
  5. 查询数据库相关表全量数据,写入文件 2
  6. 比较文件 1 和文件 2, 期望除写入时间外,所有字段值完全一致

测试二:

  1. 手动指定从留存的最旧的快照开始回放
  2. 手动指定从某中间快照回放
  3. 比对两次回放之后的数据结果,除写入时间外,所有字段值完全一致

这里只关注了数据库状态,没有真正的测试到内存状态,感觉还不够完整。
大家有没有更好的思路呢?

陈恒捷 回复

感谢回复,详细说明了问题,请看楼上。

esnake0 回复

大概理解你意思了。这个功能核心是保障重启前后内存状态保持一致,实现上采用了定时全量(快照)+ 实时增量(事件)结合的方式。

这里面有几个细节点,你没有提到,想确认下:
1、内存和数据库的关系是强一致(写内存同时就写库,失败时同时删除),还是弱一致(数据库总落后于内存,定期同步保障一致)?
2、系统内这个内存具体是怎么存储的,一个全局变量,还是多个局部变量?是否有方法直接获取值(如通过外部接口 get)?
3、你的方案二有提到 "手动指定从某中间快照回放" ,感觉你这里的快照还可以进行回放,有点奇怪。能否介绍下快照实际存储的内容是什么?是类似 git 一样的所有增量集合,还是一个没有任何历史的全量记录?

感谢回复。
关于你提到的问题:

  1. 可理解为弱一致,定期同步(测试发现最高有 5 秒左右延时)。
  2. 变量类型不太清楚,对应数据字段的外部接口查询是从数据库读取。
  3. 抱歉,这里是我的表述不清。快照的内容就是内存中的数据, 没有历史的一个瞬间状态。 “手动指定从某中间快照回放”,是指将此快照加载到内存,回放是指顺序执行快照之后的发生所有事件(重置上游 kafka 的 offset 为此次快照时的 offset, 重新消费消息, 由消息解析出事件,根据事件对内存数据进行操作,消费完历史消息后立即再打一次快照,自此启动过程结束,开始正常服务)。

补充说明: 系统设计是 event sourcing 模式,对此模式我的理解还不太深,欢迎讨论。

好的,大概理解了。你这个感觉和之前接触过的流式处理 flink 的比较接近,要求重启后要恢复到上次消费消息的状态,然后继续往后走,保障消息处理不重不漏。

event sourcing 模式搜了下,是 Martin Flower 大神设计的微服务架构模式,通过统一的事件流来触发各个子系统的操作。但你这个好像又有点不一样。event sourcing 的核心点在于 event store ,以及聚合资源库。聚合资源库就是提供应用了最新事件后的最新数据状态,供外部查询使用。我看的是这个:https://zhuanlan.zhihu.com/p/38968012 ,如果有理解不正确的欢迎指正。

回到你这个功能,个人理解核心点应该还是保障重启后消息处理的不重不漏,即重启后可回到重启前状态。你这里的场景一、场景二应该可以满足正向场景的要求。但有几个实际使用中会出现的场景可能需要考虑下:

  1. 事件是一个动态的存储内容,系统怎么知道回放到哪个事件,刚好等价于重启前的那个事件?此处如果有一些存储机制,也需要测试一下它的可靠性
  2. 你这里提到正常运行时,内存和数据库基本延迟在 5 秒内,但你的快照定时是按小时/事件数来的,应该远超过 5 秒。那为何不考虑直接把数据库内容加载到内存,而是要从快照 + 事件来呢?这个点可以了解下,可能背后包含某些取舍。
  3. 重启除了单纯的重启服务,还可能存在修改事件处理逻辑后重启(比如上线发布)。此时可能会出现修改逻辑后,事件处理结果和原来的不一致,把快照到重启前那一刻的事件都处理完,也不一定内存就回到当时状态。这种情况应该怎么处理才正确,我觉得可能也是需要去考虑的点。因为在根本设计上,你实际就是通过重复消费来恢复状态的,那就有可能出现逻辑改动引起数据处理结果不一致的情况。

你好,感谢回复。
抱歉近期由于各种原因,较忙回复你不太及时。
关于你提到的几个要点。

  1. 系统不知道该回放到哪个事件,只是在启动后,从快照记录的 offset 开始,去消费之后的事件,进行处理,对系统而言,回放的事件和未处理过的新事件是一样的。
    对于每一个输入事件,存在一个全局唯一且单调自增的序列号,系统处理后产生的输出事件,会携带此序列号,也就是说,输出事件的序列号为 “输入事件序列号 - 此事件的第 n 个输出事件” 这样的格式。
    下游关心的是对于事件的处理 “不漏”,但允许重复,重复事件意味着其序列号也完全一致,因此可根据序列号去重(当序列号小于等于前一个所处理的事件序列号,则为有效事件)

  2. 数据库只记录了部分关键数据,一些中间计算数据并未存储。

  3. 此处的处理确实需要慎重考虑,感谢您的意见。

esnake0 关闭了讨论 09月15日 21:05
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册