Nacos 如何使用 LockService 获取与释放带租约的分布式互斥锁?
Nacos 如何使用 LockService 获取与释放带租约的分布式互斥锁?
Nacos 3.0 引入了分布式锁这一实验性能力,服务端由 lock 模块承担,Java 客户端通过 LockService 加锁、解锁。本文的目标很具体:在已运行的 Nacos 服务器基础上,用 Java 客户端对一把带租约(lease)的分布式互斥锁执行获取与释放,并判断每一步的返回结果是否符合预期。
需要注意的前提:
- 分布式锁是 Nacos 3.0 引入的实验性能力,后续版本可能调整资源模型、收紧安全语义,甚至移除该模块,依赖它之前应按 lock 规范第 2 节的兼容预期评估。
- 锁操作走 gRPC 运行时链路,不是 HTTP 管理 API,因此不能通过 HTTP 接口直接加锁。
- 规范第 7 节要求客户端在发送锁操作前检查服务端是否支持
SERVER_DISTRIBUTED_LOCK能力;连接的 Nacos 版本不支持该能力时,锁操作无法完成。
准备条件
- 一个已运行的 Nacos 服务器。仓库内锁集成测试基类 BaseLockITCase.java 使用的默认地址是
127.0.0.1:8848,账号密码为nacos/nacos。实际使用时替换为你自己的服务器地址与账号。 - Java 客户端依赖
nacos-client。NacosLockFactory通过反射加载com.alibaba.nacos.client.lock.NacosLockService(见 NacosLockFactory.java),该实现位于 client 模块,锁集成测试 lock-test 模块 也正是依赖nacos-client与nacos-lock运行。 - 如果要在仓库内运行官方锁集成测试验证,按 BUILDING 文档需要 JDK 17+ 与 Maven 3.6.3+。
服务端侧有两个影响租约的配置项(见 lock 规范第 5 节,常量定义在 PropertiesConstant.java):
| 配置项 | 当前值 | 作用 |
|---|---|---|
nacos.lock.default_expire_time |
30000 毫秒 |
请求租约时长为负数时使用的默认租约 |
nacos.lock.max_expire_time |
1800000 毫秒 |
请求租约时长的上限 |
过期时间按服务端时间计算(endTime = serverCurrentTimeMillis + leaseDurationMillis),客户端本地时钟不决定锁的有效窗口;过期检查是惰性的,在加锁、解锁、清理或快照路径访问锁状态时触发。
创建 LockService
LockService 的创建入口有两个等价写法:NacosLockFactory.createLockService(Properties),或者经 NacosFactory 的静态方法(内部转调前者):
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.lock.LockService;
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848"); // 替换为你的 Nacos 地址
properties.setProperty(PropertyKeyConst.USERNAME, "nacos"); // 替换为你的账号
properties.setProperty(PropertyKeyConst.PASSWORD, "nacos"); // 替换为你的密码
LockService lockService = NacosFactory.createLockService(properties);
客户端连接建立后,锁操作即可通过 gRPC 发送。
构建带租约的 LockInstance
锁资源身份为 lockType -> key,expiredTime 字段虽然叫“过期时间”,但按 lock 规范第 3 节的说明,服务端把它解释为租约时长(毫秒)而不是绝对时间戳。
lockType 的合法取值定义在 LockConstants.java:
| 常量 | 值 |
|---|---|
LockConstants.NACOS_LOCK_TYPE |
NACOS_LOCK |
LockConstants.REENTRANT_LOCK_TYPE |
REENTRANT |
LockConstants.NON_REENTRANT_LOCK_TYPE |
NON_REENTRANT |
服务端 LockRequestHandler 会校验该字段,不属于上面三种值的请求会被拒绝。集成测试中的构造方式(BaseLockITCase.java 的 createLockInstance 方法):
import com.alibaba.nacos.api.lock.model.LockInstance;
import com.alibaba.nacos.api.lock.common.LockConstants;
LockInstance lock = new LockInstance();
lock.setKey("order-create-2026"); // 用户定义的锁名称
lock.setLockType(LockConstants.REENTRANT_LOCK_TYPE); // 锁类型
lock.setOwner(UUID.randomUUID().toString()); // 持有者标识
lock.setExpiredTime(30000L); // 租约时长 30 秒
owner 用于解锁、续租时的持有者校验,测试代码统一使用随机 UUID。params 字段是可选的可序列化扩展参数,内置互斥锁不解析它。
加锁与解锁
LockService 对外暴露的加锁、解锁方法自 3.0.0 起可用,返回 Boolean 成功值:
// 加锁
Boolean locked = lockService.lock(lock);
if (locked) {
// 临界区业务逻辑
// ...
// 解锁
Boolean released = lockService.unLock(lock);
}
也可以直接调用 LockInstance 上的便捷方法:lock.lock(lockService) 转调 remoteTryLock,lock.unLock(lockService) 转调 remoteReleaseLock(见 LockInstance.java)。
加锁与解锁结果的判断依据 lock 规范第 4 节的语义:
- 锁为空,或已有锁已过期:加锁成功(
lock返回true); - 已有锁未过期且被占用:加锁失败(返回
false); - 解锁把锁从占用状态切换为空状态;锁为空、已过期时再次解锁会返回
false。
BasicLockITCase.java 的断言给出了逐条可核对的验证标准:
| 用例 | 操作 | 预期结果 |
|---|---|---|
| IT-001 | 对空锁加锁后解锁 | lock 与 unLock 均返回 true |
| IT-002 | 同一锁连续解锁两次 | 第一次 true,第二次 false |
| IT-007 | 客户端 A 持锁期间,客户端 B 对同一 key 加锁 |
B 的 lock 返回 false;A 解锁后 B 再次加锁返回 true |
| IT-004 | 加锁时设置 expiredTime=2000L,等待 3 秒后另一客户端加锁 |
另一客户端 lock 返回 true(过期锁可被抢占) |
| IT-008 | 锁过期后再对原 LockInstance 解锁 |
unLock 返回 false |
如果你的代码中出现“加锁失败但没有人在持锁”的现象,优先怀疑租约已到期:按上面 IT-004 的模式用一个不同 owner 的 LockInstance 对同一 key 加锁,能成功即说明原锁已过期。
可选:续租(客户端 3.3.0+)
LockService 的 renew 方法标注 @Since("3.3.0"),注释描述为 “Renew lock lease time (watchdog heartbeat)”,用于在持有期间延长租约。用法(对应集成测试 IT-005):
lock.setExpiredTime(5000L); // 新租约 5 秒
Boolean renewed = lockService.renew(lock); // true 表示续租成功
集成测试验证了:续租后在原始租约到期点,其他客户端加锁仍返回 false(锁还有效);续租时长过去后其他客户端加锁返回 true。同时 IT-006 断言:客户端 B 使用不同的 owner 对 A 的锁续租,renew 返回 false。
用官方集成测试验证
仓库提供了完整的锁集成测试模块 test/lock-test。前提是先启动一台 Nacos 服务器(测试基类固定连接 127.0.0.1:8848 并使用 nacos/nacos 账号)。入口是 Makefile 的 run-it-tests 目标,它会执行 cd test && mvn clean verify -Pintegration-test,运行 test/ 下的全部集成测试,其中包含 BasicLockITCase(IT-001 至 IT-008)、并发锁、等待队列锁等用例。
限制与已知边界
- 解锁持有者校验存在文档与测试口径差异:lock 规范第 4 节写明“内置实现当前不校验解锁请求是否来自锁持有者”,即能发送同一
lockType + key解锁请求的客户端可以释放该锁;而 BasicLockITCase 中REENTRANT类型下,不同owner的解锁与续租断言为失败(IT-003、IT-006)。规范说明针对内置锁类型,测试针对REENTRANT类型,两者均属于实验性范围,实际行为以你使用的锁类型和 Nacos 版本为准。 - 鉴权尚未补齐:规范第 9 节指出 lock gRPC handler 中仍有
TODO Support auth标记,锁请求尚无完整的 owner token 校验;在安全契约补齐前,应把分布式锁视为可信客户端场景下的实验能力。 - CP 一致性:锁状态变更通过 CP 协议组提交,锁状态由进程内存加 CP 日志和快照(
nacos_lock.zip)承载,不是关系型数据库资源;CP 路径无法提交写入时,加锁或解锁会直接失败,而不是产生分裂的锁持有状态(规范第 6 节)。 - 没有管理 API 与查询能力:该模块不提供用于锁列表、迁移或手动修改状态的大范围 HTTP 管理 API;锁身份目前是全局的
lockType -> key,不包含namespaceId、groupName等租户维度。 - 资源收尾:测试基类在
@AfterAll中调用lockService.shutdown()释放客户端资源,你的客户端程序退出前也应执行该操作。