微服务的普及,使用队列处理服务之间通信成为一种潮流,利用队列消息解耦系统不可避免的会出现数据不一致问题。
产生原因
发布方问题:运行的系统无法避免的存在单点故障问题,保存本地事务和推送队列无论是哪个先执行都无法保证这两个一定都执行完成,先保存本地事务,服务发生故障,消息没推送,反之同理。
订阅方问题:消费收到消息后,执行过程中发生错误或宕机,导致没执行成功。
发布方解决方案
选出两种比较简单的并且不同体系的产品来对比(阿里的RockMq/.net core的CAP框架),先了解下两种方案的实现过程
.net core 的CAP框架
首先,在你项目运行时候会在你项目指定得数据库生成一张表(cap.published)这张表是用来存储要发送消息的日志。消息有三个状态(Scheduled,Succeeded,Failed),启动程序时候同时还会启动一个监听程序,隔几分钟把表中Failed的消息重新发送。
我用下订单来举个详细例子
这种设计方式相对于2PC/TCC来说,复杂性降低了一个级别,而且支持多种数据库和消息队列,扩展性极强,我觉得缺点应该是依赖数据库,发布消息需要插入日志消息到数据库,受限于单机数据库的瓶颈,如果分库分表还要处理这块,要么就得在数据库层面做分库分表。CAP文档
阿里的RocketMq
Rocketmq的功能十分强大,有普通消息,定时消息,全局/局部顺序消息以及事务消息,在阿里云可以直接买服务来使用。RocketMq的事务采用了TCC模式(Try-Confirm-Cancel)
提交mq半事务
半事务消息成功
确认mq事务
回查事务
回滚mq事务
有以下几种情况
1.下图是完美成功情况
2.下图是 消息发送方没有通知到位的情况
细节可以上阿里云搜Rocketmq查看该产品使用手册。
阿里的RocketMq 与 CAP 使用架构场景分析
CAP适合于发送队列消息与业务在同一个服务的场景(消息一致性完全依赖本地事务),而中台架构就不适合,因为中台架构分基础服务和业务中台,队列一般在业务中台跟业务挂钩,比如有一个订单基础服务,但有N个应用中台都是调用这个订单基础服务存储订单数据,每个中台都相当于一个应用,逻辑可能差异很大,更倾向于在中台决定推不推送消息和推什么消息,这种情况下CAP就完全不适合。而RocketMq正好解耦了消息保障功能
订阅方解决方案
1. 系统突然崩溃导致丢失
系统的瞬态故障产生的问题,重试便可以解决
2. 程序出现bug导致一直重试一直失败
这种情况重试是解决不了问题的,需要开发人员做好PlanB,重试N次不成功便执行PlanB。
3. 幂等性问题
发送端监听重复推送导致的问题,有两种方案可以解决。
- 根据业务状态判断,比如订单支付完成后重复推送根据完成状态忽略掉重复那条
- 执行业务逻辑前查询该消息是否被执行过,因为重复推送消息的消息id必然是一样的,可以根据这个消息id进行判定。
- 针对资源型数据,订阅会接收整个资源数据的实体,直接更新进数据库,此时需要有个更新时间,比对数据库这条数据的更新时间,只更新最新的。
回到选中的两大框架的消费端
CAP框架
同发送端一样,在你项目运行时候会在你项目指定得数据库生成一张表(cap.received)这张表是用来存储消费的消息日志。消息有三个状态(Scheduled,Succeeded,Failed),启动程序时候同时还会启动一个监听程序,隔几分钟把表中Failed的消息重消费。
发送端演示下单,消费端就来个扣库存的。
RocketMq
订阅消息有两种方式:
1.通过http拉取(注意 官方的SDK的http请求性能很差,建议最好自己写)
2.开启tcp监听获取(注意 .net的TCP监听获取消息的SDK至今没有Linux版本--2019年12月13)
获取消息需要在代码里进行ack确认
总结
CAP框架:功能单一,使用简单,.net体系的可自行扩展,适合自治微服务架构。
RocketMq:功能丰富,维护简单,稳定性强,但使用还需要自己做相关开发,使用成本高,对.net支持不太好,适合中台服务。