分布锁详解 Redis分布锁原理详解

分布锁详解和Redis分布锁原理详解

一、分布式锁

分布式锁概念:

分布式锁其实可以理解为:控制分布式系统有序的去对共享资源进行操作,通过互斥来保持一致性。 举个不太恰当的例子:假设共享的资源就是一个房子,里面有各种书,分布式系统就是要进屋看书的人,分布式锁就是保证这个房子只有一个门并且一次只有一个人可以进,而且门只有一把钥匙。

实现步骤
  • 加锁

    A获得了钥匙,进入房子里,看书。

  • 解锁

    A离开房间,并且还了钥匙。

  • 锁超时

    为了避免死锁,我们可以在一定单位时间内,将A清除房间,并且把钥匙放回原处。

分布式锁引擎

分布式锁的实现有很多,比如基于数据库、memcached、Redis、系统文件、etcd、zookeeper等。它们的核心的理念跟上面的过程大致相同。

二、Redis分布式锁

set命令、setnx命令和lua脚本:
  • setnx

    用法:SETNX KEY_NAME VALUE

    意义:指定的 key 不存在时,为 key 设置指定的值

    原理:redis内部使用两条命令实现该功能,set + expire,无法保证原子性。
    在2013年,Redis就发布了2.6.12版本,并且官网(set命令页),也早早就说明了“SETNX, SETEX, PSETEX可能在未来的版本中,会弃用并永久删除”。

  • set

    用法:SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]
    意义:设置给定 key 的值
    原理:redis版本在2.6.12之前,set是不支持nx参数的,如果想要完成一个锁,那么需要两条命令。
    2.6.12之后支持nx,保证可以一个进程实现setnx功能。

  • lua

    • 功能:

      set能保证单个key设置,如果需要设置多个key。可以使用lua.

      保证原子性的原因说的通俗一点:

      就算你在lua里写出花,执行也是一个命令(eval/evalsha)去执行的,一条命令没执行完,其他客户端是看不到的。

    • 参考:

      我之前的文章:Redis 脚本

      官方文档: https://redis.io/commands/eval

redis分布式锁原理
  • 多个客户端抢锁

  • 抢到锁的客户端执行业务

  • 抢到锁的客户端业务执行完,释放锁

  • redis分布式锁伪代码

    String uuid = xxxx;
    // 伪代码,具体实现看项目中用的连接工具
    // 有的提供的方法名为set 有的叫setIfAbsent
    set Test uuid NX PX 3000
    try{
        // biz handle....
    } finally {
        // unlock
        if(uuid.equals(redisTool.get('Test')){
            redisTool.del('Test');
        }
    }
    
  • redis分布式锁简单原理图
    在这里插入图片描述

redis分布式锁 超时处理问题
  • 为什么要有超时机制呢?

    进程A不讲道理啊,锁没等释放呢,万一崩了,直接原地把锁带走了,导致系统中谁也拿不到锁。

    防止客户端异常退出,无法释放锁,所有通过超时机制去释放锁。

  • 获取锁的客户端没有执行完业务,锁被超时机制释放了?

    如果进程A又不讲道理,操作锁内资源超过笔者设置的超时时间,那么就会导致B进程拿到锁,等进程A回来了,回手就是把其他进程的锁删了。

    找不到锁其实还算好的,万一此刻有个进程C过来加锁成功,那么进程B就把进程C的锁释放了。
    以此类推,进程C可能释放进程D的锁,进程D…(禁止套娃),具体什么后果就不得而知了。

redis分布式锁 超时处理解决方案
方案一:维护守护进程
  • 增加守护进程,进程未执行完业务不让锁超时,给当前进程的锁续加时间。
方案二:释放锁的时候验证
  • 加锁的时候加上客户端标识

    在用setnx(这里的setnx并不是redis命令,理解加锁操作)的时候,key虽然是主要作用,但是value也不能闲着,可以设置一个唯一的客户端ID,或者用UUID这种随机数。

  • 解锁的时候释放标识

    当解锁的时候,先获取value判断是否是当前进程加的锁,再去删除。

  • 伪代码

    String uuid = xxxx;
    // 伪代码,具体实现看项目中用的连接工具
    // 有的提供的方法名为set 有的叫setIfAbsent
    set Test uuid NX PX 3000
    try{
        // biz handle....
    } finally {
        // unlock
        if(uuid.equals(redisTool.get('Test')){
            redisTool.del('Test');
        }
    }
    
  • redis分布式锁简单超时处理原理图
    在这里插入图片描述

  • redis分布式锁简单超时处理原理图,步骤解释

    1. 进程A加锁。
    2. 进程A加锁成功。
    3. 进程B加锁, 进程B加锁失败,进入自旋状态,等待再次加锁。
    4. 进程A执行业务,业务执行时间很长,大于加锁时间。
    5. 进程B请求加锁。
    6. 进程B加锁成功。
    7. 进程B执行业务。
    8. 此时进程A业务执行完成,释放锁。发现自己的锁没了,存在进程B的锁,但是不是进程A加的锁,进程A没权限删除进程B的锁。
    9. 进程B业务执行完成,释放锁
    10. 进程B释放锁完成。

三、分布式锁五中特性

互斥性:

当一个线程/进程加锁成功后,其他线程/进程无法加锁,具有排他性。

锁失效机制:

加锁成功后,应用服务器宕机导致锁未能释放,服务恢复后一直获取不到锁。应设置超时时间,防止出现类似死锁情况。

阻塞锁(可选):

当前资源已被加锁,其他线程/进程来加锁是否阻塞等待,还是立即返回

可重入性(可选)

当前锁的持有者是否能再次进入。

公平性(可选):

加锁的顺序和请求是顺序一致,还是随机抢锁。

四、Redis分布式锁实现可重入性

Redis分布式锁基本上已经实现了分布式锁的特性。下面是关于可重入性的实现。

加锁计数
  1. 进程A第一次加锁成功,计数为1.
  2. 允许进程A第二次加锁,锁计数为2.
  3. 同理,同一个进程多次加锁,锁计数累计递增。
解锁计数
  1. 如果进程A不异常对出的情况,加锁和解锁是成对出现的。
  2. 进程A释放一次锁,计数减1。
  3. 当进程A的锁计数减到0,释放锁,允许其他进程加锁。

以上redis分布式锁,是基于单机的redis来实现。多机reids需要考虑每一台机器的加锁情况。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值