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

2 阅读3分钟

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

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

方案一:SETNX(最基础)

// 加锁
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:判断和删除不是原子操作

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

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

// 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客户端,内置了分布式锁实现。

// 引入依赖
// <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的看门狗机制自动续期:

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

方案三:RedLock(多节点)

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

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

// 多个独立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单节点就够了。

三种方案对比

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

使用注意事项

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

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

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