• Redis系列(二):Redis的分布式锁解析及应用


    在开发中,一个进程中多个线程需要竞争某一资源的时候,我们通常会用一把锁来保证只有一个线程获取到资源。如加上synchronize关键字或ReentrantLock锁等操作。但如果是多个进程相互竞争一个资源,如何保证资源只会被一个操作者持有呢?

    比如在微服务的架构下,多个应用服务要同时对同一条数据做修改,要确保数据的正确性,那就只能有一个应用修改成功。

    Redis实现分布式锁

    上一篇文章中在String-字符串类型中可以用作分布式锁,那么具体是如何实现的呢?

    首先 Redis 是一个单独的非业务服务,不会受到其他业务服务的限制,所有的业务服务都可以向Redis发送写入命令,且只有一个业务服务可以写入命令成功,那么这个写入命令成功的服务即获得了锁,可以进行后续对资源的操作,其他未写入成功的服务,则进行阻塞处理。

    String类型实现

    使用setnx(SET if Not Exists)指令实现,即如果 key 不存在,才会设置它的值,否则什么也不做。

    假设有两个客户端同时竞争锁,即向Redis写入lock_key,A客户端写入成功则A先获取到锁,客户端B写入失败则B未获取到锁。A客户端在使用完资源后,将redis中lock_key删除,即释放锁。

    # A执行写入
    > setnx lock_key true
    (integer) 1
    # A获得锁,执行A的业务
    > del lock_key
    (integer) 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    # B执行写入
    > setnx lock_key true
    (integer) 0
    # B未获取锁,阻塞
    
    • 1
    • 2
    • 3
    • 4

    但这样是存在一些问题的,试想如果A客户端在获取锁后,出现了故障,导致del语句没有调用,这样lock_key就一直得不到释放,即该资源锁一直存在,那么其他服务也就永远得不到该资源了。那么如何避免呢?

    避免死锁

    在Redis写入数据时,可以设置数据过期时间,这样即便在服务故障,锁也能自动释放。

    > setnx lock_key true
    (integer) 1
    > expire lock_key 5
    (integer) 1
    #  执行业务
    > del lock_key
    (integer) 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    通过expire指令实现锁过期时间害存在一些问题,试想如果expire出错,没有成功执行,那么也将会造成前面的死锁情况。

    因为 setnx 和 expire 是两条指令而不是原子指令,在Redis2.8版本中,可以通过set key velue ex timeout nx来实现setnx和expire两条指令。

    > set lock_key true ex 5 nx
    OK
    #  执行业务
    > del lock_key
    (integer) 1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    虽然解决了死锁,但这样又引入了新问题,试想A的业务执行过长,超过了锁的过期时间,A锁已经被释放,但此时B重新又获得了锁,A执行完后释放锁,可能就释放了B刚获取到的锁。这就出现了锁过期,释放其他服务锁的问题

    避免锁超时问题

    每个服务在设置value的时候,带上自己服务的唯一标识,如UUID,或者一些业务上的独特标识。这样在删除key的时候,只删除自己服务之前添加的key就可以了。

    但匹配value和删除key是两条指令,不是原子操作,且redis中没有提供set这样的扩展参数,没法直接通过指令实现原子操作。

    这里就需要使用Lua脚本处理,Lua脚本可以保证连续多个指令的原子性执行,将这两个操作合并成一个操作,就可以保证其原子性了。

    # delifequals
    if redis.call("get",KEYS[1]) == ARGV[1] then
        return redis.call("del",KEYS[1])
    else
        return 0
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    内网配置git代理
    Python 爬虫 / web 面试常见问题
    Linux03-网络设置
    记录一次线上内存溢出排查详细过程
    1.3媒介视角的语言观
    Zookeeper三台机器集群搭建
    贝加莱软件功能测试
    Android插件化技术的原理与实现
    如何建立完整的、有效的会员体系?
    C++实验2:图书管理系统2.0——继承、派生、多态
  • 原文地址:https://blog.csdn.net/Ber_Bai/article/details/127583695