• Redisson简明教程—你家的锁芯该换了

Redisson简明教程—你家的锁芯该换了

2025-06-05 10:00:03 栏目:宝塔面板 14 阅读

  • 1.简介
  • 2.看门狗

2-1.为什么需要这个"狗子"?

2-2.工作原理

2-3.如何“撸狗子”

  • 3.可重入锁:你的分布式"万能钥匙"
  • 3-1.Redisson可重入锁的魔法

  • 3-2.使用姿势大全

  • 3-3.公平锁

  • 3-4.非公平锁

  • 3-5.可重入原理深扒

  • 4.联锁:分布式锁中的"全家桶套餐"

  • 4-1.联锁是什么?——"要么全有,要么全无"的霸道总裁

  • 4-2.为什么需要联锁?

  • 4-3.联锁使用三件套

  • 4-4.联锁的硬核原理

  • 4-5.联锁的三大禁忌

  • 5.Redisson读写锁:分布式系统中的"读写分离"高手

  • 5-1.使用姿势

  • 5-2.原理深扒

  • 6.信号量(Semaphore):分布式系统中的"限量入场券"

  • 6-1.使用姿势大全

  • 6-2.原理深扒

  • 7.红锁(RedLock):分布式锁界的“联合国维和部队”

  • 7-1.核心原理

  • 7-2.注意事项

  • 7-3.使用姿势

  • 8.闭锁(CountDownLatch):分布式系统中的"集结号"

  • 8-1.使用姿势

  • 8-2.原理深扒

  • 9.总结


1.简介

各位攻城狮们,你还在使用原生命令来上锁么?看来你还是不够懒,饺子都给你包好了,你非要吃大饼配炒韭菜,快点改善一下“伙食”吧,写代码也要来点幸福感。今天咱们就来聊聊Redisson提供的各种锁,Redisson就像是Redis给Java程序员的一把瑞士军刀,不仅能存数据,还能玩出各种分布式花样。

  • Redis版本:Redis 2.8+,理想版本5.0+(支持 Stream、模块化等高级特性,Redisson 能秀出全部技能)。
  • 架构模式:支持单机、哨兵和集群(集群模式可靠性更高)

2.看门狗

想象你上厕所(获取锁)时带着一只忠心耿耿的阿黄(看门狗)。当你蹲坑时间快到时(锁快要过期),阿黄就会大叫:"主人你还没完事吗?我给你续时间啦!"(自动续期)

2-1.为什么需要这个"狗子"?

  • 防止业务没执行完锁就过期:默认锁30秒过期,但万一你的业务要31秒呢?
  • 避免锁丢失:如果客户端崩溃,看门狗停止续期,锁最终会自动释放
  • 不用手动计算业务时间:再也不用战战兢兢估算业务执行时间了

2-2.工作原理

  • 首次加锁:默认设置锁过期时间30秒
  • 启动看门狗:加锁成功后启动一个定时任务(后台线程)
  • 定期续期:每10秒(过期时间的1/3)检查业务是否完成。未完成:执行expire命令把锁再续30秒;已完成:停止续期。
  • 最终释放:业务完成调用unlock或客户端断开连接时释放

2-3.如何“撸狗子”

Config config = new Config();
config.setLockWatchdogTimeout(30000L); // 单位毫秒,默认就是30秒
// ...
RedissonClient redisson = Redisson.create(config);
// 你也可以在加锁时指定(会覆盖默认值)
lock.lock(60, TimeUnit.SECONDS); // 这时看门狗会按60秒周期续期

3.可重入锁:你的分布式"万能钥匙"

我们都知道Java中ReentrantLock和synchronized都是可重入锁,但都只能用于单机环境的,在分布式环境下,Redisson给我提供了类似体验的可重入锁。

3-1.Redisson可重入锁的魔法

  • 线程安全:不同JVM的相同线程也可重入
  • 自动续期:看门狗机制保活(默认30秒)
  • 公平/非公平:两种模式可选
  • 超时机制:避免无限等待

3-2.使用姿势大全

基础款(阻塞式):
RLock lock = redisson.getLock("orderLock");
lock.lock(); // 一直等到天荒地老
try {
    // 你的核心业务
} finally {
    lock.unlock(); // 一定要放在finally!
}
高级款(尝试获取):
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) { // 最多等3秒,锁30秒自动过期
    try {
        // 业务处理
    } finally {
        lock.unlock();
    }
} else {
    log.warn("获取锁失败,换个姿势再试一次");
}
骚操作款(异步获取):
RFuture lockFuture = lock.lockAsync();
lockFuture.whenComplete((res, ex) -> {
    if (ex == null) {
        try {
            // 异步业务处理
        } finally {
            lock.unlock();
        }
    }
});

3-3.公平锁

  • 排队机制:使用Redis的List结构维护等待队列
  • 订阅发布:通过Redis的pub/sub通知下一个等待者
  • 双重检查:获取锁时检查自己是否在队列头部
RLock fairLock = redisson.getFairLock("myFairLock");
try {
    fairLock.lock();
    // 业务逻辑
} finally {
    fairLock.unlock();
}

3-4.非公平锁

  • 直接竞争:所有线程同时尝试CAS操作
  • 效率优先:没有队列维护开销
  • 可能饥饿:运气差的线程可能长期得不到锁
RLock nonFairLock = redisson.getLock("hotItemLock");
if (nonFairLock.tryLock(50, TimeUnit.MILLISECONDS)) { // 拼手速!
    try {
        // 秒杀业务逻辑
    } finally {
        nonFairLock.unlock();
    }
}

3-5.可重入原理深扒

加锁Lua脚本伪代码:
-- 参数:锁key、锁超时时间、客户端ID+线程ID
if (redis.call('exists', KEYS[1]) == 0) then
    -- 锁不存在,直接获取
    redis.call('hset', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    returnnil;
end;

if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
    -- 重入情况:计数器+1
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    returnnil;
end;

-- 锁被其他线程持有
return redis.call('pttl', KEYS[1]); -- 返回剩余过期时间
解锁Lua脚本伪代码:
-- 参数:锁key、客户端ID+线程ID
if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then
    -- 压根没持有锁
    returnnil;
end;

-- 重入次数-1
local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1);
if (counter > 0) then
    -- 还有重入次数,更新过期时间
    redis.call('pexpire', KEYS[1], 30000);
    return0;
else
    -- 最后一次解锁,删除key
    redis.call('del', KEYS[1]);
    -- 发布解锁消息
    redis.call('publish', KEYS[2], ARGV[2]);
    return1;
end;

4.联锁:分布式锁中的"全家桶套餐"

4-1.联锁是什么?——"要么全有,要么全无"的霸道总裁

想象你要同时约三个女神约会:

  • 女神A:周末有空 ✅
  • 女神B:周末有空 ✅
  • 女神C:周末要加班 ❌ Redisson联锁的做法是:只要有一个拒绝,就取消所有约会!这就是联锁的"All or Nothing"哲学。

4-2.为什么需要联锁?

典型场景:
  • 跨资源事务:需要同时锁定订单、库存、优惠券三个系统
  • 数据一致性:确保多个关联资源同时被保护
  • 避免死锁:防止交叉等待导致的死锁情况

4-3.联锁使用三件套

基本用法:
// 准备三把锁(就像三个女神的联系方式)
RLock lock1 = redisson.getLock("order_lock");
RLock lock2 = redisson.getLock("stock_lock");
RLock lock3 = redisson.getLock("coupon_lock");

// 创建联锁"约会套餐"
RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2, lock3);

try {
    // 尝试同时锁定(约三位女神)
    if (multiLock.tryLock(3, 30, TimeUnit.SECONDS)) {
        // 三位都同意了!开始你的表演
        processOrder();
        updateStock();
        useCoupon();
    } else {
        log.warn("有个女神拒绝了你");
    }
} finally {
    multiLock.unlock(); // 记得送她们回家
}
高阶技巧:
// 动态构造联锁(适合不确定数量的资源)
List locks = resourceIds.stream()
    .map(id -> redisson.getLock("resource_" + id))
    .collect(Collectors.toList());

RedissonMultiLock dynamicLock = new RedissonMultiLock(locks.toArray(new RLock[0]));

4-4.联锁的硬核原理

加锁流程:
  • 顺序加锁:按传入锁的顺序依次尝试获取
  • 失败回滚:任意一个锁获取失败时,释放已获得的所有锁
  • 统一过期时间:所有锁使用相同的过期时间
底层Lua脚本(简化版):
-- 参数:多个锁的KEYS,统一过期时间,线程标识
local failed = false
for i, key inipairs(KEYS) do
    if redis.call('setnx', key, ARGV[2]) == 0then
        failed = true
        break
    end
    redis.call('expire', key, ARGV[1])
end

if failed then
    -- 释放已经获取的锁
    for j = 1, i-1do
        redis.call('del', KEYS[j])
    end
    return0
end
return1

4-5.联锁的三大禁忌

乱序使用(导致死锁):
// 线程1:
multiLock(lockA, lockB).lock();

// 线程2:
multiLock(lockB, lockA).lock(); // 危险!可能死锁

✅ 正确做法:全局统一加锁顺序

混合锁类型:
// 混合普通锁和公平锁
new MultiLock(lock1, fairLock2); // 不推荐

✅ 正确做法:使用相同特性的锁组合

忽略部分锁失败:
if (!multiLock.tryLock()) {
    // 直接返回,不处理部分获取成功的情况
    return; // 危险!
}

✅ 正确做法:确保完全获取或完全失败

5.Redisson读写锁:分布式系统中的"读写分离"高手

也许单机版的ReentrantReadWriteLock你听说过,但是分布式环境下的版本可能很少接触到。

典型场景:
  • 读多写少系统:比如商品详情页(每秒上万次读取,每分钟几次更新)
  • 数据一致性要求:保证读取时不会读到半成品数据
  • 系统性能优化:避免读操作被不必要的串行化

5-1.使用姿势

RReadWriteLock rwLock = redisson.getReadWriteLock("libraryBook_123");

// 读操作(多个线程可同时进入)
rwLock.readLock().lock();
try {
    // 查询数据(安全读取)
    Book book = getBookFromDB(123);
} finally {
    rwLock.readLock().unlock();
}

// 写操作(独占访问)
rwLock.writeLock().lock();
try {
    // 修改数据(安全写入)
    updateBookInDB(123, newVersion);
} finally {
    rwLock.writeLock().unlock();
}

5-2.原理深扒

加读锁Lua脚本(简化):
-- 检查是否可以加读锁(没有写锁或当前线程持有写锁)
if redis.call('hget', KEYS[1], 'mode') == 'write' then
    -- 如果有写锁且不是当前线程持有,则失败
    if redis.call('hexists', KEYS[1], ARGV[2]) == 0 then
        return 0;
    end;
end;

-- 增加读锁计数
redis.call('hincrby', KEYS[1], ARGV[1], 1);
redis.call('pexpire', KEYS[1], ARGV[3]);
return 1;
加写锁Lua脚本(简化):
-- 检查是否已有锁
if redis.call('exists', KEYS[1]) == 1 then
    -- 如果是读模式或有其他写锁
    if redis.call('hget', KEYS[1], 'mode') == 'read' or
       redis.call('hlen', KEYS[1]) > 1 then
        return0;
    end;
    
    -- 如果是当前线程持有的写锁(重入)
    if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then
        redis.call('hincrby', KEYS[1], ARGV[2], 1);
        return1;
    end;
end;

-- 获取写锁
redis.call('hset', KEYS[1], 'mode', 'write');
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[3]);
return1;

6.信号量(Semaphore):分布式系统中的"限量入场券"

同样的味道,Java中Semaphore的分布式版本。

6-1.使用姿势大全

基础用法:
// 获取信号量(初始10个许可)
RSemaphore semaphore = redisson.getSemaphore("apiLimit");
semaphore.trySetPermits(10); // 设置许可数量

// 获取许可(阻塞直到可用)
semaphore.acquire();
try {
    // 执行业务(保证最多10个并发)
    callLimitedAPI();
} finally {
    semaphore.release(); // 记得归还!
}

// 尝试获取(非阻塞)
if (semaphore.tryAcquire()) {
    try {
        // 抢到许可了!
    } finally {
        semaphore.release();
    }
} else {
    log.warn("系统繁忙,请稍后再试");
}
高级技巧:
// 带超时的尝试获取
if (semaphore.tryAcquire(3, 500, TimeUnit.MILLISECONDS)) {
    try {
        // 在500ms内获取到3个许可
        batchProcess();
    } finally {
        semaphore.release(3); // 归还多个许可
    }
}

// 动态调整许可数量
semaphore.addPermits(5); // 增加5个许可(扩容)
semaphore.reducePermits(3); // 减少3个许可(缩容)

6-2.原理深扒

获取许可的Lua脚本(简化):
-- 参数:信号量key、请求许可数
local value = redis.call('get', KEYS[1])
if value >= ARGV[1] then
    return redis.call('decrby', KEYS[1], ARGV[1])
else
    return -1
end
释放许可的Lua脚本(简化):
-- 参数:信号量key、释放许可数
return redis.call('incrby', KEYS[1], ARGV[1])

7.红锁(RedLock):分布式锁界的“联合国维和部队”

RedLock 的诞生是为了对抗单点 Redis 挂掉后锁失效的问题。它的目标就是:“即使部分节点挂了,我也要稳如老狗”。

7-1.核心原理

Redisson 的 RedLock 实现来源于 Redis 作者 antirez 提出的 Redlock 算法。流程如下:

  • 准备多个独立的 Redis 节点(注意:是互相独立的,不是主从复制结构)。
  • 客户端依次向这些节点尝试加锁(使用 SET NX PX 命令)。
  • 记录耗时:加锁操作总共不能超过锁过期时间的 1/2(比如设置锁有效期 10 秒,那就必须 5 秒内搞定加锁)。
  • 加锁成功节点超过半数(N/2 + 1)视为成功。
  • 若失败,立刻释放所有加锁成功的节点,以避免资源死锁。
  • 释放锁时,同样要向所有节点发送 unlock 操作。

7-2.注意事项

  • RedLock 是为多主 Redis 实例准备的,不是给 Redis Cluster 用的。
  • 你得维护多个彼此独立的 Redis 实例,部署和运维成本更高。
  • RedLock 的“强一致性”并非线性一致性,它只是通过多点确认提升“高可用性”。

7-3.使用姿势

// 准备多个独立的RLock实例
RLock lock1 = redissonClient1.getLock("lock");
RLock lock2 = redissonClient2.getLock("lock"); 
RLock lock3 = redissonClient3.getLock("lock");

// 构造红锁(建议奇数个,通常3/5个)
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);

try {
    // 尝试获取锁(等待时间100s,锁持有时间30s)
    boolean locked = redLock.tryLock(100, 30, TimeUnit.SECONDS);
    if (locked) {
        // 执行业务逻辑
        doCriticalWork();
    }
} finally {
    redLock.unlock();
}

8.闭锁(CountDownLatch):分布式系统中的"集结号"

一样是Java的CountDownLatch的分布式版本,用法也是基本一样。

8-1.使用姿势

// 主协调节点(教官)
RCountDownLatch latch = redisson.getCountDownLatch("batchTaskLatch");
latch.trySetCount(5); // 需要等待5个任务

// 工作节点(学员)
RCountDownLatch workerLatch = redisson.getCountDownLatch("batchTaskLatch");
workerLatch.countDown(); // 完成任务时调用

// 主节点等待(在另一个线程/JVM)
latch.await(); // 阻塞直到计数器归零
System.out.println("所有任务已完成!");

8-2.原理深扒

关键操作伪代码:
-- countDown操作
local remaining = redis.call('decr', KEYS[1])
if remaining <= 0then
    redis.call('publish', KEYS[2], '0') -- 通知所有等待者
    redis.call('del', KEYS[1]) -- 清理计数器
end
return remaining

-- await操作
local count = redis.call('get', KEYS[1])
if count == falseortonumber(count) <= 0then
    return1-- 已经完成
end
return0-- 需要继续等待

9.总结

Redisson 提供了丰富的分布式锁实现,适用于各种分布式场景,使用体验更好,选择锁类型时应根据具体业务场景和需求来决定,同时要注意锁的粒度和持有时间,避免分布式死锁和性能问题。


关于作者,高宏杰,转转门店技术部研发工程师。

本文地址:https://www.yitenyun.com/267.html

搜索文章

Tags

数据库 API FastAPI Calcite 电商系统 MySQL 数据同步 ACK 双主架构 循环复制 Web 应用 异步数据库 序列 核心机制 生命周期 Deepseek 宝塔面板 Linux宝塔 Docker JumpServer JumpServer安装 堡垒机安装 Linux安装JumpServer esxi esxi6 root密码不对 无法登录 web无法登录 Windows Windows server net3.5 .NET 安装出错 宝塔面板打不开 宝塔面板无法访问 SSL 堡垒机 跳板机 HTTPS Windows宝塔 Mysql重置密码 无法访问宝塔面板 HTTPS加密 查看硬件 Linux查看硬件 Linux查看CPU Linux查看内存 ES 协同 修改DNS Centos7如何修改DNS scp Linux的scp怎么用 scp上传 scp下载 scp命令 防火墙 服务器 黑客 Serverless 无服务器 语言 存储 Oracle 处理机制 Spring SQL 动态查询 Linux 安全 网络架构 工具 网络配置 加密 场景 MySQL 9.3 开源 PostgreSQL 存储引擎 RocketMQ 长轮询 配置 Canal Rsync 架构 InnoDB 缓存方案 缓存架构 缓存穿透 HexHub 信息化 智能运维 响应模型 日志文件 MIXED 3 线上 库存 预扣 监控 聚簇 非聚簇 索引 B+Tree ID 字段 数据 业务 AI 助手 数据库锁 GreatSQL Hash 字段 分库 分表 云原生 单点故障 DBMS 管理系统 Redis 自定义序列化 优化 万能公式 Redis 8.0 ​Redis 机器学习 推荐模型 SVM Embedding SpringAI openHalo OB 单机版 数据集成工具 sqlmock PostGIS 系统 SQLark 虚拟服务器 虚拟机 内存 SQLite Redka SQLite-Web 数据库管理工具 自动重启 运维 同城 双活 缓存 sftp 服务器 参数 共享锁 RDB AOF 分页查询 Netstat Linux 服务器 端口 排行榜 排序 查询 prometheus Alert 向量数据库 大模型 不宕机 容器化 分布式架构 分布式锁​ 聚簇索引 非聚簇索引 • 索引 • 数据库 Entity 开发 技术 Testcloud 云端自动化 EasyExcel MySQL8 AIOPS IT 数据备份 Postgres OTel Iceberg MongoDB 容器 数据类型 OAuth2 Token StarRocks 数据仓库 Doris SeaTunnel 人工智能 推荐系统 分页 数据结构 Python Web LRU Milvus IT运维 连接控制 机制 Caffeine CP 部署 向量库 悲观锁 乐观锁 池化技术 连接池 崖山 新版本 高可用 Ftp redo log 重做日志 MVCC 事务隔离 磁盘架构 流量 MCP 开放协议 电商 mini-redis INCR指令 单线程 线程 速度 服务器中毒 Web 接口 字典 QPS 高并发 原子性 对象 微软 SQL Server AI功能 数据脱敏 加密算法 窗口 函数 R2DBC 双引擎 RAG HelixDB 频繁 Codis 主库 Order 网络 Crash 代码 ZODB SSH 引擎 性能 List 类型 Pottery dbt 数据转换工具 1 PG DBA 工具链 优化器 模型 InfluxDB 传统数据库 向量化 发件箱模式 意向锁 记录锁 事务同步 UUIDv7 主键 网络故障 Redisson 锁芯 仪表盘 LLM 订单 线程安全 JOIN INSERT COMPACT Undo Log 连接数