事务是指以全有或全无的方式执行的一系列操作。所有操作必须全部成功完成,否则在每个操作中所作的所有更改都会被撤消。
以数据库为列,事物包含四个特性(ACID)
- 原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。
- 一致性(Consistency):事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
- 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
- 持久性(Durability):当事务正确完成后,它对于数据的改变是永久性的。
而在分布式系统中,你不能指望ACID事务。因为在分布式系统中会对数据库进行分区,这里所说的分区指的是物理分区,分区之后可能不同的库就处于不同的服务器上了,这个时候单个数据库的ACID已经不能适应这种情况了,而在这种ACID的集群环境下,再想保证集群的ACID几乎是很难达到,或者即使能达到那么效率和性能会大幅下降。
在没有ACID的情况下解决数据一致性的常见技巧是使用补偿。这种方法与ACID事务不同,你可以具有不一致的中间状态。在业务处理中通常可以忍受这些暂时的不一致,只要能确保最终清理它们并使系统恢复到一致状态。这称为最终一致性,这是分布式系统中的一个重要概念。
最终的一致性通常会产生更好的性能,更简单的操作和更好的可伸缩性,同时帮助程序员理解更复杂的数据模型。
在实现最终一致性时会出现消息重复发送问题。实际应用情况是,在分布式系统中确切地保证消息传递是不可能的。实现一次准确的消息传递完全基于消息生成者从消息使用者那里接收到确认消息。但是,ACK本身是不可靠的,因为它也将通过网络传播。在处理消息后,由于网络问题或消费者崩溃,很可能会丢失ACK。
任何通过网络进行通信,都可能会出现三种故障情形:
- 该请求尚未到达服务提供商
- 请求已到达服务提供商,但在处理期间出现异常
- 服务提供程序处理了请求,但响应丢失了
那么如何才能实现准确的消息传递?它的答案是幂等性!你必须使你的消费者操作具有幂等性。幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时不应该进行第二次扣款。
添加幂等性处理方法:
- 唯一标识。你可以生成唯一标识符并将其添加到业务中。这样,如果在服务提供端存储该ID,则可以轻松发现重复调用。
- 利用工作流引擎,比如Apache ActiveMQ可以在生产者向代理发送消息时过滤掉重复项,代理索引可以跟踪和识别重复项并将其丢弃。
- 请求哈希。如果你使用消息传递,则可以通过存储消息的哈希值来执行相同的操作。