文章目录
- 什么是数据分发
- 场景
- 作用特性
- 缺点
- 微服务数据分发一致性
- 一致性方案
- 数据双写
- 事务性发件箱(Transactional Outbox)
- 变更数据捕获(Change Data Capture, CDC)
- 当前成熟技术方案(CDC企业级项目)
- 一致性方案对比
什么是数据分发
场景
微服务架构下,不同服务单一数据源原则只能使用自己的数据源,对于其他服务只能通过远程获取,这样相互影响增大,耦合性高,同时实现也比较复杂,可通过共享部分数据来简化对其他服务的数据获取过程。这种情况下可使用分发机制,实现不同微服务之间的数据共享。
作用特性
- 降低业务之间的松散耦合度,方便不同业务的数据隔离和独立
- 提高系统的可扩展性,增强系统性能
- 可保证单一的数据来源(Single Source of Truth)-元数据,分发的数据将作为元数据的复制
缺点
- 元数据与复制数据的一致性需要维护,强一致可能无法保证,可要求最终一致性
- 需要使用分布式事务用于解决一致性问题
微服务数据分发一致性
一致性方案
数据双写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FBUOj3rQ-1611286428295)(http://doc.szkunton.com/uploads/tec-share/images/m_17e7adb563e4a161aa96b3b2a727b543_r.png)]
微服务A把数据写入DB,同时还要把数据写到MQ,如何才能保证双写的事务性(保证两个操作都正确完成)?
执行步骤:
- 第一步先更新数据库,如果更新成功,那么result设为true,如果更新失败,那么result设为false;
- 第二步,如果result为true,也就是说DB更新成功,那么我们就继续做第三步,向mq发送消息
如果发消息也成功,那么我们的流程就走到第四步,整个双写事务就成功了。
如果发消息抛异常,也就是发消息失败,那么容器会执行该方法的事务性回滚,上面的数据库更新操作也会回滚。
问题
- 如果发消息抛异常了,并不保证说发消息失败了,可能只是由于网络异常抖动而造成的抛异常,实际消息可能是已经发到MQ中,但是抛异常会造成上面数据库更新操作的回滚,结果造成两边数据不一致。
事务性发件箱(Transactional Outbox)
发送MQ消息之前,先将发送消息放到数据库(outbox),然后通过一个中介服务不断从outbox轮询数据进而发送到MQ,这样数据的更新和outbox数据将在同一个事务下执行
在数据库中,除了订单Order表,为了实现事务性双写,我们还需增加了一个发件箱Outbox表。Order表和Outbox表都在同一个数据库中,对它们进行同时更新的话,通过数据库的事务机制,可以实现事务性更新。
Order Service流程订单示例:
- Order Service先将新订单数据写入Order表,然后它再向Outbox表中写入一条订单新增记录,这两个DB操作可以包在一个DB事务里头,实现事务性写入
- 引入一个称为消息中继Message Relay的角色(定时查询Outbox表微服务),它负责定期Poll拉取Outbox中的新数据
- 将新数据Publish发送到MQ,如果写入MQ确认成功,Message Relay就可以将Outbox中的对应记录标记为已消费。这里可能会出现一种异常情况,就是Message Relay在将消息发送到MQ时,发生了网络抖动,实际消息可能已经写入MQ,但是Message Relay并没有得到确认,这时候它会重发,直到明确成功为止。所以,这里也是一个At Least Once,也就是至少交付一次的消费语义,消息可能被重复投递。因此,MQ之后的消费方要做消息去重或幂等处理。
总之,事务性发件箱模式可以保证,对Order表的修改,然后将对应事件发送到MQ,这两个动作可以实现事务性,也就是实现数据分发的事务性。
注意
这里的Message Relay角色既可以是一个独立部署的服务,也可以和Order Service在同一个服务。生产实践中,需要考虑Message Relay的高可用部署,还有监控和告警,否则如果Message Relay挂了,消息就发不出来,然后,依赖于消息的各种消费方也将无法正常工作。
案例Killbill Common Queue
变更数据捕获(Change Data Capture, CDC)
利用了数据库的事务日志记录,捕获记录变更日志并发送到MQ
数据库变更记录日志
- 对于数据库变更提交操作,都记录所谓事务日志Transaction Log,也称为提交日志Commit Log。比方说MySQL支持binlog,Postgres支持Write Ahead log
- 事务日志可以简单理解为数据库本地的一个文件队列,它记录了按时间顺序发生的对数据库表的变更提交记录
Order Service一个新订单捕获数据流程:
- Order Service将新订单记录写入Order表,并且提交。这是一次表变更操作,这次变更会被记录到数据库的事务日志当中,其中内容包括发生的变更数据
- 我们还需要引入一个称为Transaction Log Miner(事务日志监控者)这样的角色,这个Miner负责监控数据库事务日志变化,如果有新的变更记录,Miner就会捕获到变更记录
- Miner会将变更记录发送到MQ消息队列,同之前的Message Relay一样,这里的发送到MQ也是At Least Once语义,消息可能会被重复发送,所以MQ之后的消费者需要做去重或者幂等处理
总之,CDC技术同样可以保证,对Order表的修改,然后将对应事件发送到MQ,这两个动作可以实现事务性,也就是实现数据分发的事务性
注意
这里的CDC一般是一个独立部署的服务,生产中需要做好高可用部署,并且做好监控告警。否则如果CDC挂了,消息也就发不出来,然后,依赖于消息的各种消费方也将无法正常工作。
当前成熟技术方案(CDC企业级项目)
当前几个比较成熟的企业级的CDC开源项目:
- 第一个是阿里开源的Canal,目前在github上有超过1.4万颗星,这个项目在国内用得比较多,之前在拍拍贷的实时数据场景,Canal也有不少成功的应用。Canal主要支持MySQL binlog的增量订阅和消费。它是基于MySQL的Master/Slave机制,它的Miner角色是通过伪装成Slave来实现的。这个项目的使用文档相对比较完善,建议大家一步参考学习。
- 第二个是Redhat开源的Debezium,目前在github上有超过3.2k星,这个项目在国外用得较多。Debezium主要是在Kafka Connect的基础上开发的,它不仅支持mysql数据库,还支持postgres/sqlserver/mongodb等数据库。
- 第三个是Zendesk开源的Maxwell,目前在github上有超过2.1k星。Maxwell是一个轻量级的CDC Deamon,主要支持MySQL binlog的变更数据捕获和处理。
- 第四个是Airbnb开源的SpinalTap,目前在github上有两百多颗星。SpinalTap主要支持MySQL binlog的变更捕获和处理。这个项目的星虽然不多,但是它是在Airbnb SOA服务化过程中,通过实践落地出来的一个项目,值得参考。
对于上面的这些项目,如果你想生产使用的话,推荐阿里的Canal,因为这个项目毕竟是国内大厂阿里落地出来,而且在国内已经有不少企业落地案例
一致性方案对比
Transactional Outbox vs CDC
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DXKPC0Sn-1611286428299)(http://doc.szkunton.com/uploads/tec-share/images/m_b2b140226fb963db10c08c35f20986d4_r.png)]
事务性分发的两种落地模式,一种是事务性发件箱模式,另外一种是变更数据捕获模式,这两种模式其实各有优劣,为了帮助大家做选型决策,我这边对这两种模式进行一个比较,请看上面的比较表格:
- 首先比较一下复杂性,事务性发件箱相对比较简单,简单做法只需要在数据库中增加一个发件箱表,然后再启一个Poller线程拉消息和发消息就可以了。CDC技术相对比较复杂,需要你深入理解数据库的事务日志格式和协议。另外Miner的实现也不简单,要保证不丢消息,如果生产部署的话,还要考虑Miner的高可以部署,还有监控告警等环节。
- 第二个比较的是Polling延迟和开销。事务性发件箱的Polling是近实时的,同时如果频繁拉数据库表,难免会有性能开销。CDC是比较实时的,同时它不侵入数据库和表,所以它的性能开销相对小。
- 第三个比较的是应用侵入性。事务性发件箱是有一定的应用侵入性的,应用在更新业务数据的同时,还要单独发送消息。CDC对应用是无侵入的,因为它拉取的是数据库事务日志,这个和应用是不直接耦合的。当然,CDC和事务性发件箱模式并不排斥,你可以在应用层采用事务性发件箱模式,同时仍然采用CDC到数据库去捕获和发件箱中的消息对应的事务日志。这个方法对应用有一定的侵入性,但是通过CDC可以获得较好的数据同步性能。
- 第四点是适用场合。事务性发件箱主要适用于中小规模的企业,因为做法比较简单,一个开发人员也可以搞定。CDC则主要适用于中大规模互联网企业,最好有独立框架团队负责CDC的治理和维护。