分布式锁和分布式事务
分布式锁和分布式事务是分布式系统中两个至关重要但又常常被混淆的概念。它们都是为了解决分布式环境下的并发与数据一致性问题,但侧重点和应用场景完全不同。
下面我将从定义、目的、实现方式、常见方案和它们之间的关系等方面进行详细解释和对比。
一、核心概念对比
特性 | 分布式锁 (Distributed Lock) | 分布式事务 (Distributed Transaction) |
---|---|---|
核心目标 | 互斥访问 (Mutual Exclusion):保证在分布式环境下,一个共享资源在同一时间只能被一个客户端(或一个线程)访问。 | 数据一致性 (Data Consistency):保证分布在多个节点上的数据操作,要么全部成功,要么全部失败,遵循ACID原则。 |
关注点 | 控制访问时序,解决“能不能用”的问题。 | 保证操作原子性,解决“改得对不对”的问题。 |
作用范围 | 通常针对一个共享资源(如一条数据、一个文件、一个接口)。 | 跨越多个分布式资源(如多个数据库、多个服务、多个消息队列)。 |
生命周期 | 较短。从获取锁开始,到操作完成释放锁结束。 | 较长。从事务开始(Begin)到事务提交(Commit)或回滚(Rollback)结束。 |
技术本质 | 一个在分布式系统中全局唯一的标志,谁拿到这个标志谁就有权操作。 | 一个协调机制,用于管理多个参与者的提交或回滚行为。 |
二、分布式锁详解
1. 要解决的问题
在高并发场景下,防止多个客户端同时操作同一资源导致的数据错误。例如:
- 秒杀扣库存:防止超卖。
- 防止重复处理:如定时任务在多个节点上只执行一次。
- 全局唯一序列生成。
2. 实现原理
本质是在一个所有客户端都能访问的外部存储系统中,创建一个临时键来代表锁。
- 获取锁:客户端尝试在这个系统中创建一个唯一的键(如
lock:order_123
)。如果创建成功,则表示获取了锁;如果已存在(被其他客户端创建),则获取失败,需要等待或重试。 - 释放锁:操作完成后,客户端删除这个键,释放锁供其他客户端争夺。
3. 关键要求
- 互斥性:最基本的要求,同一时刻只有一个客户端能持有锁。
- 避免死锁:锁必须有超时机制(租约),即使持有锁的客户端崩溃,锁也能自动释放。
- 容错性:提供锁的服务本身需要高可用,不能是单点。
4. 常见实现方案
- 基于 Redis:使用
SET key value NX PX milliseconds
命令实现。成熟框架有 Redisson。 - 基于 ZooKeeper:利用ZooKeeper的临时有序节点和Watch机制实现,可靠性更高,性能稍差。
- 基于 Etcd:类似ZooKeeper,使用Raft协议保证一致性,通过租约(Lease)机制实现锁。
三、分布式事务详解
1. 要解决的问题
在微服务架构中,一个业务操作可能需要调用多个服务,每个服务都有自己的数据库。要保证这些服务的数据操作作为一个整体保持一致。例如:
- 银行转账:A账户扣款和B账户加款必须同时成功或失败。
- 创建订单:扣减库存、生成订单、计算积分等操作必须是一个原子单元。
2. 实现原理与模型
分布式事务的实现远比分布式锁复杂,主流模型有:
-
两阶段提交 (2PC - Two-Phase Commit)
- 阶段一(准备阶段):协调者询问所有参与者:“是否可以提交?” 参与者执行事务操作(但不提交),写入undo/redo日志,然后回复“Yes”或“No”。
- 阶段二(提交/回滚阶段):
- 如果所有参与者都回复“Yes”,协调者发送“提交”指令,参与者正式提交事务。
- 如果有任何一个参与者回复“No”,协调者发送“回滚”指令,参与者根据日志回滚事务。
- 缺点:同步阻塞(所有参与者在等待指令时处于阻塞状态)、性能低、协调者单点问题。
-
三阶段提交 (3PC):在2PC基础上引入了超时机制和预提交阶段,缓解了阻塞问题,但更复杂。
-
TCC (Try-Confirm-Cancel)
- 一种补偿型事务模型,需要业务代码配合。
- Try:尝试执行。完成所有业务检查,并预留必要的业务资源(如冻结部分库存、预扣款)。
- Confirm:确认执行。真正执行业务操作,使用Try阶段预留的资源。Confirm操作需要保证幂等性。
- Cancel:取消执行。释放Try阶段预留的资源。Cancel操作需要保证幂等性。
- 优点:性能比2PC好,数据最终一致性。
- 缺点:业务侵入性强,需要为每个操作实现Try/Confirm/Cancel三个接口。
-
基于消息的最终一致性(本地消息表、MQ事务消息)
- 这是互联网公司最常用的模式,通过消息队列异步解耦。
- 核心思想:将分布式事务拆分成一系列本地事务,通过消息队列的可靠性来保证最终一致性。
- 流程:
- 系统A执行本地事务,同时向消息表插入一条消息(状态为“待发送”)。
- 一个后台任务扫描消息表,将消息发送给MQ。
- MQ收到后,持久化该消息(确保不丢失),然后通知系统B。
- 系统B消费消息,执行本地事务,执行成功后向MQ返回ACK。
- 如果系统B执行失败,MQ会重投消息,直到成功(要求系统B的操作是幂等的)。
- 优点:高性能、高吞吐,实现了业务的解耦。
- 缺点:数据是最终一致性,存在短暂延迟,不适合强一致性场景。
四、两者的关系与协同工作
分布式锁和分布式事务不是对立的,而是常常协同工作,解决不同层次的问题。
一个典型的结合场景:秒杀系统
- 使用分布式锁:在用户抢购时,针对同一个商品ID加分布式锁。这样可以防止多个请求同时查询和扣减同一个商品的库存,避免了超卖问题。这里解决的是对单个资源(库存行)的高并发互斥访问问题。
- 使用分布式事务:创建订单的业务流程可能涉及多个服务:订单服务(写订单表)、库存服务(扣减库存)、账户服务(扣减余额)、积分服务(增加积分)。为了保证这些操作要么全做,要么全不做,就需要分布式事务(如TCC或消息最终一致性方案)。这里解决的是跨多个服务/数据库的数据一致性问题。
总结关系:
- 分布式锁通常用于分布式事务内部,作为保护单个关键资源(如数据库行)的组件。
- 你可以只用分布式锁(例如只有一个数据库的简单扣减场景)。
- 你也可以在不用分布式锁的情况下使用分布式事务(例如事务涉及的资源本身没有高并发争抢)。
- 在复杂的、高并发的场景下,两者会结合使用。