news 2026/9/14 3:03:44

Redisson 如何用 RFencedLock 获取围栏令牌保护外部资源

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redisson 如何用 RFencedLock 获取围栏令牌保护外部资源

Redisson 如何用 RFencedLock 获取围栏令牌保护外部资源

【免费下载链接】redissonRedisson: Valkey & Redis Java Client and Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Valkey and Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Spring, Tomcat, Scheduler, JCache API, Hibernate, RPC, local cache..项目地址: https://gitcode.com/GitHub_Trending/re/redisson

当你用分布式锁保护一个外部资源(数据库、对象存储、下游系统)的写入时,会面临一个普通RLock无法覆盖的问题:持锁的客户端可能因长时间 GC 暂停或网络故障被挂起,期间锁过期、其他客户端拿到锁并写入;等第一个客户端恢复后,它仍然以为自己持有着锁,继续写入就会造成脏写。Redisson 的RFencedLock就是为这个场景设计的:它在每次成功获取锁时返回一个单调递增的 fencing token,被保护的资源端记录自己接受过的最高令牌,凡是携带更低令牌的操作一律拒绝。这样即使持锁客户端"失忆"复活,它的写入也会被资源端挡下(fenced out)。

适用前提:Java 项目已通过 Redisson 客户端连接到 Valkey 或 Redis;被锁保护的写入方是能够接收并校验 token 的外部系统。如果锁只是用于进程间/节点间的协调,不涉及外部系统强制校验,文档给出的建议是直接用普通 RLock 即可。

为什么普通锁不够:令牌机制解决的场景

Redisson 的分布式锁是协作式(advisory)的,安全性依赖部署拓扑。文档用这样一个序列说明故障窗口:客户端 A 在主节点上成功获取锁后,主节点在锁写入尚未同步到副本时宕机,副本被提升为新主且没有该锁的记录,客户端 B 随即成功获取"同一把锁",两个客户端都以为自己持锁。

Redisson 默认通过副本同步检查(checkLockSyncedSlaves,默认开启;等待超时slavesSyncTimeout默认 1000 毫秒,超时后释放锁并让本次获取失败)来收窄这个窗口。而 fencing token 解决的是剩余风险:把"我认为我持锁"变成资源端可以核验的事实。RFencedLock每次获取都返回一个新令牌,资源端只接受不低于已见最高值的令牌。文档同时指出,已弃用的RedLockRLock(配合副本同步检查)和RFencedLock(需要围栏令牌时)取代。

准备:创建客户端并获取 RFencedLock

先在项目中添加 Redisson 依赖(源文档中版本号以xVERSIONx占位,替换为你实际使用的版本):

<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>xVERSIONx</version> </dependency>

然后按 configuration.md 的 Single mode 一节创建客户端(以单节点为例,地址替换为你自己的 Valkey/Redis 地址):

Config config = new Config(); // use "redis://" for Redis connection // use "valkey://" for Valkey connection // use "rediss://" for Redis SSL connection config.useSingleServer().setAddress("redis://myredisserver:6379"); RedissonClient redisson = Redisson.create(config);

RedissonClient是线程安全的,创建一次后在应用内复用即可,应用退出时调用redisson.shutdown()

获取围栏锁对象:

RFencedLock lock = redisson.getFencedLock("myLock");

getFencedLock(name)在 RedissonClient 中的说明为:实现的是 non-fair 锁,不保证线程间的获取顺序。

主路径:获取锁、拿到令牌、交给资源端校验

同步 API 的完整流程如下,取自 locks-and-synchronizers.md 的 Fenced Lock 专节:

RFencedLock lock = redisson.getFencedLock("myLock"); // 阻塞获取锁,成功时返回递增后的 fencing token Long token = lock.lockAndGetToken(); try { // 将 token 传给被保护的资源,资源端必须拒绝任何 // token 低于其已接受最高值的写入 storage.write(data, token); } finally { lock.unlock(); }

要点逐步说明:

  1. lockAndGetToken()在锁被占用时阻塞等待,直到获取成功,返回Long类型的令牌;无需判空。

  2. 被保护资源的校验规则由文档明确给出:资源端"记录已见最高令牌,拒绝携带更低令牌的操作",即校验token >= 已接受的最大值,不满足则拒绝本次操作。文档示例中这一逻辑以注释形式给出(check if token >= old token),下面是一个按该规则实现的最小示例,具体存储操作由你自己的系统完成:

    // 示例:资源端对令牌做单调性校验(规则来自文档,存储操作自行替换) long lastAcceptedToken = 0; void write(Object data, long token) { if (token < lastAcceptedToken) { // 令牌低于已接受最大值:持锁方已过期,拒绝操作 throw new IllegalStateException("stale fencing token: " + token); } lastAcceptedToken = token; // ... 这里执行真实的写存储操作 }
  3. unlock()放在finally中,确保异常路径也能释放锁。注意只有获取锁的线程可以解锁,否则抛出IllegalMonitorStateException

判断本次获取是否成功:lockAndGetToken()返回非空令牌即成功;改用tryLockAndGetToken(...)时,返回null表示在等待窗口内没有拿到锁,此时不能执行受保护操作,也无需调用unlock()

验证令牌行为:单调递增

仓库自带测试 RedissonFencedLockTest 展示了可核对的验证方式:对同一把锁连续获取,令牌依次为 1、2、3、4、5;同时验证了普通lock()之后调用getToken()能拿到当前值(该测试中为 3、4),tryLockAndGetToken()成功时返回令牌 5。据此可以做两项检查:

  • 递增检查:同一把锁连续获取两次,第二次lockAndGetToken()返回的令牌应大于第一次;getToken()在不获取锁的情况下返回当前令牌。
  • 围栏检查:用一个低于资源端已接受最高值的令牌调用写入,资源端应拒绝该操作——这正是被挂起后复活的过期持锁方会被挡下的效果。

可选的获取变体

文档为同一把锁提供了多个获取入口,按场景选用:

带租约时间,到期自动释放(对应leaseTime参数):

// 获取锁并在 10 秒后自动解锁 Long token = lock.lockAndGetToken(10, TimeUnit.SECONDS);

限时尝试获取,拿不到就放弃:

// 最多等待 100 秒,持有 10 秒后自动释放;未获取到时返回 null Long token = lock.tryLockAndGetToken(100, 10, TimeUnit.SECONDS); if (token != null) { try { // 执行带令牌校验的资源写入 ... } finally { lock.unlock(); } }

异步 API 下返回RFuture<Long>,方法名带Async后缀;文档示例中unlockAsync(threadId)需要显式传入线程 id:

RFencedLock lock = redisson.getFencedLock("myLock"); RFuture<Long> lockFuture = lock.tryLockAndGetTokenAsync(100, 10, TimeUnit.SECONDS); long threadId = Thread.currentThread().getId(); lockFuture.whenComplete((token, exception) -> { if (token != null) { try { // 执行带令牌校验的资源写入 ... } finally { lock.unlockAsync(threadId); } } });

Reactive 与 RxJava3 的对应方法为lockAndGetToken()/lockAndGetToken(leaseTime, unit)/tryLockAndGetToken(waitTime, leaseTime, unit),返回Mono<Long>/Single<Long>。文档对锁类对象有一段通用提示:在异步、Reactive、RxJava3 API 中,一次逻辑操作可能跨多个线程执行,而锁的归属绑定线程 id,因此要捕获Thread.currentThread().getId()并把同一个threadId传给locktryLockunlock调用。

行为细节与限制

  • 可重入、非公平RFencedLock是可重入锁(接口 RFencedLock 的说明),但不保证跨线程的获取顺序。
  • 解锁归属:只有所属线程能解锁,否则抛IllegalMonitorStateException
  • 看门狗:持锁的 Redisson 实例崩溃时锁可能一直处于已获取状态;看门狗在持有方存活期间续期锁的过期时间,默认 30 秒,可通过Config.lockWatchdogTimeout修改。
  • 副本同步检查checkLockSyncedSlaves(默认开启)与slavesSyncTimeout(默认 1000 毫秒)作用于RLockRSemaphoreRPermitExpirableSemaphoreRFencedLock继承RLock,共享同一套锁机制。开启检查后,若必需副本未在规定时间内确认同步,Redisson 会释放锁并让本次获取失败,客户端不会保留一把可能因故障转移丢失的锁。
  • 适用边界:文档明确 fenced lock 用于"锁保护的是能够强制校验 token 的外部系统写入";纯进程内协调用普通RLock。令牌本身只是数值,Redisson 不会替你调用资源端做校验,拒绝低令牌的逻辑必须由被保护方实现。

下一步

令牌获取与校验的完整方法签名见 RFencedLock.java(含lockAndGetTokentryLockAndGetTokengetToken各重载),锁类型选型对比表见 locks-and-synchronizers.md 开头的 "Choosing a lock",客户端创建与连接模式见 getting-started.md。

【免费下载链接】redissonRedisson: Valkey & Redis Java Client and Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Valkey and Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Spring, Tomcat, Scheduler, JCache API, Hibernate, RPC, local cache..项目地址: https://gitcode.com/GitHub_Trending/re/redisson

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 3:03:00

HTML5 PC静态模板:高性能单页网站搭建与优化指南

简介&#xff1a;这是一份专为PC端设计的产品介绍类单页静态网页模板&#xff0c;面向网页设计初学者、中小企业市场人员及个人开发者&#xff0c;解决快速搭建专业级产品展示页面的需求。模板基于HTML5标准开发&#xff0c;纯静态实现&#xff0c;无需后端支持&#xff0c;兼顾…

作者头像 李华
网站建设 2026/9/14 3:02:29

为什么七大数学难题,卡在了同一个地方?

为什么七大数学难题&#xff0c;卡在了同一个地方&#xff1f;你站在一个漆黑的房间里&#xff0c;手里只有一支手电筒。你照向墙壁&#xff0c;看到墙上有一个影子。你研究了很久&#xff0c;写下了关于这个影子的所有规律&#xff0c;写了几百篇论文&#xff0c;拿了好几个奖…

作者头像 李华
网站建设 2026/9/14 3:01:49

Megatron-LM 中基于 FP8 精度的 Llama-3-8B 分布式训练实战指南

Megatron-LM 中基于 FP8 精度的 Llama-3-8B 分布式训练实战指南 【免费下载链接】Megatron-LM Ongoing research training transformer models at scale 项目地址: https://gitcode.com/GitHub_Trending/me/Megatron-LM 本篇技术指南以 examples/llama/README.md 为核心…

作者头像 李华
网站建设 2026/9/14 3:01:41

微网虚拟电厂MILP调度:EV与需求响应协同建模

简介&#xff1a;本资源聚焦含多种需求响应与电动汽车参与的微网虚拟电厂日前优化调度问题&#xff0c;面向电力系统、能源互联网方向的研究生、科研人员及工程技术人员&#xff0c;解决分布式资源协同建模与经济性调度策略设计难题。压缩包共6个文件&#xff08;2.57MB&#x…

作者头像 李华
网站建设 2026/9/14 3:00:39

HTML5静态单页模板:零部署PC产品页实战指南

简介&#xff1a;这是一份专为产品展示场景设计的HTML5单页静态网页模板&#xff0c;面向网页设计初学者、中小企业市场人员及无需后端开发的快速建站需求者&#xff0c;解决产品介绍页面快速搭建、视觉统一与跨屏适配问题。压缩包共146个文件&#xff0c;含3个结构清晰的HTML主…

作者头像 李华
网站建设 2026/9/14 2:59:36

Dify vs 讯飞星辰Agent:生产级智能体平台选型实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华