第一章:分布式事务基础理论
1.1 ACID 与 CAP 定理回顾
ACID 特性
- 原子性(Atomicity):事务要么全部成功提交,要么全部失败回滚,中间不可见半成品。
- 一致性(Consistency):事务执行前后,系统都必须处于合法状态(满足所有约束)。
- 隔离性(Isolation):并发执行的事务之间不会相互干扰,隔离级别定义了“可见性”边界。
- 持久性(Durability):事务一旦提交,其结果对系统是永久性的,即使发生故障也不丢失。
CAP 定理
在分布式系统中,不可能同时满足以下三点,只能选择两项:
- Consistency(一致性):所有节点在同一时间看到相同的数据视图。
- Availability(可用性):每次请求都能获得非错误响应。
- Partition tolerance(分区容忍性):系统能容忍网络分区,保证继续提供服务。
对比:传统单体数据库追求 ACID;分布式系统根据业务侧重点,在 CAP 中做平衡。
1.2 分布式事务常见解决方案对比
模型 | 原理 | 优缺点 |
---|---|---|
2PC(二阶段提交) | 协调者先询问所有参与者能否提交(Prepare),然后决定提交或回滚(Commit/Rollback) | ✅ 简单 ❌ 易阻塞、单点协调者故障影响全局、性能开销大 |
3PC(三阶段提交) | 在 2PC 基础上增加“预提交”阶段(Prepare→PreCommit→Commit) | ✅ 减少阻塞风险 ❌ 实现复杂,仍无法解决网络分区下的安全性 |
Saga(补偿事务) | 将大事务拆为若干本地事务,失败时依次执行补偿操作逆转 | ✅ 无全局锁、无阻塞 ❌ 补偿逻辑复杂、状态管理难、牵涉多方业务解耦 |
XSAGA | 基于 Saga 的扩展,结合消息队列与状态机管理分布式事务 | ✅ 异步解耦、高可用 ❌ 开发成本高,需要异步可靠消息与状态机组件 |
flowchart LR
subgraph 2PC
A[协调者: Prepare?] --> B[参与者1: OK/NO]
A --> C[参与者2: OK/NO]
B & C --> D[协调者: Commit/Rollback]
end
1.3 Redis 在分布式事务体系中的定位
原子性命令
- Redis 单条命令天然原子(如
INCR
,HSET
),无需额外加锁即可保证局部一致。
- Redis 单条命令天然原子(如
MULTI/EXEC 事务
- 将多条命令打包,在执行时保证中途不被其他命令插入,但不支持回滚;失败时会跳过出错命令继续。
WATCH 乐观锁
- 监控一个或多个 key,若在事务执行前有修改,整个事务会被中止。
局限:Redis 自身不支持分布式事务协调,需配合应用侧逻辑或外部协调组件才能实现跨多个服务或数据源的一致性。
1.4 事务与锁:基础概念与关系
- 事务(Transaction):逻辑上将一组操作视为一个整体,要么全部成功,要么全部回滚。
- 锁(Lock):用于在并发场景下对某个资源或数据行加排它或共享控制,防止并发冲突。
特性 | 事务 | 锁 |
---|---|---|
关注点 | 处理多步操作的一致性 | 控制并发对单个资源或对象的访问 |
实现方式 | 协调者 + 协议(如 2PC、Saga)或数据库自带事务支持 | 悲观锁(排它锁)、乐观锁(版本/ CAS)、分布式锁(Redis、Zookeeper) |
回滚机制 | 支持(数据库或应用需实现补偿) | 不支持回滚;锁只是并发控制,解锁后资源状态根据业务决定 |
使用场景 | 跨服务、跨库的强一致性场景 | 并发写场景、资源争用高的局部协调 |
第二章:Redis 原子操作与事务命令
2.1 MULTI/EXEC/DISCARD 四大命令详解
Redis 提供了原生的事务支持,通过以下命令组合完成:
- MULTI
开始一个事务,将后续命令入队,不立即执行。 - EXEC
执行事务队列中的所有命令,作为一个批次原子提交。 - DISCARD
放弃事务队列中所有命令,退出事务模式。 - UNWATCH(或隐含于 EXEC/DISCARD 后)
解除对所有 key 的 WATCH 监控。
Client> MULTI
OK
Client> SET user:1:name "Alice"
QUEUED
Client> INCR user:1:counter
QUEUED
Client> EXEC
1) OK
2) (integer) 1
- 在 MULTI 与 EXEC 之间,所有写命令均返回
QUEUED
而不执行。 - 若执行过程中某条命令出错(如语法错误),该命令会在 EXEC 时被跳过,其它命令依然执行。
- EXEC 之后,事务队列自动清空,返回结果列表。
2.2 事务队列实现原理
内部流程简化示意:
flowchart LR
subgraph Server
A[Client MULTI] --> B[enter MULTI state]
B --> C[queue commands]
C --> D[Client EXEC]
D --> E[execute queued commands one by one]
E --> F[exit MULTI state]
end
- Redis 仅在内存中维护一个简单的命令数组,不做持久化。
- 由于单线程模型,EXEC 阶段不会被其他客户端命令插入,保证了“原子”提交的效果。
- 事务并不支持回滚:一旦 EXEC 开始,出错命令跳过也不影响其它操作。
2.3 WATCH 的乐观锁机制
WATCH 命令用来在事务前做乐观并发控制:
- 客户端
WATCH key1 key2 …
,服务器会在内存中记录被监控的 key。 - 执行
MULTI
入队命令。 - 若执行
EXEC
前其他客户端对任一 watched key 执行了写操作,当前事务会失败,返回nil
。
Client1> WATCH user:1:counter
OK
Client1> MULTI
OK
Client1> INCR user:1:counter
QUEUED
# 在此期间,Client2 执行 INCR user:1:counter
Client1> EXEC
(nil) # 事务因 watched key 被修改而放弃
- UNWATCH:可在多 key 监控后决定放弃事务前,手动取消监控。
- WATCH+MULTI+EXEC 模式被视作“乐观锁”:假设冲突少,事务提交前无需加锁,冲突时再回退重试。
2.4 事务冲突场景与重试策略示例
在高并发场景下,WATCH 模式下冲突不可避免。常见重试模式:
import redis
r = redis.Redis()
def incr_user_counter(uid):
key = f"user:{uid}:counter"
while True:
try:
r.watch(key)
count = int(r.get(key) or 0)
pipe = r.pipeline()
pipe.multi()
pipe.set(key, count + 1)
pipe.execute()
break
except redis.WatchError:
# 发生冲突,重试
continue
finally:
r.unwatch()
流程:
- WATCH 监控 key
- 读取当前值
- MULTI -> 修改 -> EXEC
- 若冲突(WatchError),则重试整个流程
- 图解:
sequenceDiagram
participant C as Client
participant S as Server
C->>S: WATCH key
S-->>C: OK
C->>S: GET key
S-->>C: value
C->>S: MULTI
S-->>C: OK
C->>S: SET key newValue
S-->>C: QUEUED
C->>C: (some other client modifies key)
C->>S: EXEC
alt key changed
S-->>C: nil (transaction aborted)
else
S-->>C: [OK]
end
- 重试注意:应设定最大重试次数或退避策略,避免活锁。
第三章:悲观锁与乐观锁在 Redis 中的实现
3.1 悲观锁概念与使用场景
悲观锁(Pessimistic Locking) 假定并发冲突随时会发生,因此,访问共享资源前先获取互斥锁,直到操作完成才释放锁,期间其他线程被阻塞或直接拒绝访问。
适用场景:
- 写多读少、冲突概率高的关键资源(如库存、账户余额)。
- 对一致性要求极高,无法容忍重试失败或脏读的业务。
3.2 Redis 分布式悲观锁(SETNX + TTL)
通过 SETNX
(SET if Not eXists)命令实现基本分布式锁:
SET lock:key uuid NX PX 5000
NX
:仅当 key 不存在时设置,保证互斥。PX 5000
:设置过期时间,避免死锁。
Java 示例(Jedis)
public boolean tryLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SetParams.setParams().nx().px(expireTime));
return "OK".equals(result);
}
public boolean releaseLock(Jedis jedis, String lockKey, String requestId) {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
Object result = jedis.eval(luaScript, Collections.singletonList(lockKey),
Collections.singletonList(requestId));
return Long.valueOf(1).equals(result);
}
- 优点:实现简单,基于 Redis 原子命令。
- 缺点:不支持自动续期、SETNX 与 DEL 之间存在时机窗口。
3.3 乐观锁实现:基于版本号与事务重试
乐观锁(Optimistic Locking) 假定冲突少,通过版本号或时间戳检查,只有在操作前后资源未被修改才提交。
- 实现方式:使用 Redis 的
WATCH
+MULTI/EXEC
。
import redis
r = redis.Redis()
def update_balance(uid, delta):
key = f"user:{uid}:balance"
while True:
try:
r.watch(key)
balance = float(r.get(key) or 0)
new_balance = balance + delta
pipe = r.pipeline()
pipe.multi()
pipe.set(key, new_balance)
pipe.execute()
break
except redis.WatchError:
# 冲突,重试
continue
finally:
r.unwatch()
- 优点:无锁等待成本,适合读多写少场景。
- 缺点:在高并发写场景下可能频繁重试,性能下降。
3.4 代码示例:悲观锁与乐观锁对比实战
下面示例展示库存扣减场景的两种锁策略对比。
// 悲观锁方案:SETNX
public boolean decrementStockPessimistic(Jedis jedis, String productId) {
String lockKey = "lock:stock:" + productId;
String requestId = UUID.randomUUID().toString();
if (!tryLock(jedis, lockKey, requestId, 3000)) {
return false; // 获取锁失败
}
try {
int stock = Integer.parseInt(jedis.get("stock:" + productId));
if (stock <= 0) return false;
jedis.decr("stock:" + productId);
return true;
} finally {
releaseLock(jedis, lockKey, requestId);
}
}
// 乐观锁方案:WATCH + MULTI
public boolean decrementStockOptimistic(Jedis jedis, String productId) {
String key = "stock:" + productId;
while (true) {
jedis.watch(key);
int stock = Integer.parseInt(jedis.get(key));
if (stock <= 0) {
jedis.unwatch();
return false;
}
Transaction tx = jedis.multi();
tx.decr(key);
List<Object> res = tx.exec();
if (res != null) {
return true; // 成功
}
// 冲突,重试
}
}
对比:
- 悲观锁在高并发写时因为互斥性可能成为瓶颈;
- 乐观锁则可能因冲突频繁重试而浪费 CPU 和网络资源。
第四章:RedLock 深度解析
4.1 RedLock 算法背景与设计目标
RedLock 是 Redis 创始人 Antirez 提出的分布式锁算法,旨在通过多个独立 Redis 节点协同工作,解决单节点故障时锁可能失效的问题。
设计目标:
- 安全性:获取锁后,只有持锁者才能解锁,防止误删他人锁。
- 可用性:即使部分 Redis 节点故障,只要大多数节点可用,仍可获取锁。
- 性能:锁获取、释放的延迟保持在可接受范围。
4.2 RedLock 详细流程图解
sequenceDiagram
participant C as Client
participant N1 as Redis1
participant N2 as Redis2
participant N3 as Redis3
participant N4 as Redis4
participant N5 as Redis5
C->>N1: SET lock_key val NX PX ttl
C->>N2: SET lock_key val NX PX ttl
C->>N3: SET lock_key val NX PX ttl
C->>N4: SET lock_key val NX PX ttl
C->>N5: SET lock_key val NX PX ttl
Note right of C: 如果超过半数节点成功,且<br/>总耗时 < ttl,则获取锁成功
步骤:
- 客户端生成唯一随机值
val
作为请求标识。 - 按顺序向 N 个 Redis 实例发送
SET key val NX PX ttl
,使用短超时保证请求不阻塞。 - 计算成功设置锁的节点数量
count
,以及从第一台开始到最后一台花费的总时延elapsed
。 - 若
count >= N/2 + 1
且elapsed < ttl
,则视为获取锁成功;否则视为失败,并向已成功节点发送DEL key
释放锁。
4.3 实现代码剖析(Java 示例)
public class RedLock {
private List<JedisPool> pools;
private long ttl;
private long acquireTimeout;
public boolean lock(String key, String value) {
int successCount = 0;
long startTime = System.currentTimeMillis();
for (JedisPool pool : pools) {
try (Jedis jedis = pool.getResource()) {
String resp = jedis.set(key, value, SetParams.setParams().nx().px(ttl));
if ("OK".equals(resp)) successCount++;
} catch (Exception e) { /* 忽略单节点故障 */ }
}
long elapsed = System.currentTimeMillis() - startTime;
if (successCount >= pools.size() / 2 + 1 && elapsed < ttl) {
return true;
} else {
// 释放已获取的锁
unlock(key, value);
return false;
}
}
public void unlock(String key, String value) {
String lua =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else return 0 end";
for (JedisPool pool : pools) {
try (Jedis jedis = pool.getResource()) {
jedis.eval(lua, Collections.singletonList(key),
Collections.singletonList(value));
} catch (Exception e) { /* 忽略 */ }
}
}
}
关键点:
- 使用相同
value
确保解锁安全。 - 超时判断:若总耗时超过
ttl
,即便设置足够节点,也视为失败,防止锁已过期。 - 异常处理:忽略部分节点故障,但依赖多数节点可用。
- 使用相同
4.4 RedLock 的安全性与争议
安全性分析:
- 在 N/2+1 节点写入成功的前提下,即使部分节点宕机,也能保留锁权。
- 随机
value
确保只有真正持有者能解锁。
争议点:
- 网络延迟波动 可能导致
elapsed < ttl
判定失效,从而出现锁重入风险。 - 时钟漂移:RedLock 假设各个 Redis 节点时钟同步,否则 PX 过期可能不一致。
- 社区质疑:部分专家认为单节点 SETNX + TTL 足以满足大多数分布式锁场景,RedLock 复杂度与收益不匹配。
- 网络延迟波动 可能导致
第五章:基于 Lua 脚本的分布式锁增强
Lua 脚本在 Redis 中以“原子批处理”的方式执行,保证脚本内所有命令在一个上下文中顺序执行,不会被其他客户端命令打断。利用这一特性,可以实现更加安全、灵活的分布式锁。
5.1 Lua 原子性保证与使用场景
- 原子执行:当你向 Redis 发送
EVAL
脚本时,服务器将整个脚本当作一个命令执行,期间不会切换到其他客户端。 脚本场景:
- 自动续期(Watchdog)
- 安全解锁(检查 value 后再 DEL)
- 可重入锁(记录重入次数)
- 锁队列(实现公平锁)
5.2 典型锁脚本实现(一):安全解锁
-- KEYS[1] = lockKey, ARGV[1] = ownerId
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
流程:
- 先
GET lockKey
,与持有者 ID (UUID) 做比对 - 匹配时才
DEL lockKey
,否则返回 0
- 先
- 效果:保证只有真正持锁者才能解锁,防止误删他人锁。
5.3 典型锁脚本实现(二):自动续期 Watchdog
当锁持有时间可能不足以完成业务逻辑时,需要“自动续期”机制,常见实现——后台定时执行脚本。
-- KEYS[1] = lockKey, ARGV[1] = ownerId, ARGV[2] = additionalTTL
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
else
return 0
end
- 在业务执行过程中,每隔
TTL/3
调用一次该脚本延长锁寿命,确保业务完成前锁不被过期。
5.4 可重入锁脚本示例
可重入锁允许同一个客户端多次加锁,每次加锁仅需增加内部计数,释放时再递减,直至 0 才真正释放。
-- KEYS[1]=lockKey, ARGV[1]=ownerId, ARGV[2]=ttl
local entry = redis.call("HGETALL", KEYS[1])
if next(entry) == nil then
-- 首次加锁,创建 hash: { owner=ownerId, count=1 }
redis.call("HMSET", KEYS[1], "owner", ARGV[1], "count", 1)
redis.call("PEXPIRE", KEYS[1], ARGV[2])
return 1
else
local storedOwner = entry[2]
if storedOwner == ARGV[1] then
-- 重入:计数+1,并续期
local cnt = tonumber(entry[4]) + 1
redis.call("HSET", KEYS[1], "count", cnt)
redis.call("PEXPIRE", KEYS[1], ARGV[2])
return cnt
else
return 0
end
end
- 存储结构:使用 Hash 记录
owner
与count
。 释放脚本:
-- KEYS[1]=lockKey, ARGV[1]=ownerId local entry = redis.call("HGETALL", KEYS[1]) if next(entry) == nil then return 0 end if entry[2] == ARGV[1] then local cnt = tonumber(entry[4]) - 1 if cnt > 0 then redis.call("HSET", KEYS[1], "count", cnt) return cnt else return redis.call("DEL", KEYS[1]) end end return 0
5.5 性能与安全性评估
特性 | 优点 | 缺点 |
---|---|---|
原子脚本执行 | 无需往返多条命令,网络延迟低;执行期间不会被打断 | Lua 脚本会阻塞主线程 |
安全解锁 | 避免 SETNX+DEL 的竞态 | 脚本过长可能影响性能 |
可重入支持 | 业务调用可安全重入、无锁重获失败 | 状态存储更复杂,Hash 占用更多内存 |
自动续期 | 保障长时业务场景的锁稳定性 | 需要客户端定时心跳,复杂度提升 |
第六章:分布式事务模式在 Redis 上的实践
在微服务与分布式架构中,跨服务或跨数据库的一致性需求日益突出。传统的全局事务(如 2PC)在性能与可用性方面存在瓶颈。基于 Redis、消息队列以及应用协议的分布式事务模式成为主流选择。本章聚焦两大常见模式:Saga 与 TCC,并探讨 XSAGA 在 Redis 场景下的实现思路。
6.1 Saga 模式基础与 Redis 实现思路
Saga 模式将一个大事务拆分为一系列本地事务(step)与相应的补偿事务(compensation)。各服务按顺序执行本地事务,若中途某步失败,依次调用前面步骤的补偿事务,达到数据最终一致性。
步骤示意
- 执行 T1;若成功,推进至 T2,否则执行 C1。
- 执行 T2;若成功,推进至 T3,否则依次执行 C2、C1。
- …
sequenceDiagram
Client->>OrderService: CreateOrder()
OrderService->>InventoryService: ReserveStock()
alt ReserveStock OK
OrderService->>PaymentService: ReservePayment()
alt ReservePayment OK
OrderService->>Client: Success
else ReservePayment FAIL
OrderService->>InventoryService: CompensateReleaseStock()
OrderService->>Client: Failure
end
else ReserveStock FAIL
OrderService->>Client: Failure
end
Redis 实现要点
状态存储:使用 Redis Hash 存储 Saga 状态:
HSET saga:{sagaId} step T1 status PENDING
- 可靠调度:结合消息队列(如 RabbitMQ)确保命令至少执行一次。
- 补偿执行:若下游失败,由协调者发送补偿消息,消费者触发补偿逻辑。
- 超时处理:利用 Redis TTL 与 keyspace notifications 触发超时回滚。
6.2 TCC(Try-Confirm-Cancel)模式与 Redis
TCC模式将事务分为三步:
- Try:预留资源或执行业务预处理。
- Confirm:确认事务,正式扣减或提交。
- Cancel:取消预留,回滚资源。
典型流程
sequenceDiagram
Client->>ServiceA: tryA()
ServiceA->>ServiceB: tryB()
alt All try OK
ServiceA->>ServiceB: confirmB()
ServiceA->>Client: confirmA()
else Any try FAIL
ServiceA->>ServiceB: cancelB()
ServiceA->>Client: cancelA()
end
Redis 协调示例
在 Try 阶段写入预留 key,并设置 TTL:
SET reserved:order:{orderId} userId NX PX 60000
- Confirm 成功后,DEL 该 key;Cancel 失败后,同样 DEL 并执行回滚逻辑。
- 优点:明确的三段式接口,易于补偿管理。
- 缺点:需实现 Try、Confirm、Cancel 三套接口,开发成本高。
6.3 XSAGA 模式示例:结合消息队列与 Redis
XSAGA 是 Saga 的扩展,使用状态机 + 可靠消息实现多事务编排,典型平台如 Apache ServiceComb Pack。
核心组件
- 事务协调者:控制 Saga 执行流程,发布各 Step 消息。
- 消息中间件:保证消息可靠投递与重试。
- 参与者:消费消息,执行本地事务并更新状态。
- Redis 存储:缓存 Saga 全局状态、Step 状态与补偿函数路由。
Redis 存储设计
HSET xsaga:{sagaId}:status globalState "INIT"
HSET xsaga:{sagaId}:steps step1 "PENDING"
HSET xsaga:{sagaId}:steps step2 "PENDING"
消费者在成功后:
HSET xsaga:{sagaId}:steps step1 "SUCCESS"
失败时:
HSET xsaga:{sagaId}:steps step1 "FAIL" RPUSH xsaga:{sagaId}:compensate compensateStep1
- 协调者根据状态机读取 Redis 并发布下一个命令或补偿命令。
6.4 实战案例:电商下单跨服务事务
以“创建订单 → 扣减库存 → 扣减余额”场景展示 Saga 模式实战。
创建订单:OrderService 记录订单信息,并保存状态至 Redis:
HSET saga:1001 step:createOrder "SUCCESS"
扣减库存:InventoryService 订阅
ReserveStock
消息,执行并更新 Redis:HSET saga:1001 step:reserveStock "SUCCESS"
- 扣减余额:PaymentService 订阅
ReservePayment
消息,执行并更新 Redis。 - 确认:协商者检查所有 step 状态为 SUCCESS 后,发布 Confirm 消息至各服务。
- 补偿:若任一 step FAIL,顺序执行补偿,如库存回滚、余额退回。
第七章:锁失效、超时与防死锁策略
在分布式锁场景中,锁过期、超时、死锁是常见挑战,本章深入分析并提供解决方案。
7.1 锁过期失效场景分析
- 业务处理超时:持有者业务执行超过锁 TTL,锁自动过期,其他客户端可能抢占,导致并发操作错误。
- 解决:自动续期或延长 TTL。
7.2 Watchdog 自动续期机制
基于 Redisson 的 Watchdog:若客户端在锁到期前仍在运行,自动为锁续期。
- 默认超时时间:30 秒。
- 调用
lock()
后,后台定时线程周期性发送PEXPIRE
脚本延长 TTL。
RLock lock = redisson.getLock("resource");
lock.lock(); // 自动续期
try {
// 业务代码
} finally {
lock.unlock();
}
7.3 防止死锁的最佳实践
- 合理设置 TTL:结合业务最坏执行时间估算。
- 使用可重入锁:减少同线程重复加锁引发的死锁。
- 请求超时机制:客户端设定最大等待时间,尝试失败后放弃或降级。
7.4 代码示例:可靠锁释放与续期
String luaRenew =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('pexpire', KEYS[1], ARGV[2]) " +
"else return 0 end";
// 定时续期线程
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
jedis.eval(luaRenew, List.of(lockKey), List.of(requestId, "5000"));
}, 5, 5, TimeUnit.SECONDS);
第八章:高可用与锁的容灾设计
锁机制在 Redis Sentinel 或 Cluster 环境下,需考虑主从切换与分片容灾。
8.1 单实例锁的局限性
- 节点宕机,锁丢失;
- 尝试获取锁时可能连接失败。
8.2 Sentinel/Cluster 环境下的锁可靠性
- Sentinel:自动主从切换,客户端需使用 Sentinel Pool;锁脚本需在所有节点上统一执行。
- Cluster:锁 key 分布到某个 slot,需确保所有脚本与客户端指向正确节点。
8.3 主从切换与锁恢复
- 切换窗口期,锁可能在新主上不存在;
- 解决:使用 RedLock 多节点算法,或在多个实例上冗余存储锁。
8.4 容灾演练:故障切换场景下的锁安全
- 模拟主节点挂掉,检查 RedLock 是否仍能获取大多数节点锁;
- Sentinel 切换后,验证脚本与客户端自动连接。
第九章:锁与性能优化
9.1 锁的粒度与并发影响
- 粗粒度锁:简单但并发性能差;
- 细粒度锁:提高并发但管理复杂。
9.2 限流、降级与锁结合
- 使用 Token Bucket 限流先前置;
- 锁失败时可降级返回缓存或默认值。
if (!rateLimiter.tryAcquire()) return fallback();
if (lock.tryLock()) { ... }
9.3 大并发场景下的锁性能测试
- 利用 JMeter 或 custom thread pool 对比 SETNX、RedLock、Redisson 性能;
- 指标:成功率、平均延迟、吞吐量。
9.4 环境搭建与压力测试脚本
# JMeter 测试脚本示例,设置并发 1000
jmeter -n -t lock_test.jmx -Jthreads=1000
第十章:分布式事务监控与故障排查
10.1 监控锁获取、释放与超时指标
- 收集
lock:acquire:success
、lock:acquire:fail
、lock:release
计数; - Prometheus + Grafana 可视化。
10.2 事务执行链路跟踪
- 使用 Sleuth 或 Zipkin,链路中记录 Redis 脚本调用。
- 在链路报文中标注锁 key 与 requestId。
10.3 常见故障案例剖析
- 锁未释放:可能因脚本错误或网络中断;
- 续期失败:脚本未执行,TTL 到期。
10.4 报警策略与实践
lock:acquire:fail
超阈值报警;- 未释放锁告警(探测 key 长时间存在)。
第十一章:生产级锁框架与封装
11.1 Spring-Redis 分布式锁实践
@Component
public class RedisLockService {
@Autowired private StringRedisTemplate redis;
public boolean lock(String key, String id, long ttl) { ... }
public boolean unlock(String key, String id) { ... }
}
11.2 Redisson、Lettuce 等客户端对比
框架 | 特性 |
---|---|
Redisson | 高级特性(可重入、延迟队列) |
Lettuce | 轻量、高性能、响应式 |
Jedis | 简单、成熟 |
11.3 自研锁框架关键模块设计
- LockManager 管理不同类型锁;
- RetryPolicy 定制重试逻辑;
- MetricsCollector 上报监控。
11.4 发布与灰度、回滚方案
- 分组灰度:逐步打开分布式锁功能;
- 回滚:配置中心开关、客户端降级到本地锁。