前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >空谈发件箱模式(outbox pattern)

空谈发件箱模式(outbox pattern)

作者头像
Bruce Li
发布2020-10-14 17:47:50
2.9K0
发布2020-10-14 17:47:50
举报
文章被收录于专栏:天马行空布鲁斯

之前聊过很多次分布式事务这个话题,本文继续聊一聊如何用发件箱模式实现分布式事务。

1

基于微服务架构模式(当然不限于)的应用系统,常常会利用消息中间件(kafka,rabbitmq等)来实现各个微服务之间的通信。对于用户的某个操作,一个微服务可能需要执行“存数据库”和“发送event”两个步骤。

举个例子,用户创建一个订单,订单服务需要存数据库和发送订单created event,如下:

代码语言:javascript
复制
begin transactionsave(order) to dbsend(order_created_event) to kafkacommit/rollback

对于上面这个例子,由于牵扯到两个系统,数据库事务和kafka事务并不能保证整个操作的事务性(ACID),至多能保证各个子系统的事务性,最终可能导致出现如下数据不一致的情况:

  1. 发送event成功,然后commit到数据库失败(原因可能是数据库不available,或者是违反数据库表的constraints)。
  2. 发送event成功到commit到数据库之间有一定延迟,如果consumer消费到event,接着call订单服务查找这个order,结果就可能会查不到。

有同学会说,可以把发送event放在存数据库transaction后面,如下:

代码语言:javascript
复制
begin transactionsave(order) to dbcommit/rollbacksend(order_created_event) to kafka

即使这样,又会出现另外一种不一致的情况,即:存数据库成功,发event失败,比如由于kafka临时不available。这直接导致用户的操作失败,必须要重新提交请求,在某些场景下对用户来说可能是不可接受的;并且数据库里面还有一条脏数据存在。

那么,如何保证存数据库和发event是一个transaction呢?

其实,抽象这个问题,其本质就是一个分布式事务问题,如何实现分布式事务,网上有很多文章介绍,可能最常被提到的就是两阶段提交2PC(2 phase commit),但其需要各个子系统都支持两阶段提交协议(比如XA),很多数据库都有支持,但是很多消息中间件都不支持,所以2PC不适用这里的场景,并且由于2PC的一些缺点(参见我的另外一篇文章:关于分布式系统数据一致性的那些事(二)),它并不是实现分布式事务的常规选择。

基于此,这里介绍的发件箱模式可以有效地解决这个问题。

2

发件箱模式,简单讲就是在数据库里面额外增加一个outbox表用于存储需要发送的event,把直接发送event的步骤换成先把event存储到数据库outbox表;另外,程序启动一个scheduler job不断去抓取outbox表里面的记录,发送给Kafka,最后删除发送成功的记录。如下:

主逻辑:

代码语言:javascript
复制
begin transactionsave(order) to dbsave(order_created_event) to db outboxcommit/rollback

scheduler job:

代码语言:javascript
复制
begin transactionget(order_created_event) from db outboxsend(order_created_event) to kafkadelete(order_created_event) to db outboxcommit/rollback

基于这样的实现,存order和event就可以通过数据库的transaction来保障,这样即使是Kafka不available,也不影响用户的行为,只是说后续处理可能会有一些延迟而已;此外,如果scheduler job删除event失败,最坏的行为也就是重新再发一次这个event,即是重发消息的问题,这就需要消费端实现幂等性处理。

3

发件箱模式主要有两种实现方式:

  • Transaction log tailing
  • Polling publisher

Transaction log tailing,又称CDC(change data capture),就是依赖于数据库的transaction log,一般数据库在成功执行完一条语句之后就会记录一条log,那么这个方式就是不断地增量抓取数据库log,然后发送到消息系统。目前,已经有一些支持这种方式的open source可以直接使用,比如:Debezium。另外,采用这种方式,可能需要数据库装相关的插件以支持CDC。

Polling publisher,其实就是自己实现一个轮询的sheduler job,不断抓取outbox表里面的event,然后发送到消息系统,最后删除event。在实现层面,可能需要注意下面几点:

  1. 一般来说,为了保证event的顺序,在抓取event的时候,需要把event按时间排序。
  2. 如果是分布式的环境,为了保证scheduler job同时只在一个instance上run,需要实现分布式锁;或者借助于成熟的scheduling的library,比如:quartz。

相关阅读

References

  • https://microservices.io/patterns/data/transactional-outbox.html
  • https://mp.weixin.qq.com/s/PMIWr8j3cN-K0FbS6qGW0A
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 天马行空布鲁斯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档