Redis实现分布式锁

佛系码 等级 390 1 1

一、redis分布式锁的简易实现

用redis实现分布式锁是一个老生常谈的问题了。因为redis单条命令执行的原子性和高性能,当多个客户端执行setnx(相同key)时,最多只有一个获得成功。因此在对可用性要求不是特别高的场景下,redis分布式锁方案不失为一个性价比高的实现。

  1. 多个客户端执行setnx lockid random px lock-duration
    其中lockid与被锁住资源唯一对应。random为随机值,用于客户端判定自身是否为该锁的owner。lock-duration为锁保持时长,由业务操作耗时决定。
  2. 对于客户端来说,执行命令后,如果redis返回1,则表示抢到锁;否则没抢到
  3. 对于抢到锁的客户端,完成业务操作之后,需主动删除该锁。

二、redis分布式锁的注意事项

1. 锁必须要设定一个过期时间

如果不设置过期时间,考虑如下时序:

  • 客户端A抢锁成功
  • 客户端A的进程异常退出,没来得及主动释放锁
  • 其他客户端试图抢锁(毫无疑问是失败的)

如上所示,客户端A抢到锁了,但是由于某些异常导致进程还没有来得及释放锁就退出了。这样其他客户端setnx的返回永远是0,即永远也抢不到锁。

相反,如果设置过期时间,即使客户端A没有主动释放锁,到了过期时间之后redis也会自动释放。

  • 客户端A抢锁成功
  • 客户端A的进程异常退出,没来得及主动释放锁
  • 其他客户端抢锁失败
  • 锁自动过期
  • 其他客户端抢锁成功

2. 获取锁的命令不能分为两步执行

如果实现为,

setnx lockid
expire lockid lock-duration 

除非使用lua script, 否则redis无法支持上述两个命令的原子性,当第一个命令执行完成后,抢到锁的客户端A异常退出了,那么其他客户端将永远抢到锁。

注:redis在2.6.12版本后已经支持setnx命令的TTL参数,这个问题不复存在

3. 锁的值必须设置为随机值

假设锁的值为固定值,考虑如下情况

  • 客户端A抢到锁,执行业务操作
  • 客户端A由于某些原因阻塞,超过了锁有效时间,导致锁自动被释放
  • 客户端B抢锁成功,执行操作
  • 客户端A从阻塞中恢复,主动释放锁,执行del lockid
  • 客户端B创建的锁被客户端A删除。此时客户端C抢锁成功,客户端B与C的业务操作产生竞态。

如果锁的值是随机值,并且每次成功加锁时,都记录该随机值的话,并且释放锁时,判断锁的值是否等于记录值,等于则del, 不等于则跳过。

4. 释放锁时,需使用lua script封装保证原子性

如果不使用lua封装释放锁的逻辑,考虑时序:

  • 客户端A抢到锁,执行业务操作
  • 客户端A完成业务操作,主动释放锁:首先get lockid,发现记录值和锁当前值相等,判定该锁为自己所加。
  • 客户端A由于某些原因阻塞(比如GC),超过锁有效时间,锁被redis自动释放
  • 客户端B成功抢锁
  • 客户端A从阻塞中恢复,执行下一步del lockid,客户端B加的锁被A释放
  • 客户端C抢锁成功,B与C产生竞态

而redis执行lua script的原子性能避免上述问题。

5. 多个redis节点保证高可用

如果只在一个redis节点上抢锁,如果该节点宕机,将导致所有的客户端都抢不到锁,无法保证服务的高可用。

三、redsync实现一览

redlock是一种基于redis的分布式锁算法。而redsync是redlock算法的golang实现,其暴露了三个API:加锁(Lock),解锁(Unlock),续锁(Extend)

1. Lock

  • 首先随机生成一个value
  • 针对所有redis连接,执行set lockid value NX PX lock-duration
  • 如果超过半数连接上的请求都正常返回,且now < start + (1 - factor) * expire,意味着抢锁成功
  • 否则先清理key, 然后重试,重试时间间隔可由用户自定义。

2. Unlock

针对所有redis实例,执行lua脚本。这里会判断key对应的value和Mutex在Lock时使用的value值是否一致,只有一致了执行del命令。此举是为了保证每个客户端不会释放别的客户端创建的锁。

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

如果有超过半数实例上的请求返回,则意味着释放锁成功。否则判定失败。

3. Extend

Extend操作是为了保证当客户端业务处理时长超过expire时间时,客户端可主动延长锁的过期时间,而无需二次抢锁。针对所有redis连接,执行lua脚本,重新设置过期时间

 if redis.call("GET", KEYS[1]) == ARGV[1] then
        return redis.call("pexpire", KEYS[1], ARGV[2])
    else
        return 0
    end 

半数以上返回成功,则意味着Extend成功

收藏
评论区

相关推荐

我是Redis,MySQL大哥被我害惨了!
本文转自 轩辕之风 ,链接如下 https://mp.weixin.qq.com/s?__bizMzIyNjMxOTY0NA&mid2247486528&idx1&sn3f7b09eb21969fdb16f5b0805ff69fed&scene21wechat_redirect 我是Redis 你好,我是Redis,一个叫Antirez的
Go-连接Redis-学习go-redis包
Redis介绍 Redis是一个开源的内存数据结构存储,常用作数据库、缓存和消息代理。目前它支持的数据结构有诸如string、hash、list、set、zset、bitmap、hyperloglog、geospatial index和stream。Redis内置了复制、Lua脚本、LRU清除、事务和不同级别的磁盘持久性,并通过Redis Sentinel
Redis实现分布式锁
一、redis分布式锁的简易实现 用redis实现分布式锁是一个老生常谈的问题了。因为redis单条命令执行的原子性和高性能,当多个客户端执行setnx(相同key)时,最多只有一个获得成功。因此在对可用性要求不是特别高的场景下,redis分布式锁方案不失为一个性价比高的实现。 1. 多个客户端执行setnx lock
Go:分布式锁实现原理与最佳实践
分布式锁应用场景 很多应用场景是需要系统保证幂等性的(如api服务或消息消费者),并发情况下或消息重复很容易造成系统重入,那么分布式锁是保障幂等的一个重要手段。 另一方面,很多抢单场景或者叫交易撮合场景,如dd司机抢单或唯一商品抢拍等都需要用一把“全局锁”来解决并发造成的问题。在防止并发情况下造成库存超卖的场景,也常用分布式锁来解决。 实现
php操作redis哨兵模式,主从切换后自动获取master
本文将介绍如何使用PHP来连接redis哨兵模式。哨兵模式:大概的原理就是监听redis主库心跳包,如果心跳断开,则枚举一个从库推举成为新的主库,防止redis宕机不能使用。为了增强redis的性能,防止其挂掉,引用redis哨兵监控redis集群是个不错的选择。下面三步简单记录php连接redis哨兵。 第一步、获取哨兵模式连接redis句柄对象/
Zookeeper分布式锁?
客户端A要获取分布式锁的时候首先到locker下创建一个临时顺序节点(node_n),然后立即获取locker下的所有(一级)子节点。此时因为会有多个客户端同一时间争取锁,因此locker下的子节点数量就会大于1。对于顺序节点,特点是节点名称后面自动有一个数字编号,先创建的节点数字编号小于后创建的,因此可以将子节点按照节点名称后缀的数字顺序从小到大排序,这样
notifyAll唤醒线程的范围?
今天看到开源中国上有这样一个问答:假设我有两个对象锁,对象A锁有5个线程在等待,对象B锁有3个线程在等待,对象A锁中的线程执行完,这时调用notifyAll,是唤醒了对象AB两个锁的全部的等待线程还是只唤醒了A锁的5个线程? 1. 方法文档解释通过看该方法文档的解释,可以得出下面结论: notifyAll()中All的含义是所有的线程,而不是所有的锁,只能唤
springBoot集成redis
Redis作为一个Java后端面试中的一个常问考点,并且在项目中越来越常用,所以自己动手搭建了一个基于springboot集成redis做为数据缓存的demo(springboot集成mybatis、redis,并具有增删改查询接口)。关注微信公众号【菜鸟阿都】并回复:redis,可获得源码。后面也会继续深入研究redis相关知识,期待与大家一起学习交流。r
解决进程死锁——银行家算法透析
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。避免死锁算法中最有代表性的算法是Dijkstra E.W 于1968年提出的银行家算法: 下面我们将从例题中一点一点的分析: 解题: 第一步:
大厂首发!java哨兵模式的作用
引言做了5年开发的我,阿里一直是我心之所向,如今我如愿以偿进入了国内互联网巨头——Alibaba!其实,今年下半年我面试不少互联网企业,像涂鸦智能,百度,京东,腾讯,字节,滴滴,阿里等等都有三井的身影,之后总结出来的针对Java面试的知识点或真题,每个点或题目都是在面试中被问过的,满满干货,诚意分享! 由于整理成了文档,总结的内容比较多,希望大家都能领取一份
推荐程序员面试秘籍!2021年大厂Java岗面试必问
01 JAVA基础 1.1 java知识点 Hashmap 源码级掌握,扩容,红黑树,最小树化容量,hash冲突解决,有些面试官会提出发自灵魂的审问,比如为什么是红黑树,别的树不可以吗;为什么8的时候树化,4不可以吗,等等 concureentHashMap,段锁,如何分段,和hashmap在hash上的区别,性能,等等 HashTable ,同步锁,这块可
窗口只显示一次,窗口置顶
root Toplevel() root.grabset() 窗口锁定在top上 root.focusset() 焦点锁定在top上
MyBatis-Plus
一、MyBatisPlus本文转自 https://www.cnblogs.com/lyh/p/12859477.html,如有侵权,请联系删除。 1、简介  MyBatisPlus 是一个 Mybatis 增强版工具,在 MyBatis 上扩充了其他功能没有改变其基本功能,为了简化开发提交效率而存在。官网文档地址:   https://mp.baomid
阿里Java架构师谈:2021年最新Java面试经历
第一家是美团美团的话,三面下来,设计的内容知识也是挺广的吧,有MySQL、Redis、Kafka、线程、算法、+、volatile、线程、并发、设计模式等等... 一面问题:MySQL+Redis+Kafka+线程+算法 mysql知道哪些存储引擎,它们的区别 mysql索引在什么情况下会失效 mysql在项目中的优化场景,慢查询解决等 my
最新Java大厂高频面试题,看这一篇就够了!
常见resdis面试真题40道(含解析)1. 什么是 Redis?2. Redis 的数据类型?3. 使用 Redis 有哪些好处?4. Redis 相比 Memcached 有哪些优势?5. Memcache 与 Redis 的区别都有哪些?6. Redis 是单进程单线程的?7. 一个字符串类型的值能存储最大容量是多少?8. Redis