# 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的实现已经足够成熟稳定,大部分场景直接用它就好。

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

相关推荐
Fatbobman(东坡肘子)1 小时前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
开发语言·人工智能·redis·ios·swiftui·iphone·swift
温暖小土2 小时前
在 CentOS 上安装 Redis Stack
linux·redis·centos
Mikko75 小时前
Redis 连接数满了服务端有错误信息吗?连接数不增加却命令超时?6.2 实测连接池、超时与大 key 怎么判定
java·spring boot·redis·后端
无言听风雨丶6 小时前
Redis 运维实战与排错
运维·数据库·redis
字节探索10 小时前
Redis 分布式锁:从 SETNX 误删到看门狗自动续期的原理与实践
redis·分布式
真上帝的左手11 小时前
30. 技术专题-‌应用框架组件-缓存-Redis
数据库·redis·分布式·缓存·中间件
bksczm12 小时前
C++Redis开发(Ubuntu 22.04)
数据库·redis·缓存
goehou12 小时前
缓存该删还是该改,LLM 服务里的双写问题
redis·缓存·llm·一致性·系统设计
余槐i12 小时前
Spring Boot 3.2 偶发500无日志:线程池复用导致请求上下文丢失
spring boot·redis·后端·mysql·性能优化
IT规划师14 小时前
Redis 数据类型全解 - ZSet:两个编码,七种玩法(五)
redis·.net