多数据源之间不使用分布式事务实现异步最终一致性

来源:互联网 发布:jquery获取table数据 编辑:程序博客网 时间:2024/04/29 17:58


数据库dbA
表t1

数据库dbB
表t2

目标,t1插入记录时,同时保证t2也插入

假如使用分布事务,非常简单

开始分布事务
...
insert into t1 ...
insert into t2 ...
提交分布事务


不使用分布事务要保证一致是无法直接实现的,比如

开始事务
...
insert into t1 ...
提交事务
如果成功,insert into t2 ...

看起来,只要dbB始终可靠似乎没问题,
实际即便能保证dbB可靠且始终能插入,也还是存在问题.
比如,提交事务成功,此时程序崩溃或者断电了,
那么就没机会执行t2插入了.



为此我们需要两个库各增加一张消息表
dbA
msgToB(id int not null,s varchar)

dbB
msgFromA(id int not null,s varchar)

其中id是主键,s是消息内容,流程如下:


开始dbA事务
...
insert into t1 ...
给dbB发消息 insert into msgToB (id,s)values(...)
提交dbA事务


其中插入msgToB的id可以是自增量,也可以是额外获得的唯一ID.
这样保证只要t1插入记录,msgToB就有对应的一条消息

然后另一个程序
循环
 读dbA中msgToB到id,s
 开始dbB事务
   如果msgFromA中没有id记录
     将id插入msgFromA
     根据s中内容,insert into t2 ...
 
   如果msgFromA中已经存在,说明已经处理过,直接返回即可
 提交dbB事务
 如果事务成功,删除msgToB中对应记录

这样事务保证处理且只处理一次消息,事务外如果未删除
msgToB记录,等到下次处理就会被自动删除且不会被重复处理.

这样达到我们的目标,两个独立的库通过额外的两张表用异步消息的机制达成了最终一致性.

当然这里有一点是需要应用注意的,就是在dbB中的事务中
如果插入t2失败(比如已经存在记录),那么对应消息将永远无法处理,
在单数据事务中,t2出错时,数据库会回滚对t1的插入.
一般这也是我们期望的.然而此种机制则需要应用来解决这个问题,
比如如果处理反复失败,则标记这个消息已经处理,并给dba发送一条新消息,
让dbA来处理这种状态,比如撤销对t1的插入.

更通常的做法应该是由应用保证类似情况不会发生,
既然我们已经设计了两个独立的数据源仅仅依靠异步消息来连接,
那么就必须考虑所有的正常流程与回滚方法.
比如:
单机里
1.下单,插入订单表
2.扣仓库库存
3.库存不够,回滚

在一个事务里,用户下单后,系统可以立刻告诉他没库存了,
这时根本不会生成新订单(事务中被回滚了).

如果分布处理
1.订单库,用户下单
此时订单已经生成了

2.订单转到仓库1
3.仓库1意外发现无货了(比如刚发现货被摔烂了...)
4.仓库1发消息给订单库
5.订单库将订单状态修改成无货取消
然后可以再给另外的支付库发消息,完成退款

这里,订单库,与仓库1不光逻辑上是不同数据库而且物理上
可能相隔甚远,两者网络可能高延时,低带宽,不稳定(比如星淘,在另外一个星球上...)
这时两阶段提交的分布事务几乎无可能,
这种分布式异步处理最终强一致性系统可能是唯一的选择.



扩展一下:
1.任何一个支持本地事务的数据源都可以参与这种事务.
比如一个数据库可以和redis或者两个redis之间.
因为虽然redis不支持分布式事务,但是内部却有事务机制,
只要在双方增加一个类似的消息表.

2.分布式水平扩展
比如dba实际可以对应dbb0,dbb1,dbb2...
可以很简单的根据t1的主键将数据分布到不同数据库的t2表而实现水平扩展.


3.通过级联,可以将一个事务扩展到更多不同节点.
比如,数据源1发消息给数据源2,数据源2处理后发给数据源3...

开始数据源2事务
...
完成数据源1的消息处理,insert into msgFrom1
给数据源3发消息,insert into msgTo3

提交数据源2事务

结合充分理解和使用单机事务,你会发现这种机制非常简单好用。

4.与直接使用消息队列系统的比较
消息队列已经在现代大型IT系统广泛采用了.
一种是弱事务性的处理,允许丢或多消息,这种方式与我们上面要求的强一致性应用场景完全不同.
另一种也要求消息绝对不能丢,这种场景与上面讨论的一致.
但是要注意到,必须使用两阶段提交才能保证不丢消息!
即便消息队列本身可以保证.

比如发送消息,如果一个保证可靠消息的消息系统告诉我们成功了,
我们可以肯定这个消息不会丢失,接受者肯定可以接受到,
但是接受到不代表能处理!比如接收方

开始消息事务
读消息
 开始数据库事务
   处理消息
 提交数据库事务
删除消息
提交消息事务

问题很显然,提交数据库事务后,消息实际被处理了,
但是此时系统崩溃的话,消息将不会被删除,还会被再处理一遍.
反之如果先提交消息事务,随后崩溃的话,就会导致消息丢失.
解决方式就是要么将消息事务与数据库事务合并为分布式事务,
要么同样采用上面的方式,在两个数据源增加表.
而采用后种方式时,消息队列的优势就丧失了(因为使用消息系统
一般就是看中她快捷,如果在插入消息队列时还要额外再插入数据库
消息系统还有什么存在的必要呢?).

从这里可以看出,消息队列系统实际最适合异步弱一致性处理,
类似处理日志这种,偶尔多个少个几个完全无所谓.
如果看重他的其他方面但还要求消息绝对可靠,
那么在和其他系统连接时,必须使用分布式事务.
不然长长的管子不漏,接头漏了,还是白忙活.




1 0
原创粉丝点击