分布式锁是解决分布式系统并发问题的关键组件,Redis因其高性能和丰富的数据结构成为实现分布式锁的常用选择。以下是Redis分布式锁的几种主流实现方式及其技术细节:
![图片[1]_Redis分布式锁的几种实现方法详解_知途无界](https://zhituwujie.com/wp-content/uploads/2025/04/d2b5ca33bd20250416093057.png)
一、基础实现方案
1. SETNX + EXPIRE(基本版)
// 加锁Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order:123", "1", 30, TimeUnit.SECONDS);// 解锁(直接删除)redisTemplate.delete("lock:order:123");// 加锁 Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order:123", "1", 30, TimeUnit.SECONDS); // 解锁(直接删除) redisTemplate.delete("lock:order:123");// 加锁 Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order:123", "1", 30, TimeUnit.SECONDS); // 解锁(直接删除) redisTemplate.delete("lock:order:123");
问题:非原子性操作可能导致死锁(SETNX成功但EXPIRE失败)
2. SET扩展参数(推荐基础方案)
// 原子性加锁(Redis 2.6.12+)Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order:123",UUID.randomUUID().toString(),30,TimeUnit.SECONDS);// 解锁(Lua脚本保证原子性)String script ="if redis.call('get', KEYS[1]) == ARGV[1] then " +" return redis.call('del', KEYS[1]) " +"else " +" return 0 " +"end";redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),Collections.singletonList("lock:order:123"),lockValue);// 原子性加锁(Redis 2.6.12+) Boolean locked = redisTemplate.opsForValue().setIfAbsent( "lock:order:123", UUID.randomUUID().toString(), 30, TimeUnit.SECONDS ); // 解锁(Lua脚本保证原子性) String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; redisTemplate.execute( new DefaultRedisScript<>(script, Long.class), Collections.singletonList("lock:order:123"), lockValue );// 原子性加锁(Redis 2.6.12+) Boolean locked = redisTemplate.opsForValue().setIfAbsent( "lock:order:123", UUID.randomUUID().toString(), 30, TimeUnit.SECONDS ); // 解锁(Lua脚本保证原子性) String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; redisTemplate.execute( new DefaultRedisScript<>(script, Long.class), Collections.singletonList("lock:order:123"), lockValue );
优点:原子性操作,避免误删其他客户端锁
二、高可用方案
3. RedLock算法(Redis官方推荐)
// 需要至少3个独立的Redis实例List<RedisTemplate> redisTemplates = Arrays.asList(redis1, redis2, redis3);// 获取锁String lockKey = "lock:order:123";String lockValue = UUID.randomUUID().toString();int lockExpireTime = 30; // 秒int successCount = 0;long startTime = System.currentTimeMillis();for (RedisTemplate rt : redisTemplates) {if (rt.opsForValue().setIfAbsent(lockKey, lockValue, lockExpireTime, TimeUnit.SECONDS)) {successCount++;}}// 验证是否获取多数节点锁boolean locked = successCount >= redisTemplates.size() / 2 + 1;long costTime = System.currentTimeMillis() - startTime;// 有效时间需减去获取锁耗时if (locked && costTime < lockExpireTime * 1000) {// 锁获取成功} else {// 释放已获取的锁for (RedisTemplate rt : redisTemplates) {rt.execute(unlockScript, Collections.singletonList(lockKey), lockValue);}}// 需要至少3个独立的Redis实例 List<RedisTemplate> redisTemplates = Arrays.asList(redis1, redis2, redis3); // 获取锁 String lockKey = "lock:order:123"; String lockValue = UUID.randomUUID().toString(); int lockExpireTime = 30; // 秒 int successCount = 0; long startTime = System.currentTimeMillis(); for (RedisTemplate rt : redisTemplates) { if (rt.opsForValue().setIfAbsent(lockKey, lockValue, lockExpireTime, TimeUnit.SECONDS)) { successCount++; } } // 验证是否获取多数节点锁 boolean locked = successCount >= redisTemplates.size() / 2 + 1; long costTime = System.currentTimeMillis() - startTime; // 有效时间需减去获取锁耗时 if (locked && costTime < lockExpireTime * 1000) { // 锁获取成功 } else { // 释放已获取的锁 for (RedisTemplate rt : redisTemplates) { rt.execute(unlockScript, Collections.singletonList(lockKey), lockValue); } }// 需要至少3个独立的Redis实例 List<RedisTemplate> redisTemplates = Arrays.asList(redis1, redis2, redis3); // 获取锁 String lockKey = "lock:order:123"; String lockValue = UUID.randomUUID().toString(); int lockExpireTime = 30; // 秒 int successCount = 0; long startTime = System.currentTimeMillis(); for (RedisTemplate rt : redisTemplates) { if (rt.opsForValue().setIfAbsent(lockKey, lockValue, lockExpireTime, TimeUnit.SECONDS)) { successCount++; } } // 验证是否获取多数节点锁 boolean locked = successCount >= redisTemplates.size() / 2 + 1; long costTime = System.currentTimeMillis() - startTime; // 有效时间需减去获取锁耗时 if (locked && costTime < lockExpireTime * 1000) { // 锁获取成功 } else { // 释放已获取的锁 for (RedisTemplate rt : redisTemplates) { rt.execute(unlockScript, Collections.singletonList(lockKey), lockValue); } }
特点:
- 需要部署多个独立Redis实例
- 遵循CP模型,牺牲部分性能换取可靠性
- 时钟漂移可能影响准确性
三、客户端框架方案
4. Redisson实现(生产级推荐)
// 配置Redisson客户端Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379");RedissonClient redisson = Redisson.create(config);// 获取锁对象RLock lock = redisson.getLock("lock:order:123");try {// 尝试加锁,最多等待10秒,锁定后30秒自动释放boolean isLock = lock.tryLock(10, 30, TimeUnit.SECONDS);if (isLock) {// 业务逻辑}} finally {lock.unlock();}// 配置Redisson客户端 Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); // 获取锁对象 RLock lock = redisson.getLock("lock:order:123"); try { // 尝试加锁,最多等待10秒,锁定后30秒自动释放 boolean isLock = lock.tryLock(10, 30, TimeUnit.SECONDS); if (isLock) { // 业务逻辑 } } finally { lock.unlock(); }// 配置Redisson客户端 Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); // 获取锁对象 RLock lock = redisson.getLock("lock:order:123"); try { // 尝试加锁,最多等待10秒,锁定后30秒自动释放 boolean isLock = lock.tryLock(10, 30, TimeUnit.SECONDS); if (isLock) { // 业务逻辑 } } finally { lock.unlock(); }
核心机制:
- 看门狗自动续期(默认30秒检测,续期到30秒)
- 可重入锁设计
- 支持公平锁和非公平锁
- 提供联锁(MultiLock)、红锁(RedLock)等扩展
四、特殊场景方案
5. 基于Hash的细粒度锁
// 对订单ID 123的库存项456加锁String lockKey = "lock:order:123";String field = "item_456";// 加锁redisTemplate.opsForHash().putIfAbsent(lockKey, field, "1");redisTemplate.expire(lockKey, 30, TimeUnit.SECONDS);// 解锁redisTemplate.opsForHash().delete(lockKey, field);// 对订单ID 123的库存项456加锁 String lockKey = "lock:order:123"; String field = "item_456"; // 加锁 redisTemplate.opsForHash().putIfAbsent(lockKey, field, "1"); redisTemplate.expire(lockKey, 30, TimeUnit.SECONDS); // 解锁 redisTemplate.opsForHash().delete(lockKey, field);// 对订单ID 123的库存项456加锁 String lockKey = "lock:order:123"; String field = "item_456"; // 加锁 redisTemplate.opsForHash().putIfAbsent(lockKey, field, "1"); redisTemplate.expire(lockKey, 30, TimeUnit.SECONDS); // 解锁 redisTemplate.opsForHash().delete(lockKey, field);
适用场景:需要对同一资源的不同部分加锁
6. 乐观锁实现(CAS机制)
// 获取当前版本号int currentVersion = Integer.parseInt(redisTemplate.opsForValue().get("version:order:123"));// 业务处理...// 更新时检查版本redisTemplate.execute(new SessionCallback() {@Overridepublic Object execute(RedisOperations operations) {operations.watch("version:order:123");int newVersion = Integer.parseInt(operations.opsForValue().get("version:order:123"));if (newVersion == currentVersion) {operations.multi();operations.opsForValue().increment("version:order:123");return operations.exec(); // 返回null表示失败}operations.unwatch();return null;}});// 获取当前版本号 int currentVersion = Integer.parseInt(redisTemplate.opsForValue().get("version:order:123")); // 业务处理... // 更新时检查版本 redisTemplate.execute(new SessionCallback() { @Override public Object execute(RedisOperations operations) { operations.watch("version:order:123"); int newVersion = Integer.parseInt(operations.opsForValue().get("version:order:123")); if (newVersion == currentVersion) { operations.multi(); operations.opsForValue().increment("version:order:123"); return operations.exec(); // 返回null表示失败 } operations.unwatch(); return null; } });// 获取当前版本号 int currentVersion = Integer.parseInt(redisTemplate.opsForValue().get("version:order:123")); // 业务处理... // 更新时检查版本 redisTemplate.execute(new SessionCallback() { @Override public Object execute(RedisOperations operations) { operations.watch("version:order:123"); int newVersion = Integer.parseInt(operations.opsForValue().get("version:order:123")); if (newVersion == currentVersion) { operations.multi(); operations.opsForValue().increment("version:order:123"); return operations.exec(); // 返回null表示失败 } operations.unwatch(); return null; } });
五、方案对比分析
方案 | 可靠性 | 性能 | 实现复杂度 | 适用场景 |
---|---|---|---|---|
SETNX+EXPIRE | ★★☆ | ★★★ | ★☆☆ | 简单场景,低并发 |
SET扩展参数 | ★★★ | ★★★ | ★★☆ | 常规业务,中等并发 |
RedLock | ★★★ | ★★☆ | ★★★ | 高可靠要求,金融场景 |
Redisson | ★★★ | ★★☆ | ★☆☆ | 生产环境首选 |
Hash细粒度锁 | ★★☆ | ★★☆ | ★★☆ | 资源分段锁定 |
乐观锁 | ★☆☆ | ★★★ | ★★★ | 冲突少的读多写少场景 |
六、生产环境注意事项
- 锁续期机制:
// Redisson看门狗实现原理private void scheduleExpirationRenewal(long threadId) {Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) {// 异步续期renewExpirationAsync(threadId);}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);}// Redisson看门狗实现原理 private void scheduleExpirationRenewal(long threadId) { Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) { // 异步续期 renewExpirationAsync(threadId); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); }// Redisson看门狗实现原理 private void scheduleExpirationRenewal(long threadId) { Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) { // 异步续期 renewExpirationAsync(threadId); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); }
- 锁等待策略优化:
- 指数退避重试
- 熔断机制防止雪崩
- 锁监控:
# Redis监控命令redis-cli --bigkeysredis-cli monitor | grep 'lock:'# Redis监控命令 redis-cli --bigkeys redis-cli monitor | grep 'lock:'# Redis监控命令 redis-cli --bigkeys redis-cli monitor | grep 'lock:'
- 死锁预防:
- 设置合理的TTL
- 实现锁的自动释放
- 添加线程标识防止误删
七、选型建议
- 中小规模系统:直接使用Redisson框架
- 高并发场景:RedLock + 本地缓存降级
- 秒杀系统:Redis锁 + 库存分段 + 乐观锁
- 旧版本Redis:Lua脚本实现原子操作
正确实现分布式锁需要综合考虑CAP理论中的权衡,建议在开发完成后进行:
- 并发压力测试
- 网络分区模拟
- 故障恢复演练
最终方案应根据业务特点、Redis版本和团队技术栈综合决定。
© 版权声明
文中内容均来源于公开资料,受限于信息的时效性和复杂性,可能存在误差或遗漏。我们已尽力确保内容的准确性,但对于因信息变更或错误导致的任何后果,本站不承担任何责任。如需引用本文内容,请注明出处并尊重原作者的版权。
THE END
暂无评论内容