为什么要选择Redis?
Redis凭借其高性能、丰富的数据结构和原子操作特性,成为分布式锁的首选方案之一。但不同实现方案在可靠性、性能和复杂度等方面存在显著差异。
一、基础方案:SETNX命令实现
public class SimpleRedisLock {
private Jedis jedis;
private String lockKey;
public SimpleRedisLock(Jedis jedis, String lockKey) {
this.jedis = jedis;
this.lockKey = lockKey;
}
public boolean tryLock() {
Long result = jedis.setnx(lockKey, "locked");
if (result == 1) {
jedis.expire(lockKey, 30); // 设置过期时间
return true;
}
return false;
}
public void unlock() {
jedis.del(lockKey);
}
}
// 使用示例
Jedis jedis = new Jedis("localhost");
SimpleRedisLock lock = new SimpleRedisLock(jedis, "order_lock");
try {
if (lock.tryLock()) {
// 业务逻辑
}
} finally {
lock.unlock();
}
问题分析:
-
非原子操作:setnx和expire非原子操作,可能产生死锁
-
锁误删:任何客户端都可以删除锁
-
不可重入:同一线程重复获取会失败
二、改进方案:原子SET命令
-
public class AtomicRedisLock { private Jedis jedis; private String lockKey; private String clientId; public AtomicRedisLock(Jedis jedis, String lockKey) { this.jedis = jedis; this.lockKey = lockKey; this.clientId = UUID.randomUUID().toString(); } public boolean tryLock(int expireSeconds) { String result = jedis.set(lockKey, clientId, SetParams.setParams().nx().ex(expireSeconds)); return "OK".equals(result); } public boolean unlock() { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " + "return redis.call('del', KEYS[1]) " + "else " + "return 0 " + "end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(clientId)); return result.equals(1L); } } // 使用示例 AtomicRedisLock lock = new AtomicRedisLock(jedis, "payment_lock"); try { if (lock.tryLock(30)) { // 处理支付业务 } } finally { lock.unlock(); }
核心改进:
-
使用原子SET命令:
SET key value NX EX
-
Lua脚本保证删除原子性
-
客户端唯一标识防止误删
仍然存在的问题:
-
锁续期困难
-
单点故障风险
-
业务超时可能导致锁失效
三、高可用方案:RedLock算法
3.1 RedLock实现原理
public class RedLock {
private List<Jedis> jedisList;
private String lockKey;
private String clientId;
private int quorum;
public RedLock(List<Jedis> jedisList, String lockKey) {
this.jedisList = jedisList;
this.lockKey = lockKey;
this.clientId = UUID.randomUUID().toString();
this.quorum = jedisList.size() / 2 + 1;
}
public boolean tryLock(int expireMillis) {
long startTime = System.currentTimeMillis();
// 第一阶段:尝试获取多数节点锁
int successCount = 0;
for (Jedis jedis : jedisList) {
if (tryAcquire(jedis, expireMillis)) {
successCount++;
}
if ((System.currentTimeMillis() - startTime) > expireMillis) {
break;
}
}
// 第二阶段:验证锁有效性
if (successCount >= quorum) {
long validityTime = expireMillis - (System.currentTimeMillis() - startTime);
return validityTime > 0;
}
// 第三阶段:释放已获得的锁
for (Jedis jedis : jedisList) {
release(jedis);
}
return false;
}
private boolean tryAcquire(Jedis jedis, long expireMillis) {
try {
String result = jedis.set(lockKey, clientId,
SetParams.setParams().nx().px(expireMillis));
return "OK".equals(result);
} catch (Exception e) {
return false;
}
}
private void release(Jedis jedis) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(clientId));
}
}
部署要求:
-
至少5个独立Redis实例
-
节点间时钟同步
-
需要配置合理的超时时间
适用场景:
-
金融交易等对可靠性要求极高的场景
-
需要跨机房部署的分布式系统
四、生产级方案:Redisson实现
4.1 Redisson分布式锁
// 配置Redisson客户端
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 获取锁对象
RLock lock = redisson.getLock("orderLock");
try {
// 尝试加锁,最多等待100秒,锁定后30秒自动解锁
boolean isLock = lock.tryLock(100, 30, TimeUnit.SECONDS);
if (isLock) {
// 业务处理
}
} finally {
lock.unlock();
}
// 关闭客户端
redisson.shutdown();
核心特性:
-
自动续期机制(Watchdog)
// Watchdog实现原理(简化版)
private void renewExpiration() {
Timeout task = commandExecutor.schedule(() -> {
if (redisClient.eval(...)) { // 检查是否仍持有锁
expireAsync(); // 续期
renewExpiration(); // 递归调用
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
}
-
支持可重入锁
-
提供公平锁、联锁(MultiLock)、红锁(RedLock)实现
-
完善的故障处理机制
五、方案对比与选型指南
方案 |
可靠性 |
性能 |
复杂度 |
适用场景 |
---|---|---|---|---|
SETNX基础版 |
★☆☆☆☆ |
5w+ |
低 |
测试环境、临时任务 |
原子SET+Lua |
★★★☆☆ |
4w+ |
中 |
中小型业务、非关键路径 |
RedLock |
★★★★☆ |
2w+ |
高 |
金融级可靠性要求 |
Redisson |
★★★★★ |
3w+ |
低 |
生产环境首选方案 |
性能压测数据参考:
并发线程数 |
SETNX方案 |
Redisson方案 |
RedLock方案 |
---|---|---|---|
100 |
3200 TPS |
2800 TPS |
1200 TPS |
500 |
4100 TPS |
3500 TPS |
1500 TPS |
1000 |
4500 TPS |
3800 TPS |
1800 TPS |
六、常见问题解决方案
问题1:锁提前释放
场景:业务处理时间 > 锁过期时间
解决方案:
// Redisson自动续期
lock.lock(30, TimeUnit.SECONDS); // 看门狗自动续期
// 手动续期(仅Redisson高级用法)
lock.expire(60, TimeUnit.SECONDS);
问题2:主从切换数据丢失
解决方案:
// 使用RedLock算法
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock();
// 启用WAIT命令(Redis 3.0+)
jedis.set(lockKey, value, SetParams.setParams().nx().ex(30));
jedis.waitReplicas(1, 5000); // 等待1个副本确认
问题3:GC停顿导致锁失效
应对策略:
-
监控JVM GC时间
-
适当延长锁超时时间
-
使用网络心跳检测
总结:
如何选择合适方案?
-
初创快速验证:使用Redisson单节点锁
-
金融交易场景:RedLock + 5节点集群
-
高并发读场景:Redisson读写锁
-
定时任务调度:Redisson公平锁
最终建议:
-
生产环境优先使用Redisson
-
特殊需求可基于Lua脚本扩展
-
必须进行故障演练:
-
模拟网络分区
-
强制主从切换
-
注入时钟漂移异常
-