笔耕墨耘,深研术道。

01写在前面Redis是一个高性能的内存数据库,常用于数据库、缓存和消息中间件。它提供了丰富的数据结构,更适合各种业务场景;基于AP模型,Redis保证了其高可用和高性能。

本文主要内容:

  • Redis实现分布式锁的依据
  • 基于setnx + expire的不断探索

  • 分布式利器—Redisson

  • Redlock算法及其争议

02Redis实现分布式锁的依据

Redis分布式锁的实现的核心依据:redis的单线程模型,保证最终只有一个setnx命令可以执行成功;

如何理解Redis的单线程模型(选读)?

Redis基于Reactor模式开发了自己网络事件处理器:这个处理器被称为文件事件处理器(file event handler):

  • 文件事件处理器使用I/O多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器;

  • 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

注:文件事件处理器是统称,是网络事件处理的模块(或者说方案),其包括四个组成部分:套接字、I/O多路复用程序、文件事件分派器(dispatcher),以及事件处理器。

文件事件处理器四个组成部分如下:

文件事件处理器的四个组成部分

  • I/O多路复用程序负责监听多个套接字,对于产生了事件的套接字,将它们放入一个队列里面,然后通过这个队列,以有序、同步、每次一个套接字的方式向文件事件分派器传送套接字(单线程模型);

  • 当上一个套接字产生的事件被处理完毕之后,I/O多路复用程序才会继续向文件事件分派器传送下一个套接字;

  • 文件事件分派器接受I/O多路复用程序传来的套接字,并根据套接字产生的事件类型,调用相应的事件处理器;

  • 这些事件处理器是一个个函数,它们会执行相应的操作。

总结:虽然文件事件处理器以单线程方式运行,但通过使用I/O多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与Redis服务器中其他同样以单线程方式运行的模块进行对接,这保持了Redis内部单线程设计的简单性。03基于setnx + expire的不断探索为了代码的复用和可维护性,这里提供一个分布式锁的接口,各个版本的方案均实现了此接口。

基本实现代码:这里给出了阻塞和非阻塞两种方式。

public interface DistributedLock {    /**     * 阻塞     *     * @param lockKey     * @param timeout     */    void lock(String lockKey, long timeout);    /**     * 非阻塞     *     * @param lockKey     * @param timeout     * @return     */    boolean tryLock(String lockKey, long timeout);    /**     * 释放锁     *     * @param lockKey     */    void unlock(String lockKey);}

利用stringRedisTemplate实现,以下代码并不严谨,仅仅是演示某些问题。

    @Override    public void lock(String lockKey, long timeout) {        BoundValueOperations<String, String> boundValueOps = stringRedisTemplate.boundValueOps(lockKey);        while (true) {            Boolean b = boundValueOps.setIfAbsent("1");            if (b) {                Boolean expire = boundValueOps.expire(timeout, TimeUnit.SECONDS);                if (expire) {                    return;                }            }            // 休眠1s后再重试            LockSupport.parkNanos(1*1000*1000);        }    }    @Override    public boolean tryLock(String lockKey, long timeout) {        BoundValueOperations<String, String> boundValueOps = stringRedisTemplate.boundValueOps(lockKey);        Boolean b = boundValueOps.setIfAbsent("1");        if (b) {            Boolean expire = boundValueOps.expire(timeout, TimeUnit.SECONDS);            if (expire) {                return true;            }        }        return false;    }    @Override    public void unlock(String lockKey) {        stringRedisTemplate.delete(lockKey);    }

上述代码,是一种简单的分布式锁的实现,但是问题也较明显:

  • 非原子性:setnx + expire两条命令非原子操作,有可能导致死锁;

  • 锁误解除:B线程加的锁,可能被A线程释放掉,导致锁失效;

  • 超时并发:业务执行时间超过锁超时时间,导致锁失效,产生并发安全问题;

  • 不可重入:不支持可重入;

  • 集群问题:Redis哨兵模式和集群模式带来的问题,主从发生failover时候带来的锁失效。

针对以上问题,提供以下解决方案:

Q1非原子性

  • 方案一:在高版本redis中(Redis 2.6.12以后),官方完善了setnx:SET key value [EX seconds] [PX milliseconds] [NX|XX];

  • 方案二:使用LUA脚本,如下:

if (redis.call('setnx', KEYS[1], ARGV[1]) < 1)then return 0;end;redis.call('expire', KEYS[1], tonumber(ARGV[2]));return 1;

Q2 & Q3锁误解除和锁超时并发

如下图:

当锁超时释放后,会有其他线程争抢锁。此时,会发生线程A释放了线程B占用锁的情况,并且会导致锁失效产生并发问题(图中棕色所示)。针对锁误解除的问题,可以:

  • 加锁时候,设置value值,这个value值来标记当前线程,严格来说,该值在所有客户端和所有锁定请求中必须唯一。

This value must be unique across all clients and all lock requests.

  • 在解锁的时候判断解锁线程是否是占有锁的线程,出于安全性(原子性)考虑,这段解锁逻辑使用LUA脚本编写。如下:

Basically the random value is used in order to release the lock in a safe way, with a script that tells Redis: remove the key only if it exists and the value stored at the key is exactly the one I expect to be.

if redis.call("get",KEYS[1]) == ARGV[1] then    return redis.call("del",KEYS[1])else    return 0end

针对锁超时带来的并发问题,可以:

为获取锁的线程(图中线程A)设置一个守护线程,守护线程周期性地给当前锁续期,当线程A执行完成任务,会显示关闭守护线程;即使线程A挂掉,由于线程A和守护线程在同一个进程,守护线程也会停下。这把锁到了超时的时候,没人给它续命,也就自动释放了。如下图:

补充:Redisson的锁续期实现,基于netty的时间轮算法。

Q4不可重入

参考Redisson利用hash数据结构实现对线程的重入计数。这个问题将在Redisson分布式锁源码分析里面讲述。

Q5集群问题

我们知道Redis的主从复制是异步的,主从发生failover时将带来锁失效问题。

What happens if the Redis master goes down? Well, let’s add a slave! And use it if the master is unavailable. This is unfortunately not viable. By doing so we can’t implement our safety property of mutual exclusion, because Redis replication is asynchronous.

如上图所示:

  • 左边client在Master获取到锁;

  • 在将锁信息同步到slave之前,master挂掉,此时发生failover;

  • slave节点升级为新的master(New Master),此时,其他线程来获取锁,发现并没有其他线程占用,也加锁成功。这导致了锁失效。

解决方案:

可以使用Redlock算法。

We propose an algorithm, called Redlock, which implements a DLM which we believe to be safer than the vanilla single instance approach.

04分布式利器—Redisson

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。这里我们关注其对分布式锁的支持,它解决了我们上面论述的几种问题。

概览Redisson锁的实现

如上图Redisson的类图,总结常用对象:

  • 可重入锁(Reentrant Lock)

  • 公平锁(Fair Lock)

  • 联锁(MultiLock)

  • 红锁(RedLock)

  • 读写锁(ReadWriteLock)

  • 信号量(Semaphore)

这里我们以RedissonLock(最常用的)为例,了解下其常用的一些api:

public void lock();// 不建议使用,锁续期仅仅在leaseTime = -1时生效public void lock(long leaseTime, TimeUnit unit);public boolean tryLock();// 不建议使用,锁续期仅仅在leaseTime = -1时生效public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit);public boolean tryLock(long waitTime, TimeUnit unit);

上述api的实际使用,这里不再演示,相对比较简单,可以通过其官网学习:

https://github.com/redisson/redisson/wiki

注意事项

  • 锁续期生效的场景:锁续期仅仅在leaseTime = -1时生效;

  • 锁【lock.lock()】的使用必须紧跟try代码块,且unlock要放到finally块第一行。这点是阿里规范,说明如下:

05Redlock算法及其争议为了解决集群环境下,分布式锁的缺陷,Antirez发明了Redlock算法,它的实现流程比较复杂,不过业界已经有很多开源的类库封装实现了。比如Redisson提供的RedissonRedLock对象,其实现了Redlock描述的加锁算法。

Redlock算法

为了使用Redlock算法,需要提供多个Redis实例,并且这些实例之间相互独立,没有主从关系(不会有数据的同步等)。同很多分布式算法一样,Redlock也使用大多数机制。加锁时候,它会向过半节点发送set(key,value,nx=True,ex=xxx)指令,只要过半节点set成功,就认为加锁成功。释放锁时候,需要向所有节点发送del指令。不过Redlock算法还需要考虑超时问题、出错重试、时钟漂移等很多细节问题,同时因为Redlock需要向多个节点进行读写,意味着其相比单实例的Redis的性能会下降一些。

Redlock算法的争议

可以参阅:

[1] https://redis.io/topics/distlock

[2] http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

[3] http://antirez.com/news/101

06总结至此,简单介绍了分布式锁基于Redis的实现。实际项目中建议使用Redisson提供的锁来保证临界资源的安全性。对于追求业务强一致的业务场景,可以利用分布式协调器来实现,如基于zookeeper的实现,这将在后面提到。07引用[1] https://redis.io/topics/distlock

[2] https://github.com/redisson/redisson/wiki

[3] https://mp.weixin.qq.com/s/8fdBKAyHZrfHmSajXT_dnA

[4] https://learnku.com/articles/47769

[5] 黄健宏,《Redis设计与实现》,机械工业出版社

[6] Josiah L. Carlson,黄健宏,《Redis实战》,中国工信出版社

[7] 钱文品,《Redis深度历险》,中国工信出版社

发现“在看”和“赞”了吗,戳我试试吧

redis 分布式锁 看门狗_漫谈分布式锁之Redis实现相关推荐

  1. redis 分布式锁 看门狗_分布式锁Redisson的使用,看门狗机制

    Redisson简介 Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid).它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式 ...

  2. redis 分布式锁 看门狗_带你研究Redis分布式锁,源码走起

    前言 前阵子我们讲了分布式锁的实现方式之一:zookeeper,那么这次我们来讲讲同样流行,甚至更胜一筹的Redis. 除了这两种其实还有数据库实现分布式锁啊,但是这种方式是非主流,所以咱这里就不讲了 ...

  3. redis 分布式锁 看门狗_redis分布式锁原理及实现

    一.写在前面 现在面试,一般都会聊聊分布式系统这块的东西.通常面试官都会从服务框架(Spring Cloud.Dubbo)聊起,一路聊到分布式事务.分布式锁.ZooKeeper等知识. 所以咱们这篇文 ...

  4. redisson 看门狗_Redisson的分布式锁

    最近想使用redisson的分布式锁去替换系统中的redis分布式锁从而解决续期问题,查看了源码,发现其原理还是比较容易理解的. 一.Maven配置 <dependency> <gr ...

  5. 【面试 分布式锁详细解析】续命 自旋锁 看门狗 重入锁,加锁 续命 解锁 核心源码,lua脚本解析,具体代码和lua脚本如何实现

    Redisson实现分布式锁原理 自己实现锁续命 在 controller 里开一个 线程 (可以为 守护线程) 每10秒,判断一个 这个 UUID是否存在,如果 存在,重置为 30秒. 如果不存在, ...

  6. redis实现轮询算法_【07期】Redis中是如何实现分布式锁的?

    点击上方"Java面试题精选",关注公众号 面试刷图,查缺补漏 分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 本地面 ...

  7. 一个项目部署多个节点会导致锁失效么_不为人知的分布式锁实现,全都在这里了

    1引入业务场景 首先来由一个场景引入: 最近老板接了一个大单子,允许在某终端设备安装我们的APP,终端设备厂商日活起码得几十万到百万级别,这个APP也是近期产品根据市场竞品分析设计出来的,几个小码农通 ...

  8. stm32看门狗_「正点原子NANO STM32开发板资料连载」第十一章 独立看门狗实验

    1)实验平台:ALIENTEK NANO STM32F411 V1开发板2)摘自<正点原子STM32F4 开发指南(HAL 库版>关注官方微信号公众号,获取更多资料:正点原子 第十一章 独 ...

  9. Abb机器人看门狗_人类大战机器人 《看门狗2》1.15版本内容公布

    育碧正式公布了开放世界动作冒险游戏<看门狗2>1.15版本的更新内容.在本次更新中,玩家将和好友一起进行有史以来难度最高的合作模式并在夺取卡车时面临诸多威胁,让我们一起来看看究竟有哪些新玩 ...

最新文章

  1. cv2 画多边形不填充_OpenCV python: 任意多边形填充和凸多边形填充(fillPoly和fillConvexPoly的区别,有图有真相!)...
  2. 吴恩达《Machine Learning》精炼笔记 11:推荐系统
  3. 考研重庆邮电大学计算机跨专业,重庆邮电大学计算机考研难度
  4. 科普:Windows下Netcat使用手册
  5. Nginx——配置负载均衡
  6. BotVS开发基础—2.4 获取订单、取消订单、获取未完成订单
  7. angular input 为file on-change 无效
  8. Kotlin的基本数据类型
  9. DisplayObjectContainer 属性和方法
  10. 重载类型转换操作符(overload conversion operator)
  11. html矩形变圆形的代码,html5canvas绘制矩形和圆形的实例代码.pdf
  12. 推荐系统的4个方面完全总结
  13. 基于 钉钉认证 通过 华为、H3C 结合 OpenPortal认证计费系统 实现 网络准入 钉钉授权 实名认证
  14. [7 kyu] Exes and Ohs
  15. 利用微信公众号实现商品的展示和支付(2)
  16. C语言课设--藏书管理信息系统
  17. RTS超低延时直播技术:保障大型赛事直播零时差互动
  18. ROSCon会议详细资料
  19. 开源BI工具2:apache/superset
  20. SCI论文投稿全程邮件模板

热门文章

  1. ThreadLocal 详解
  2. 分布式存储与传统SAN、NAS的优、劣对比
  3. 浏览器厂商开始默认支持WebAssembly格式
  4. [转]自适应网页设计(Responsive Web Design)
  5. secureCRT配置ssh -x
  6. Android HelloWorld 例子
  7. 2021 最流行的十大 JS 框架,前三又换牌了,最值得关注的是 Svelte 和 Solid 的崛起!...
  8. 1 HBase 介绍
  9. reactjs入门示例
  10. hdfs yarn hbase pid文件被删除解决办法:修改hadoop-daemon.sh yarn-daemon.sh hbase-daemon.sh中PID_DIR存储路径