分布式事务的解决方案

前言

先声明,本文不会介绍诸如ACID、2PC、CAP等概念性的问题(要介绍也是Ctrl CV (:🐶 ),想了解的同学可自行Google~ 本文只记录笔者工作中遇到的事务问题,以及解决方案。

在工作中,事务问题是比较常见的,同时也是比较危险的,稍一不注意就会背P0事故。那我们在工作中要如何解决事务问题,保证业务安全运行呢?

试想一个业务场景:用户在电商网站下单某个商品,这时需要进行两个操作:

  1. 更改订单状态
  2. 将商品从购物车中移除

如果操作1成功了,操作2却失败。这时用户看到商品仍在购物车中,以为没有下单成功,又再次点击下单,造成重复订单。

如果操作1失败了,操作2却成功。这时显示下单失败,但是购物车中的商品被移除了,用户对此也会产生疑惑。

综上,操作1、2 只能同时成功,或者同时失败。 否则就会出现各种想象不到的异常情况。

那要怎么保证呢?这时候就需要事务

1. 数据库事务

这应该算是最简单的事务问题了,因为常用的数据库本身也支持事务操作。

针对以上场景,可以编写伪代码:

// 开启一个事务
tx := db.Begin()
// 1.更改订单状态
tx.updateOrderStatus(xxxx) 
// 2.将商品从购物车中移除
tx.removeItemFromShoppingCart(xxxx)
// 出现错误,回滚操作 1、2
if err != nil {
    tx.rollback()
    return
 }
 //操作1、2都运行成功,提交事务
 tx.commit()

通过伪代码不难发现,数据库事务只适用于操作同一个DB,但现实的项目中,往往是以微服务划分各自的职责,订单服务和购物车服务甚至都归属于不同的团队,更别说用同一个数据库了。

这时候就需要使用分布式事务

2. 事务消息

还是上文的场景,虽然订单服务和购物车服务归属于不同的服务,但是服务之间的协作可以通过消息队列实现。

简单来说,就是当更新订单状态成功之后,发送一条消息给购物车服务,然后购物车服务执行移除操作

image

这样,乍一看好像没什么问题,但深入思考之后会有几个疑问:

  1. 订单状态更新成功了,但消息发送失败,要如何处理?
  2. 消息发送成功了,要怎么保证购物车服务一定能消费到?

概括成一句话:如何保证消息的生产端和消费端的事务性

2.1 事务消息 - 生产端

image

还是以用户下单场景解析事务消息是如何发送的:

  1. producer发送订单状态已更新的消息
  2. 消息发送成功
  3. 执行本地事务,这时真正更新订单的状态
  4. 本地事务执行成功,则提交该事务消息,失败则回滚消息。

但是考虑到网络的原因,在发送commit或rollback的消息丢失了,broker接收不到信息,无法进行下一步操作。

对于这个问题很好解决,如步骤5:在Producer端提供一个回查的接口,供Broker定期回查本地事务的状态。然后可以根据反查结果决定回滚还是提交事务。

2.2 事务消息 - 消费端

话说大部分文章在介绍事务消息时都只侧重于生产端,对消费端一笔带过甚至提都不提
image

但其实在实战中,如何实现高效、正确的消费端也是一大难题。

想问大家一个问题,在消费端,是先消费消息再提交commit?还是先提交commit 再消费消息呢?

其实这两种方式没有对错之分,只是在不同业务下的选择。

我们就以这两种方式来介绍如何实现消费端的“事务性”

1. 先消费消息再提交commit

err := handle(msg)
if err!=nil{
    return err
}
consumer.commit(msg)

这种消费方式本身也具备事务性了,因为只有消息消费成功,才提交偏移量,如果消费失败,Broker则会重新投递。(多次投递失败,则会发送死信队列)

image

但这种方式也有两个缺点:

  1. Broker可能会多次投递,造成重复消费,所以消费者要实现好幂等逻辑
  2. 先消费再提交commit意味着不能异步多线程消费,消费速度较慢

2. 先提交commit再消费

consumer.commit(msg)
go handle(msg)

这种实现方式可以运用多线程异步消费,较于方式1能极大提升消费速度。但是同时也带来了隐患。

因为Broker只投递一次消息,所以处理失败case只能由业务自己去重试。

image

通用的方案是设计重试队列,当业务逻辑处理失败时,交由重试队列去处理,当重试超过一定次数,则需要告警人为干预。

注:这种实现方式,不能保证最终一致性,在极端情况下仍会出现不一致的情况。

对于事务消息的消费端,两种实现方式都各有利弊,要深入业务调研,从而做出最好的选择。

对于分布式事务的解决方案,上文介绍了事务消息,对于这种方案,能保证最终的结果是可靠的,过程也非常简单易理解。但是整个过程完全没有任何隔离性可言。

对于订单和购物车的场景,对隔离性要求不高,所以使用事务消息来解决该种场景是非常合适的。

但是对于另一个场景:用户下单某个商品,对应两个操作:

  1. 更改订单状态
  2. 扣减商品库存

如果使用缺乏隔离性的事务消息来处理该场景,会带来一个显而易见的问题“超卖”。

因为两个客户完全有可能在短时间内都成功购买了同一件商品,而且他们各自购买的数量都不超过目前的库存,但他们购买的数量之和却超过了库存。

所以就需要使用隔离性更强的分布式事务方案 -- TCC 事务来处理。

3. TCC

在具体实现上,TCC 较为烦琐,它是一种业务侵入式较强的事务方案。要求业务处理过程必须拆分为“预留业务资源”和“确认/释放消费资源”两个子过程。如同 TCC 的名字所示,它分为(Try、Confirm、Cancel)三个阶段。

图片
  • Try:尝试执行阶段,完成所有业务可执行性的检查(保障一致性),并且预留好全部需用到的业务资源(保障隔离性)。
  • Confirm:确认执行阶段,不进行任何业务检查,直接使用 Try 阶段准备的资源来完成业务处理。Confirm 阶段可能会重复执行,因此本阶段所执行的操作需要具备幂等性。
  • Cancel:取消执行阶段,释放 Try 阶段预留的业务资源。Cancel 阶段可能会重复执行,也需要满足幂等性。

业务时序图:

图片
  1. 订单服务发起事务请求,库存服务&积分服务预留业务资源(冻结库存、预添加积分)

  2. Try阶段全部成功,完成业务操作(扣减库存,为会员添加积分)

  3. Try阶段有操作失败或超时,取消业务操作(释放库存、取消添加积分)

总结

分布式事务有多种解决方案,同一种方案,根据业务的不同也有不同的实现方式。所以要深入业务,选择一个最合适的方案。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容