Nacos 如何使用 LockService 获取与释放带租约的分布式互斥锁?

原创2026-09-09 23:18:431,713 阅读
文章标签:后端微服务配置中心服务注册发现云原生

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 版本不支持该能力时,锁操作无法完成。

准备条件

  1. 一个已运行的 Nacos 服务器。仓库内锁集成测试基类 BaseLockITCase.java 使用的默认地址是 127.0.0.1:8848,账号密码为 nacos/nacos。实际使用时替换为你自己的服务器地址与账号。
  2. Java 客户端依赖 nacos-client。NacosLockFactory 通过反射加载 com.alibaba.nacos.client.lock.NacosLockService(见 NacosLockFactory.java),该实现位于 client 模块,锁集成测试 lock-test 模块 也正是依赖 nacos-client 与 nacos-lock 运行。
  3. 如果要在仓库内运行官方锁集成测试验证,按 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() 释放客户端资源,你的客户端程序退出前也应执行该操作。
登录后查看全文
nacos