如何用Redis实现分布式锁?RedLock算法的核心思想?Redisson的看门狗机制原理?

news2025/5/15 11:43:20

一、Redis分布式锁基础实现

public class RedisDistributedLock {
    private JedisPool jedisPool;
    private String lockKey;
    private String clientId;
    private int expireTime = 30; // 默认30秒

    public boolean tryLock() {
        try (Jedis jedis = jedisPool.getResource()) {
            // NX表示不存在时设置,PX设置过期时间(毫秒)
            String result = jedis.set(lockKey, clientId, SetParams.setParams().nx().px(expireTime * 1000));
            return "OK".equals(result);
        }
    }

    public void unlock() {
        try (Jedis jedis = jedisPool.getResource()) {
            // 使用Lua脚本保证原子性
            String script = 
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
            jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(clientId));
        }
    }
}

关键点:

  1. 使用SET NX PX命令保证原子性
  • 单命令原子性:Redis服务器单线程顺序执行命令
  • 仅包含SET操作:仅完成键值设置和过期时间配置
  • 无逻辑判断:仅判断键是否存在(NX特性)
  1. 客户端唯一标识(clientId)防止误删
  2. Lua脚本保证解锁操作的原子性
  • 多命令原子性:组合GET、DEL等命令的复合操作
  • 包含业务逻辑:实现"比较后删除"的CAS(Compare And Set)操作
  • 支持复杂流程:可包含条件判断、循环等逻辑

缺陷:
如果锁存在A redis节点,然后B是A的从库,服务先获取A节点的redis key锁,如果A网络波动的时候的时候,主从切换,B节点升级为主节点,这个时候另一个服务获取B节点的相同的redis key,这种情况就发生脑裂了。

二、RedLock算法核心思想

RedLock算法由Redis作者提出,主要解决单点故障问题:

  1. 多节点部署:使用5个(奇数)独立的Redis节点
  2. 顺序获取:客户端依次向所有节点申请锁
  3. 成功条件:获得超过半数的锁(3个)
  4. 耗时计算:总耗时应小于锁的TTL时间
  5. 失败释放:失败时需要释放所有已获得的锁

三、Redisson看门狗机制原理

public class RedissonWatchdogExample {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        
        RedissonClient redisson = Redisson.create(config);
        RLock lock = redisson.getLock("myLock");

        try {
            // 默认30秒过期,看门狗自动续期
            lock.lock();
            // 业务逻辑执行时间可能超过30秒
            Thread.sleep(40000); 
        } finally {
            lock.unlock();
        }
    }
}

集群防止脑裂

Redisson 实现分布式锁的核心机制和集群脑裂防护原理如下:

1. 基础锁实现原理
-- Redis 原子操作脚本
if redis.call('exists', KEYS[1]) == 0 then
    redis.call('hset', KEYS[1], ARGV[2], 1)
    redis.call('pexpire', KEYS[1], ARGV[1])
    return nil
end
2. 集群模式防护机制
// RedissonMultiLock 集群锁实现
List<RLock> locks = new ArrayList<>();
locks.add(redissonClient1.getLock("lock1"));
locks.add(redissonClient2.getLock("lock2"));
RLock multiLock = new RedissonMultiLock(locks.toArray(new RLock[0]));

关键实现要点:

1. 多节点提交机制
  • 需要 N/2+1 个节点成功获取锁才算有效
  • 使用异步线程维持锁心跳(Watchdog 机制)
// Watchdog 线程实现(伪代码)
private void scheduleExpirationRenewal() {
    if (expirationRenewalMap.putIfAbsent(lockName, newTimeout) == null) {
        // 每 10 秒续期一次
        internalLockLeaseTime / 3 周期执行
    }
}
2. 脑裂防护策略
  • 同步延迟控制:主从同步超时时间 > 锁过期时间
  • 多数派原则:成功获取锁的节点数 > 集群半数节点
  • 故障转移阻断:当节点失联时自动启动锁失效倒计时
3. 异常处理机制
// 锁释放时的集群同步
public void unlock() {
    for (RLock lock : locks) {
        if (lock.isHeldByCurrentThread()) {
            lock.unlockAsync();
        }
    }
}

集群模式下重要参数配置建议:

# redisson.yaml
clusterServersConfig:
  nodeAddresses:
    - "redis://127.0.0.1:7000"
    - "redis://127.0.0.1:7001"
  scanInterval: 1000
  retryAttempts: 3
  retryInterval: 500
  slaveConnectionMinimumIdleSize: 8
  failedSlaveReconnectionInterval: 30000

该实现通过以下方式保证脑裂场景下的数据一致性:

  1. 使用 Raft 式多数派提交协议
  2. 网络分区时自动降级为只读模式
  3. 主节点切换后新主节点会等待旧主节点锁超时
  4. 客户端自动检测集群拓扑变化并重建连接
    看门狗机制关键点:
  5. 后台线程:每10秒检查锁状态
  6. 自动续期:当业务未完成时,将过期时间重置为30秒
  7. 客户端存活判断:只有客户端保持活跃才会续期
  8. 默认配置:lockWatchdogTimeout=30秒

四、完整方案对比

方案优点缺点
基础Redis锁实现简单单点故障风险
RedLock算法高可用性实现复杂、性能损耗
Redisson实现自动续期、可重入锁、多种锁类型需要维护客户端连接

实际建议:

  1. 单节点场景使用Redisson基础锁
  2. 高可用场景使用Redisson+Redis Cluster
  3. 极端可靠性需求使用RedLock算法

生产环境注意事项:

  1. 合理设置超时时间(业务平均耗时 * 2)
  2. 监控锁等待时间和获取次数
  3. 为不同业务使用不同的锁前缀
  4. 做好锁等待超时的异常处理

竞品分析

分布式锁实现方案对比及优劣势分析

一、ZooKeeper 实现方案
// 使用Curator框架示例
public class ZkDistributedLock {
    private CuratorFramework client;
    private InterProcessMutex lock;
    
    public boolean tryLock(String lockPath) throws Exception {
        lock = new InterProcessMutex(client, lockPath);
        return lock.acquire(3, TimeUnit.SECONDS); // 3秒获取超时
    }
    
    public void unlock() throws Exception {
        if (lock != null) {
            lock.release();
        }
    }
}

核心原理
通过创建临时顺序节点实现,获取锁的客户端会生成有序节点,只有序号最小的节点持有锁

优势

  1. 自动释放(会话失效时自动删除节点)
  2. 公平锁机制(顺序节点)
  3. 强一致性保证

劣势

  1. 写操作性能低于Redis
  2. 需要维护ZooKeeper集群
  3. 客户端实现相对复杂

二、Etcd 实现方案
// 使用jetcd客户端示例
public class EtcdDistributedLock {
    private Client client;
    private Lease lease;
    
    public boolean tryLock(String lockKey) throws Exception {
        lease = client.getLeaseClient().grant(30).get(); // 30秒租约
        Txn txn = client.getKVClient().txn();
        txn.If(new Cmp(lockKey, Cmp.Op.EQUAL, CmpTarget.version(0)))
           .Then(Op.put(lockKey, "locked", PutOption.newBuilder().withLeaseId(lease.getID()).build()))
           .Else(Op.get(lockKey));
        return txn.commit().get().isSucceeded();
    }
}

核心原理
基于租约(Lease)机制,利用事务操作实现原子性锁获取

优势

  1. 强一致性(Raft协议)
  2. 自动续期机制
  3. 支持公平锁/非公平锁

劣势

  1. 运维复杂度较高
  2. 客户端生态不如Redis完善
  3. 性能低于Redis

三、数据库实现方案
// 基于MySQL的乐观锁实现
public class DbDistributedLock {
    @Transactional
    public boolean tryLock(String lockName) {
        // 使用唯一索引约束
        int result = jdbcTemplate.update(
            "INSERT INTO distributed_lock(lock_name,owner) VALUES (?,?) ON DUPLICATE KEY UPDATE owner=IF(expire_time < NOW(), VALUES(owner), owner)",
            lockName, UUID.randomUUID().toString());
        return result > 0;
    }
}

核心原理
基于数据库唯一约束或排他锁(SELECT FOR UPDATE)

优势

  1. 无需额外中间件
  2. 实现简单快速

劣势

  1. 性能差(高并发场景容易成为瓶颈)
  2. 无自动释放机制
  3. 死锁风险较高

方案对比总结表

方案一致性性能自动释放实现复杂度适用场景
Redis最终一致支持简单高并发、允许短暂不一致
ZooKeeper强一致支持复杂强一致性要求、公平锁场景
Etcd强一致支持较复杂强一致性且需要自动续期
数据库强一致不支持简单低频访问、无中间件环境的应急方案

选型建议

  1. 追求性能 ➜ Redis(Redisson实现)
  2. 强一致性要求 ➜ ZooKeeper/Etcd
  3. 无中间件环境 ➜ 数据库方案(需谨慎处理超时)
  4. 混合使用场景 ➜ 可组合使用(如Redis做主锁,数据库做备用锁)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2376064.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Git的安装和配置(idea中配置Git)

一、Git的下载和安装 前提条件&#xff1a;IntelliJ IDEA 版本是2023.3 &#xff0c;那么配置 Git 时推荐使用 Git 2.40.x 或更高版本 下载地址&#xff1a;CNPM Binaries Mirror 操作&#xff1a;打开链接 → 滚动到页面底部 → 选择2.40.x或更高版本的 .exe 文件&#xf…

【2025版】Spring Boot面试题

文章目录 1. Spring, Spring MVC, SpringBoot是什么关系&#xff1f;2. 谈一谈对Spring IoC的理解3. Component 和 Bean 的区别&#xff1f;4. Autowired 和 Resource 的区别&#xff1f;5. 注入Bean的方法有哪些&#xff1f;6. 为什么Spring 官方推荐构造函数注入&#xff1f;…

火山引擎实时音视频 高代码跑通日志

实时音视频 SDK 概览--实时音视频-火山引擎 什么是实时音视频 火山引擎实时音视频&#xff08;Volcengine Real Time Communication&#xff0c;veRTC&#xff09;提供全球范围内高可靠、高并发、低延时的实时音视频通信能力&#xff0c;实现多种类型的实时交流和互动。 通…

jenkins 启动报错

java.lang.UnsatisfiedLinkError: /opt/application/jdk-17.0.11/lib/libfontmanager.so: libfreetype.so.6: cannot open shared object file: No such file or directory。 解决方案&#xff1a; yum install freetype-devel 安装完成之后重启jenkins。

【合新通信】无人机天线拉远RFOF(射频光纤传输)解决方案

无人机天线拉远RFOF方案通过光纤替代传统射频电缆&#xff0c;实现无人机与地面控制站之间的高保真、低损耗信号传输&#xff0c;尤其适用于高频段&#xff08;如毫米波&#xff09;、远距离或复杂电磁环境下的无人机作业场景。 核心应用场景 军事侦察与电子战 隐蔽部署&…

程序设计语言----软考中级软件设计师(自用学习笔记)

目录 1、解释器和编译器 2、程序的三种控制结构 3、程序中的数据必须具有类型 4、编译、解释程序翻译阶段 5、符号表 6、编译过程 7、上下文无关文法 8、前、中、后缀表达式 9、前、后缀表达式计算 10、语法树中、后序遍历 11、脚本语言和动态语言 12、语法分析方法…

通过SMTP协议实现Linux邮件发送配置指南

一、环境准备与基础配置 1. SMTP服务开通&#xff08;以qq邮箱为例&#xff09; 登录qq邮箱网页端&#xff0c;进入「设置」-「POP3/SMTP/IMAP」 开启「SMTP服务」并获取16位授权码&#xff08;替代邮箱密码使用&#xff09; 记录关键参数&#xff1a; SMTP服务器地址&#…

若依框架页面

1.页面地址 若依管理系统 2.账号和密码 管理员 账号admin 密码admin123 运维 账号yuwei 密码123456 自己搭建的地址方便大家学习&#xff0c;不要攻击哦&#xff0c;谢谢啊

44、私有程序集与共享程序集有什么区别?

私有程序集&#xff08;Private Assembly&#xff09;与共享程序集&#xff08;Shared Assembly&#xff09;是.NET框架中程序集部署的两种不同方式&#xff0c;它们在部署位置、版本控制、访问权限等方面存在显著差异&#xff0c;以下是对二者的详细比较&#xff1a; 1. 部署…

【Java面试题】——this 和 super 的区别

&#x1f381;个人主页&#xff1a;User_芊芊君子 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 &#x1f50d;系列专栏&#xff1a;【Java】内容概括 【前言】 在Java的世界里&#xff0c;this和 super是两个非常重要且容易混淆的关键字。无论是在日常…

CentOS 7 内核升级指南:解决兼容性问题并提升性能

点击上方“程序猿技术大咖”&#xff0c;关注并选择“设为星标” 回复“加群”获取入群讨论资格&#xff01; CentOS 7 默认搭载的 3.10.x 版本内核虽然稳定&#xff0c;但随着硬件和软件技术的快速发展&#xff0c;可能面临以下问题&#xff1a; 硬件兼容性不足&#xff1a;新…

解决 PicGo 上传 GitHub图床及Marp中Github图片编译常见难题指南

[目录] 0.行文概述 1.PicGo图片上传失败 2.*关于在Vscode中Marp图片的编译问题* 3.总结与启示行文概述 写作本文的动机是本人看到了Awesome Marp&#xff0c;发现使用 Markdown \texttt{Markdown} Markdown做PPT若加持一些 CSS , JavaScript \texttt{CSS},\texttt{JavaScript} …

软考 系统架构设计师系列知识点之杂项集萃(59)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之杂项集萃&#xff08;58&#xff09; 第96题 假设关系模式R(U, F)&#xff0c;属性集U{A, B, C}&#xff0c;函数依赖集F{A->B, B->C}。若将其分解为p{R1(U1, F1), R2(U2, F2)&#xff0c;其中U1{A, B}, U2{A, …

python使用matplotlib画图

【README】 plot画图有两种方法&#xff1a;包括 plt.plot(), ax.plot()-画多个子图 &#xff0c;其中ax表示某个坐标轴; 【1】画单个图 import matplotlib # 避免兼容性问题&#xff1a;明确指定 matplotlib 使用兼容的后端&#xff0c;TkAgg 或 Agg matplotlib.use(TkAgg) …

upload-labs通关笔记-第5关 文件上传之.ini绕过

目录 一、ini文件绕过原理 二、源码审计 三、渗透实战 1、查看提示 2、制作.user.ini文件 &#xff08;1&#xff09;首先创建一个文本文件 &#xff08;2&#xff09;保存文件名为.user.ini 2、制作jpg后缀脚本 &#xff08;1&#xff09;创建一个文本文件 &#xf…

ssti模板注入学习

ssti模板注入原理 ssti模板注入是一种基于服务器的模板引擎的特性和漏洞产生的一种漏洞&#xff0c;通过将而已代码注入模板中实现的服务器的攻击 模板引擎 为什么要有模板引擎 在web开发中&#xff0c;为了使用户界面与业务数据&#xff08;内容&#xff09;分离而产生的&…

填涂颜色(bfs)

归纳编程学习的感悟, 记录奋斗路上的点滴, 希望能帮到一样刻苦的你! 如有不足欢迎指正! 共同学习交流! 🌎欢迎各位→点赞 👍+ 收藏⭐ + 留言​📝 含泪播种的人一定能含笑收获! 题目描述 由数字 0 0 0 组成的方阵中,有一任意形状的由数字 1 1 1 构成的闭合圈。现…

ros1+docker环境快速搭建

快速使用python 解析ros1的bag消息ros这个东西可以说安装起来非常麻烦的&#xff0c;费时费力&#xff0c;很可能还安装不成功&#xff0c;特别是我的环境是ubuntu22.04 &#xff0c;官方都不支持安装ros1。因此一个可行且快速的方法是使用别人配置好的ros的docker环境 一、下…

GpuGeek全栈AI开发实战:从零构建企业级大模型生产管线(附完整案例)

目录 背景一、算力困境&#xff1a;AI开发者的「三重诅咒」1.1 硬件成本黑洞‌1.2 资源调度失衡‌1.3 环境部署陷阱‌ 二、三大核心技术突破GpuGeek的破局方案2.1 ‌分时切片调度引擎&#xff08;Time-Slicing Scheduler&#xff09;‌2.2 ‌异构计算融合架构2.3 ‌AI资产自动化…

DataX从Mysql导数据到Hive分区表案例

0、下载DataX并解压到对应目录 DataX安装包&#xff0c;开箱即用&#xff0c;无需配置。 https://datax-opensource.oss-cn-hangzhou.aliyuncs.com/202308/datax.tar.gz 相关参考文档 https://github.com/alibaba/DataX/blob/master/hdfswriter/doc/hdfswriter.md 1、Hive分区…