Redis如何实现分布式锁


Posted in Redis onAugust 23, 2021

今天我们来聊一聊分布式锁的那些事。

相信大家对锁已经不陌生了,我们在多线程环境中,如果需要对同一个资源进行操作,为了避免数据不一致,我们需要在操作共享资源之前进行加锁操作。在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。

比如你去相亲,发现你和一大哥同时和一个女的相亲,那怎么行呢...,搞不好还要被揍一顿。

那什么是分布式锁呢。当多个客户端需要争抢锁时,我们就需要分布式锁。这把锁不能是某个客户端本地的锁,否则的话,其它客户端是无法访问的。所以分布式锁是需要存储在共享存储系统中的,比如Redis、Zookeeper等,可以被多个客户端共享访问和获取。今天我们就来看一下如何使用Redis来实现分布式锁。

一、前言

在正式开始之前,我们先来了解两个Redis的命令:

SETNX key value

这个命名的含义是,当key存在时,不做任何赋值操作;当key不存在时,就创建key,并赋值成value,即(不存在即设置)。

SET key value [EX seconds | PX milliseconds] NX

SET后加NX选项,就和SETNX命令类似了,也实现不存在即设置的功能。此外,这个命令在执行时,可以通过EX或者PX设置键值对的过期时间。

二、正文

开始之前,我们先引入一个场景:

假设要给某个商品举行秒杀活动,我们事先把库存数据100已经存入到了redis中,我们现在需要来进行库存扣减。

如图所示,我们假设有1000个客户端来进行库存扣减操作,那我们该如何做,才能保证库存扣减顺序一致且不会超扣呢。

Redis如何实现分布式锁

我们首先想到的就是加锁,在进行库存扣减之前,我们先拿到锁,然后进行扣减,最后再释放锁。在redis中我们创建一个key来代表一个锁变量,然后对应的值来表示锁变量的值。我们来看一下如何进行加锁。

假设1000个客户端同时进行加锁请求。因为redis使用单线程来处理请求,所以redis会串行执行他们的请求操作。假设redis先处理客户端2的请求,读取lock_key的值,发现lock_key为0,所以客户端2就把lock_key的value设置成1,表示已经进行了加锁操作。如果此时客户端3被处理,发现lock_key的值已经为1了,所以就返回加锁失败的信息。

Redis如何实现分布式锁

当拿到锁的客户端2处理完共享资源后,就要进行释放锁的操作,释放锁很简单,就是将lock_key重新设置为0。

由于加锁操作包含了三个操作(读取锁变量、判断锁变量的值以及把锁变量的值设置成1),而这三个操作在执行的过程中需要保证原子性。那怎么保证原子性呢?

我们可以使用SETNX命令来实现加锁操作,SETNX命令表示key不存在时就创建,key存在时就不做任何赋值操作,当加锁时候,我们执行

SETNX lock_key 1

对于释放锁操作来说,我们可以使用DEL命令来删除锁变量。比如客户端2进行加锁,执行SETNX lock_key 1,如果lock_key不存在,则会创建lock_key,返回加锁成功,此时客户端2可以进行共享资源的访问。如果这时客户端1来发起请求加锁操作,而此时lock_key已经存在,SETNX lock_key 1不做任何赋值操作操作,返回加锁失败,所以客户端1加锁失败。当客户端2执行完共享资源访问后,执行DEL命令来释放锁。此时当有其它客户端再来访问时,lock_key已经不存在了,就可以进行正常的加锁操作了。所以,我们可以使用SETNX和DEL命令组合来进行加锁和释放锁的操作。

不过这里有两个问题:

1.当某个客户端执行完SETNX命令、加锁后,此时发生了异常,结果一直没有执行DEL操作命令来释放锁。因此,这个客户端一直占用着这个锁,其它客户端无法拿到锁。

解决这个问题,一个有效的方法就是,给锁变量设置一个过期时间。这样一来,即使持有锁的客户端发生了异常,无法主动的释放锁,Redis也会根据锁变量的过期时间把它删除。其它客户端在锁变量过期后,就可以重新进行加锁操作了。

2.如果客户端1执行了SETNX命令加锁后。如果此时客户端2执行DEL命令删除锁,这时,客户端A的锁就被误释放了。这是我们不能接受的。

为了解决这个问题,我们需要能区分来自不同客户端的锁操作。我们该如何做呢?我们可以给每个客户端生成一个唯一值,在进行加锁时,我们把锁变量赋值成这个唯一值。这样在释放锁的时候,客户端需要判断,当前锁变量的值是否和自己的唯一标识相等,在相等的情况下,才能释放锁。

下面来看一下如何在Redis中进行实现。我们可以使用SET加EX/PX和NX选项,来进行加锁操作。

SET lock_key uuid NX PX 100

 其中lock_key是锁变量,uuid表示客户端的唯一标识,PX 100表示100ms过期。由于我们在释放锁时需要对比客户端的标识和锁变量的值是否一致,这包含了多个操作,为了保证原子性,我们需要使用lua脚本,下面是lua脚本的实现。

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

其中KEY[1]表示lock_key,ARGV[1]表示当前客户端的唯一标识,这两个值是我们在执行lua脚本时作为参数传入的。下面我们来看一下完整的代码实现。

import redis
import traceback
import uuid
import time
 
class Inventory(object):
    def __init__(self):
        pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
        client = redis.StrictRedis(connection_pool=pool, max_connections=20)
        self.client=client
        self.uuid=str(uuid.uuid1())
        print(self.uuid)
        self.key="lock_key"
        self.inventory_key="inventory"
    def unlock(self):
        unlock_script="" \
                      "if redis.call(\"get\",KEYS[1]) == ARGV[1] then" \
                      "   return redis.call(\"del\",KEYS[1])" \
                      "else" \
                      "   return 0 " \
                      "end"
        try:
            unlock_cmd=self.client.register_script(unlock_script)
            result=unlock_cmd(keys=[self.key],args=[self.uuid])
            if result==1:
                print("释放成功")
            else:
                print("释放出错")
        except:
            print(traceback.format_exc())
 
    def lock(self):
        try:
          while True:
             result=self.client.set(self.key,self.uuid,px=100,nx=True)
             print(result)
             if result==1:
                 break
 
             print("sleep 1s")
             time.sleep(1)
          print("加锁成功")
          return True
        except:
          print(traceback.format_exc())
    def inventory(self):
        if self.lock():
           print("库存扣减")
           self.client.decr(self.inventory_key)
           print("扣减完成")
           self.unlock()
 
 
inv=Inventory()
inv.inventory()

到此,我们就把Redis实现分布式锁就聊完了。既然都读到了这里,不妨给个「三连」吧,你的三连就是我最大的动力。

到此这篇关于Redis如何实现分布式锁的文章就介绍到这了,更多相关Redis 分布式锁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
redis连接被拒绝的解决方案
Apr 12 Redis
Redis5之后版本的高可用集群搭建的实现
Apr 27 Redis
redis内存空间效率问题的深入探究
May 17 Redis
SpringBoot 集成Redis 过程
Jun 02 Redis
Redis可视化客户端小结
Jun 10 Redis
Redis主从配置和底层实现原理解析(实战记录)
Jun 30 Redis
Redis入门教程详解
Aug 30 Redis
Redis 持久化 RDB 与 AOF的执行过程
Nov 07 Redis
Redis Stream类型的使用详解
Nov 11 Redis
Redis 的查询很快的原因解析及Redis 如何保证查询的高效
Mar 16 Redis
Redis实战高并发之扣减库存项目
Apr 14 Redis
Redis配置外网可访问(redis远程连接不上)的方法
Dec 24 Redis
Redisson实现Redis分布式锁的几种方式
Redis分布式锁Redlock的实现
Aug 07 #Redis
关于redisson缓存序列化几枚大坑说明
Aug 04 #Redis
Redis Cluster 集群搭建你会吗
Aug 04 #Redis
解析redis hash应用场景和常用命令
Aug 04 #Redis
redis 存储对象的方法对比分析
Aug 02 #Redis
springboot使用Redis作缓存使用入门教程
Jul 25 #Redis
You might like
php注册登录系统简化版
2020/12/28 PHP
php实现文件与16进制相互转换的方法示例
2017/02/16 PHP
Js sort排序使用方法
2011/10/17 Javascript
从阶乘函数对比Javascript和C#的异同
2012/05/31 Javascript
javascript 系统文件夹文件操作及参数介绍
2013/01/08 Javascript
向当前style sheet中插入一个新的style实现方法
2013/04/01 Javascript
js 表单提交后按钮变灰的实例代码
2013/08/16 Javascript
JS保留两位小数,多位小数的示例代码
2014/01/07 Javascript
JavaScript编程中容易出BUG的几点小知识
2015/01/31 Javascript
利用js查找数组中指定元素并返回该元素的所有索引示例
2017/03/29 Javascript
js实现放大镜特效
2017/05/18 Javascript
jstree单选功能的实现方法
2017/06/07 Javascript
javascript用rem来做响应式开发
2018/01/13 Javascript
完美解决axios在ie下的兼容性问题
2018/03/05 Javascript
从vue源码看props的用法
2019/01/09 Javascript
详解用js代码触发dom事件的实现方案
2020/06/10 Javascript
python正则匹配查询港澳通行证办理进度示例分享
2013/12/27 Python
详解Python中for循环的使用
2015/04/14 Python
介绍Python的Django框架中的QuerySets
2015/04/20 Python
总结Python编程中三条常用的技巧
2015/05/11 Python
Python运行报错UnicodeDecodeError的解决方法
2016/06/07 Python
Django 使用logging打印日志的实例
2018/04/28 Python
matplotlib.pyplot绘图显示控制方法
2019/01/15 Python
Python使用Paramiko控制liunx第三方库
2020/05/20 Python
美国隐形眼镜零售商:LensPure
2019/03/10 全球购物
Wedgwood英国官方网站:英式精致骨瓷餐具、礼品与生活精品,源于1759年
2019/09/02 全球购物
医学生自我鉴定范文
2013/11/08 职场文书
中学教师自我鉴定
2014/02/07 职场文书
《明天,我们毕业》教学反思
2014/04/24 职场文书
励志广播稿300字(5篇)
2014/09/15 职场文书
辞职信格式模板
2015/02/27 职场文书
2015年员工工作表现评语
2015/03/25 职场文书
不会写演讲稿,快来看看这篇文章!
2019/08/06 职场文书
使用pandas或numpy处理数据中的空值(np.isnan()/pd.isnull())
2021/05/14 Python
Python中json.dumps()函数的使用解析
2021/05/17 Python
Spring Boot两种全局配置和两种注解的操作方法
2021/06/29 Java/Android