宜昌网站设计制作公司,唐山做网站那家好,攻城霸业手游下载,咸阳网站开发由于数据量的巨大#xff0c;大部分Web应用都需要部署很多个数据库实例。这样#xff0c;有些用户操作就可能需要去修改多个数据库实例中的数据。传统的解决方法是使用分布式事务保证数据的全局一致性#xff0c;经典的方法是使用两阶段提交协议。长期以来#xff0c;分布式…由于数据量的巨大大部分Web应用都需要部署很多个数据库实例。这样有些用户操作就可能需要去修改多个数据库实例中的数据。传统的解决方法是使用分布式事务保证数据的全局一致性经典的方法是使用两阶段提交协议。长期以来分布式事务提供的优雅的全局ACID保证麻醉了应用开发者的心灵很多人都不敢越雷池一步想像没有分布式事务的世界会是怎样。如今就如MySQL和PostgreSQL这类面向低端用户的开源数据库都支持分布式事务了开发者更是沉醉其中不去考虑分布式事务是否给系统带来了伤害。事实上有所得必有所失分布式事务提供的ACID保证是以损害系统的可用性、性能与可伸缩性为代价的。只有在参与分布式事务的各个数据库实例都能够正常工作的前提下分布式事务才能够顺利完成只要有一个工作不正常整个事务就不能完成。这样系统的可用性就相当于参加分布式事务的各实例的可用性之积实例越多可用性下降越明显。从性能和可伸缩性角度看首先是事务的总持续时间通常是各实例操作时间之和因为一个事务中的各个操作通常是顺序执行的这样事务的响应时间就会增加很多其次是一般Web应用的事务都不大单机操作时间也就几毫秒甚至不到1毫秒一但涉及到分布式事务提交时节点间的网络通信往返过程也为毫秒级别对事务响应时间的影响也不可忽视。由于事务持续时间延长事务对相关资源的锁定时间也相应增加从而可能严重增加了并发冲突影响到系统吞吐率和可伸缩性。正是由于分布式事务有以上问题eBay在设计上就不采用分布式事务而是通过其它途径来解决数据一致性问题。其中使用的最重要的技术就是消息队列和消息应用状态表。举个例子。假设系统中有以下两个表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表存储在不同的节点上那么上述事务就是一个分布式事务。要消除这一分布式事务将它拆分成两个子事务一个更新transaction表一个更新user表是不行的因为有可能transaction表更新成功后更新user失败系统将不能恢复到一致状态。解决方案是使用消息队列。如下所示先启动一个事务更新transaction表后并不直接去更新user表而是将要对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 queuebegin;dequeue message;if message.type “seller” thenUPDATE user SET amt_sold amt_sold message.amount WHERE id message.user_id;elseUPDATE user SET amt_bought amt_bought message.amount WHERE id message.user_id;endcommit;end上述解决方案看似完美实际上还没有解决分布式问题。为了使第一个事务不涉及分布式操作消息队列必须与transaction表使用同一套存储资源但为了使第二个事务是本地的消息队列存储又必须与user表在一起。这两者是不可能同时满足的。如果消息具有操作幂等性也就是一个消息被应用多次与应用一次产生的效果是一样的话上述问题是很好解决的只要将消息队列放到transaction表一起然后在第二个事务中先应用消息再从消息队列中删除。由于消息队列存储与user表不在一起应用消息后可能还没来得及将应用过的消息从队列中删除时系统就出故障了。这时系统恢复后会重新应用一次这一消息由于幂等性应用多次也能产生正确的结果。但实际情况下消息很难具有幂等性比如上述的UPDATE操作执行一次和执行多次的结束显然是不一样的。解决这一问题的方法是使用另一个表记录已经被成功应用的消息并且这个表使用与user表相同的存储。假设增加以下表 message_applied(msg_id)记录被成功应用的消息则产生最终的解决方案如下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 queuebegin;SELECT count(*) as cnt FROM message_applied WHERE msg_id message.id;if cnt 0 thenif message.type “seller” thenUPDATE user SET amt_sold amt_sold message.amount WHERE id message.user_id;elseUPDATE user SET amt_bought amt_bought message.amount WHERE id message.user_id;endINSERT INTO message_applied VALUES(message.id);endcommit;if 上述事务成功dequeue messageDELETE FROM message_applied WHERE msg_id message.id;endend我们来仔细分析一下1、消息队列与transaction使用同一实例因此第一个事务不涉及分布式操作2、message_applied与user表在同一个实例中也能保证一致性3、第二个事务结束后dequeue message之前系统可能出故障出故障后系统会重新从消息队列中取出这一消息但通过message_applied表可以检查出来这一消息已经被应用过跳过这一消息实现正确的行为4、最后将已经成功应用且已经从消息队列中删除的消息从message_applied表中删除可以将message_applied表保证在很小的状态不清除也是可以的不影响系统正确性。由于消息队列与message_applied在不同实例上dequeue message之后将对应message_applied记录删除之前可能出故障。一但这时出现故障message_applied表中会留下一些垃圾内容但不影响系统正确性另外这些垃圾内容也是可以正确清理的。虽然由于没有分布式事务的强一致性保证使用上述方案在系统发生故障时系统将短时间内处于不一致状态。但基于消息队列和消息应用状态表最终可以将系统恢复到一致。使用消息队列方案解除了两个数据库实例之间的紧密耦合其性能和可伸缩性是分布式事务不可比拟的。当然使用分布式事务有助于简化应用开发使用消息队列明显需要更多的工作量两者各有优缺点。个人观点是对于时间紧迫或者对性能要求不高的系统应采用分布式事务加快开发效率对于时间需求不是很紧对性能要求很高的系统应考虑使用消息队列方案。对于原使用分布式事务且系统已趋于稳定性能要求高的系统则可以使用消息队列方案进行重构来优化性能。注: 本文取材于eBay的工程师Dan Pritchet写的这篇文章 并转载至http://wangyuanzju.blog.163.com/blog/static/1302920086424341932。转载于:https://www.cnblogs.com/hb_cattle/articles/2473455.html