背景

微服务场景下需要同步信息的场景。

还是前文的栗子: 如下微服务

  • 支付服务:负责完成支付操作,其中有支付流水数据。
  • 账单服务:指定时间生成账单给用户,其中有账单流水数据。

此时产品上有个需求,在支付管理端根据是否出账搜索支付流水,而出账是账单服务的功能。所以这里涉及到信息的同步,那么,我们怎么保证同步一定能成功呢(最终一致性)。

消费者

保证在队列中的消息,一定会被消费。用白话来讲,就是不停消费直到成功。

方式比较简单:消息队列都使用手动提交。处理完了,再保证提交。尽量遵循触发-查询机制,提供可重入性,即消息队列只传递id这种非实质信息,收到之后再通过rpc查询拉取完整数据来更新。

生产者

主要是发送消息到队列这步的可靠性考量

方案一:浅尝辄止

以递增的时间间隔重试5次。如果失败了,上报到日志和告警,人工介入。同时,具体业务准备好重试的脚本。根据实时的情况进行处理。优点:

  • 简单,能解决瞬间的网络抖动造成的失败。

缺点:

  • 可靠性低。在消息队列故障30分钟这种场景下,无法自动恢复,同时从日志捞取信息,也不是特别方便。

方案二:内有波澜

失败之后,内存维护一个重试队列,先由5,10,20, 40, 80, 160, 320s的间隔重试。之后以5分钟一次的间隔请求。同时,也要打入日志系统,告警通知。

优点:可靠性会比一高很多,在消息队列故障30分钟这种场景下,也能自动恢复。可以做成package的方式,方便接入。

缺点:

  • 服务重启或者机器挂了,消息就丢了。

限制:

  • 消息处理需要遵循触发-查询机制

方案三:内有波澜plus

失败之后,内存维护一个重试队列,先由5,10,20, 40, 80,160, 320s的间隔重试。然后append到本地文件,同时以5分钟一次的频率做重试。重试完成之后,从磁盘中删除对应信息。当服务重启,从磁盘把数据导入内存即可。

优点:

  • 可靠性会比一高很多,在消息队列故障30分钟这种场景下,也能自动恢复,可以做成包的方式。
  • 具有比较强的通用型

缺点:

  • 增加了和磁盘打交道的逻辑,引入了文件io。

限制:

  • 消息处理需要遵循触发-查询机制

方案四:有备无患

实现任务重试微服务,该服务通过维护一张任务表,重试任务直到成功。相当于是消息队列这个可靠中间件有问题,就丢给这个重试服务这个自己实现的“中间件”。

优点:

  • 可靠性会比一高很多,在消息队列故障30分钟这种场景下,也能自动恢复。
  • 具有比较强的通用性。

缺点:

  • 成本变高,需要额外服务。同时,如果服务也挂了,还是得依赖上报。(当然,上报也可能挂了)

限制:

  • 消息处理需要遵循触发-查询机制

以上方案中,三,四基本能解决重试阶段写入消息队列的可靠性问题。但针对另一个场景:正想写本身服务就没了的情况(比如oom导致服务被系统kill了) 还是不行。

方案五:先入为主

要做变更之前,先写入到消息同步微服务,告诉他要做什么事(把什么消息放入消息队列),和流程最长执行时间,以及发给谁。该服务维护一张任务表,任务初始处于未激活状态。

等业务做完要同步的时候,再rpc请求触发激活。

任务管理微服务如果发现一个任务,超过最长执行时间没有激活。就说明激活rpc失败了,或者是服务崩溃,本身就没做变更。此时,自动激活即可。

优点:完全可靠(事务可能还是会失败,可靠指数据一定最终一致)。

缺点:

  • 开发成本比较大,同时会增加消息调用。
  • 增加一个节点,事务失败得可能性更高。

限制:

  • 消息处理需要遵循触发-查询机制

总结

推荐:方案三

推荐理由:成本不高,可靠性较强。可靠度99.99%。(此处不论代码本身bug)

作者:牛牛码特

来源:juejin.cn/post/6844903924915240974