You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Redis指定key分布式锁 保障多Java微服务互斥访问

针对跨微服务Redis单key互斥操作的实现方案

你的场景本质是跨10个JVM进程实现对单个Redis key的互斥访问,即同一时间只允许一个服务实例操作该key,其余实例阻塞等待,以下是生产环境验证过的可行方案:

方案1:Redisson分布式锁(生产环境首选)

这是Java生态下最稳妥的实现,不需要自己处理锁的各种边界问题,开箱即用:

  • 引入Redisson依赖,配置好你现有Redis实例的连接信息即可,不需要额外部署其他组件
  • 给你要互斥访问的业务key绑定一个独立的锁key,比如业务key为global:common:config,对应的锁key设为lock:global:common:config即可,不要直接把业务key当锁key用,避免和正常读写逻辑冲突
  • 核心特性完全匹配你的需求:
    • 调用lock()方法加锁时,未拿到锁的实例会自动阻塞重试,直到持有锁的实例释放锁后抢到执行权
    • 默认自带Watch Dog看门狗续期机制,只要当前实例没执行完操作,就会自动给锁续期,不会出现业务没跑完锁提前过期的并发问题
    • 加锁、释放锁都是原子操作,释放时会校验锁持有者身份,不会出现A服务加的锁被B服务误删的问题
  • 最简代码示例:
// 从Spring容器中注入配置好的RedissonClient实例
RLock commonKeyLock = redissonClient.getLock("lock:你要互斥访问的特定key名称");
try {
    // 阻塞等待,成功拿到锁才会执行后续逻辑
    commonKeyLock.lock();
    // 此处编写对该特定key的所有查询、修改、删除操作逻辑
} finally {
    // 无论业务逻辑是否抛异常,最终都要释放自己持有的锁
    if (commonKeyLock.isHeldByCurrentThread()) {
        commonKeyLock.unlock();
    }
}

方案2:基于Redis原生命令手写简易阻塞锁(轻量场景适用)

如果不想额外引入Redisson依赖,也可以基于Redis基础命令自己实现锁逻辑,但要严格规避竞态问题:

  • 加锁必须使用原子命令:SET 锁key 客户端唯一标识 NX EX 锁过期时间
    • NX参数保证只有key不存在时才能设置成功,即同一时间只有一个客户端能拿到锁
    • 客户端唯一标识可以用UUID+当前服务实例ID+线程ID生成,用来区分锁是哪个实例加的
    • 过期时间要根据你操作key的最大耗时合理设置,避免服务宕机导致锁永久不释放形成死锁
  • 未拿到锁的客户端采用固定间隔自旋重试,比如每100ms执行一次加锁命令,直到加锁成功再往下执行业务逻辑,不要无间隔高频自旋打满Redis CPU
  • 释放锁必须用LUA脚本保证原子性,只有当锁的value和当前客户端的唯一标识一致时才删除锁,避免误删其他实例持有的锁,脚本内容如下:
if redis.call('GET', KEYS[1]) == ARGV[1] then
    return redis.call('DEL', KEYS[1])
else
    return 0
end
  • 该方案的缺陷是没有自动续期能力,如果业务操作耗时超过设置的锁过期时间,会提前释放锁导致并发问题,仅适合操作耗时固定、执行时间短的场景。

落地注意事项

  • 禁止使用GET判断key不存在再SET的非原子操作实现加锁,高并发下必然出现多个实例同时拿到锁的竞态问题
  • 锁的范围要尽可能小,只包裹对该特定key的操作逻辑,不要把无关的数据库查询、远程调用等耗时操作包在锁内,避免锁持有时间过长拖慢所有服务的访问效率
  • 单Redis实例/常规主从集群场景下,普通单节点分布式锁已经足够满足可靠性要求,不需要盲目使用复杂度更高的红锁。

内容的提问来源于stack exchange,提问作者yashjn72

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 15:42:28