# Redis分布式锁三种实现方案,踩坑记录

单机环境下的锁用synchronized就够了。但分布式环境下,多个服务实例共享同一份数据,锁就得靠Redis来实现。

今天把Redis分布式锁的三种实现方案都过一遍,包括每种方案的坑。

方案一:SETNX(最基础)

java 复制代码
// 加锁
public boolean tryLock(String key, String value, long expireSeconds) {
    // SETNX = SET if Not eXists
    Boolean result = redisTemplate.opsForValue()
        .setIfAbsent(key, value, expireSeconds, TimeUnit.SECONDS);
    return Boolean.TRUE.equals(result);
}

// 解锁
public void unlock(String key, String value) {
    // 先判断是不是自己的锁,再删除
    String currentValue = redisTemplate.opsForValue().get(key);
    if (value.equals(currentValue)) {
        redisTemplate.delete(key);
    }
}

坑1:判断和删除不是原子操作

css 复制代码
线程A:get → 是我的锁
                              此时锁过期了
线程B:set成功,拿到了锁
线程A:delete → 把B的锁删了!

解决:用Lua脚本保证原子性。

java 复制代码
// Lua脚本:原子性地判断+删除
String luaScript =
    "if redis.call('get', KEYS[1]) == ARGV[1] then " +
    "    return redis.call('del', KEYS[1]) " +
    "else " +
    "    return 0 " +
    "end";

redisTemplate.execute(
    new DefaultRedisScript<>(luaScript, Long.class),
    Collections.singletonList(lockKey),
    lockValue
);

坑2:锁过期了但业务还没执行完

解决:锁续期。但自己实现续期很复杂,所以有了Redisson。

方案二:Redisson(推荐)

Redisson是Java生态中最成熟的Redis客户端,内置了分布式锁实现。

java 复制代码
// 引入依赖
// <dependency>
//     <groupId>org.redisson</groupId>
//     <artifactId>redisson-spring-boot-starter</artifactId>
//     <version>3.25.0</version>
// </dependency>

// 配置
@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
              .setAddress("redis://127.0.0.1:6379")
              .setPassword("yourpassword");
        return Redisson.create(config);
    }
}

// 使用
@Service
public class OrderService {

    @Autowired
    private RedissonClient redisson;

    public void createOrder(Long userId) {
        RLock lock = redisson.getLock("order:" + userId);

        try {
            // 加锁:等待3秒,锁定后30秒自动释放
            boolean acquired = lock.tryLock(3, 30, TimeUnit.SECONDS);
            if (!acquired) {
                throw new BusinessException("系统繁忙,请稍后重试");
            }

            // 执行业务逻辑
            processOrder(userId);

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 解锁(只有持有锁的线程才能解锁)
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

Redisson的看门狗机制自动续期:

erlang 复制代码
加锁成功(默认30s过期)
  ↓
业务执行中...
  ↓
每10s自动续期一次(看门狗线程)
  ↓
业务执行完毕,主动解锁
  ↓
如果服务宕机,看门狗停止,30s后锁自动过期

方案三:RedLock(多节点)

单节点Redis的分布式锁有隐患------如果Redis主节点挂了,锁数据还没同步到从节点,其他线程可能重复加锁。

RedLock的思路:在多个独立的Redis节点上加锁,大多数节点加锁成功才算成功。

java 复制代码
// 多个独立Redis实例
List<RedisURI> uris = Arrays.asList(
    new RedisURI("redis://node1:6379"),
    new RedisURI("redis://node2:6379"),
    new RedisURI("redis://node3:6379")
);

Config config = new Config();
// 配置多节点...
RedissonRedLock redLock = redisson.getRedLock(
    redisson.getLock("lock1"),
    redisson.getLock("lock2"),
    redisson.getLock("lock3")
);

redLock.lock(); // 至少N/2+1个节点加锁成功

但RedLock争议很大------Martin Kleppmann(《DERTA》作者)公开质疑过其安全性。实际项目中,大部分场景用Redisson单节点就够了。

三种方案对比

对比 SETNX Redisson RedLock
实现难度 低(但坑多) 低(封装好了) 高
安全性 中(有坑) 高(看门狗续期) 最高
性能 最好 好 较差
推荐度 学习用 生产推荐 极端场景

使用注意事项

注意点 说明
设置过期时间 必须设!否则死锁
唯一标识 value用UUID+线程ID,防止误删别人的锁
重试策略 tryLock失败后重试,别直接报错
锁粒度 越细越好,别锁太大
超时时间 比业务最大执行时间稍长

分布式锁是微服务架构中的常见需求。Redisson的实现已经足够成熟稳定,大部分场景直接用它就好。

你们项目里分布式锁用的什么方案?

相关推荐
石小千1 小时前
Redis开启ACL认证
数据库·redis·缓存
hweiyu003 小时前
Redis命令:LPOS
redis·缓存
guo_wen_qiang3 小时前
云服务器redis主从复制-哨兵模式
运维·服务器·redis
for_ever_love__3 小时前
LangGraph State 设计:多节点之间怎么安全地共享数据
redis·mysql·状态模式·checkpoint·state·状态图·langgraph
大圣编蚕4 小时前
Redis 为什么快?现在还是单线程吗?
数据库·redis·缓存
Linux实战笔记4 小时前
记一次Redis分布式锁实现线上排查:Redlock算法与 Lua 脚本
redis·高并发·分布式锁·redlock 算法·lua 脚本
轮子大叔5 小时前
Redis深入浅出
redis·缓存
计算机就业还在扩大5 小时前
Redis中的setnx是原子性的?
数据库·redis·缓存
Java爱好狂.8 小时前
Java小白如何三天速通redis?
java·数据库·redis·架构师·分布式锁·java面试·java程序员