专栏首页天马行空布鲁斯空谈发件箱模式(outbox pattern)

空谈发件箱模式(outbox pattern)

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

1

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

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

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后面,如下:

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,最后删除发送成功的记录。如下:

主逻辑:

begin transactionsave(order) to dbsave(order_created_event) to db outboxcommit/rollback

scheduler job:

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

本文分享自微信公众号 - 天马行空布鲁斯(gh_2feda5c053bd),作者:huazailmh

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-10-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 空谈分布式系统设计之幂等性

    在之前的文章,有多次提到转账系统这个案例,由于这个案例太典型了,很多大学教授数据库事务的时候就是用的这个案例。

    Bruce Li
  • 如何不宕机实现数据库迁移

    由于业务的扩展或者其他原因,常常会有迁移系统数据库的场景,对于有大量用户7*24小时不间断使用的系统,如何不宕机实现数据库迁移,这是个很有挑战的话题。

    Bruce Li
  • 关于分布式系统数据一致性的那些事(二)

    接上一篇文章(关于分布式系统数据一致性的那些事),继续更新一些关于分布式系统数据一致性方面的知识。

    Bruce Li
  • 如何找到一个前端事件对应的事件处理函数

    版权声明:本文为博主汪子熙原创文章,未经博主允许不得转载。 https://jerry.bl...

    Jerry Wang
  • Angular JS + Express JS入门搭建网站

      3月份开始,接到了新的任务,跟UI开发有关,用的是Angular JS,Express JS等技术。于是周末顺便学习下新技术。   组里产品UI架构如下: ...

    宋凯伦
  • Greenplum数据库使用总结(干货满满)--PGBENCH使用

    $ psql -h 192.168.31.200 -d postgres -U postgres -p 5432

    小徐
  • Greenplum Pgbench命令详解

    TPC:Transactionprocessing Performance Council事务处理性能委员会

    小徐
  • Greenplum Pgbench命令详解

    TPC:Transactionprocessing Performance Council事务处理性能委员会

    小徐
  • 网站的PV、访问次数、浏览量,这三个分别代表什么,有什么不同?

    访问次数(VV):记录所有访客1天内访问了多少次您的网站,相同的访客有可能多次访问您的网站。

    数据通
  • 使用rdesktop来在Windows和Linux之间共享数据

    rdesktop是一个开源的远程桌面客户端,用来从Linux机器连接到Windows机器。它遵循RDP协议(Remote Desktop Protocol),并...

    王云峰

扫码关注云+社区

领取腾讯云代金券