经过几天的资料查找,对解决分布式事务的方法有两阶段提交、支付宝分享的TCC(try-confirm-cancel)和基于消息的最终一致解决方案,其中第一条和第二条虽然也能解决问题,但广泛对第三种基于消息队列的最终一致解决方案推荐多比较高,因此第一条和第二条能够参考使用。java
业务拆分,架构设计时应“尽可能避免”分布式事务,若是实在避免不了(这已是高并发、用户量比较多的网站了)则使用“最终一致性”处理。git
C(一致性)一致性是指数据的原子性,在经典的数据库中经过事务来保障,事务完成时,不管成功或回滚,数据都会处于一致的状态,在分布式环境下,一致性是指多个节点数据是否一致;github
A(可用性)服务一直保持可用的状态,当用户发出一个请求,服务能在必定的时间内返回结果;数据库
P(分区容忍性)在分布式应用中,可能由于一些分布式的缘由致使系统没法运转,好的分区容忍性,使应用虽然是一个分布式系统,可是好像一个能够正常运转的总体架构
1. BA: Basic Availability 基本业务可用性;并发
2. S: Soft state 柔性状态,中间状态;app
3. E: Eventual consistency 最终一致性;框架
两阶段提交分为准备阶段和提交阶段两个阶段,其原理架构图以下:分布式
图3-1高并发
概述:在提交事务的过程当中须要在多个节点之间进行协调,而各节点对锁资源的释放必须等到事务最终提交时,比较耗时,锁资源发生冲突的几率增长,当事务的并发量达到必定数量的时候,就会出现大量事务积压甚至出现死锁,系统性能就会严重下滑。
TCC分三部分,以下所述:
1. Try: 尝试执行业务;
2. Confirm: 确认执行业务;
3. Cancel: 取消执行业务;
此方案开始由支付宝提出来的一种方案,在开源社区github上找到一个TCC型事务java实现(https://github.com/changmingxie/tcc-transaction),部署到本机环境测试了一下demo,能够实现分布式事务补偿机制,因为时间仓促,尚未来得及研究源码。
优势:现成的框架时间,使用不难,有开发使用文档,能够为咱们之后设计分布式事务时提供一种设计思路和参考。
缺点:不是很成熟,关注度不是很高,可能会存在一些问题。
借助消息队列,在处理业务逻辑的地方,发送消息,业务逻辑处理成功后,提交消息,确保消息是发送成功的,以后消息队列投递来进行处理,若是成功,则结束,若是没有成功,则重试,直到成功,不过仅仅适用业务逻辑中,第一阶段成功,第二阶段必须成功的场景。架构图以下C流程:
图3-3
前面部分和上面基于事务型消息的队列,不一样的是,第二阶段重试的地方,再也不是消息中间件自身的重试逻辑了,而是单独的补偿任务机制。其实在大多数的逻辑中,第二阶段失败的几率比较小,因此单独独立补偿任务表出来,能够更加清晰,可以比较明确的直到当前多少任务是失败的。对应上图的E流程。
至于如何实现幂等性操做,能够经过增长一个message_applied(msg_id)记录被成功应用的消息,在事务完成以后删除记录便可。
举个例子。假设系统中有如下两个表
user(id, name, amt_sold, amt_bought)
transaction(xid, seller_id, buyer_id, amount)
其中user表记录用户交易汇总信息,transaction表记录每一个交易的详细信息。
这样,在进行一笔交易时,若使用事务,就须要对数据库进行如下操做:
begin;
INSERT INTO transaction VALUES(xid, $seller_id, $buyer_id, $amount);
UPDATE user SET amt_sold = amt_sold + $amount WHERE id = $seller_id;
UPDATE user SET amt_bought = amt_bought + $amount WHERE id = $buyer_id;
commit;
即在transaction表中记录交易信息,而后更新卖家和买家的状态。假设transaction表和user表存储在不一样的节点上,那么上述事务就是一个分布式事务。
则使用基于消息队列的最终一致解决方案的伪代码以下所示:
第一步:
begin;
INSERT INTO transaction VALUES(xid, $seller_id, $buyer_id, $amount);
put_to_queue "update user("seller", $seller_id, amount);
put_to_queue "update user("buyer", $buyer_id, amount);
commit;
第二步:
for each message in queue
begin;
SELECT count(*) as cnt FROM message_applied WHERE msg_id = message.id;
if cnt = 0 then
if message.type = "seller" then
UPDATE user SET amt_sold = amt_sold + message.amount WHERE id = message.user_id;
else
UPDATE user SET amt_bought = amt_bought + message.amount WHERE id = message.user_id;
end
INSERT INTO message_applied VALUES(message.id);
end
commit;
第三步:
if 上述事务成功
dequeue message
DELETE FROM message_applied WHERE msg_id = message.id;
end
end
根据不一样的业务场景对一致性、可用性和分区容忍性的要求不一样,在此三者间寻找一个最佳的平衡点来知足不一样的业务场景,能够根据以上方案灵活选择,不局限于某一种方案。