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数据结构之链表与字典的使用
May 11 Redis
redis实现共同好友的思路详解
May 26 Redis
k8s部署redis cluster集群的实现
Jun 24 Redis
使用redis实现延迟通知功能(Redis过期键通知)
Sep 04 Redis
基于Redis结合SpringBoot的秒杀案例详解
Oct 05 Redis
Redis Stream类型的使用详解
Nov 11 Redis
redis缓存存储Session原理机制
Nov 20 Redis
解决redis批量删除key值的问题
Mar 23 Redis
redis sentinel监控高可用集群实现的配置步骤
Apr 01 Redis
Redis批量生成数据的实现
Jun 05 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+DBM的同学录程序(4)
2006/10/09 PHP
PHP简洁函数小结
2011/08/12 PHP
解析php扩展php_curl.dll不加载的解决方法
2013/06/26 PHP
PHP创建文件,并向文件中写入数据,覆盖,追加的实现代码
2016/03/25 PHP
Windows2003下php5.4安装配置教程(Apache2.4)
2016/06/30 PHP
php图像处理函数imagecopyresampled用法详解
2016/12/02 PHP
tp5(thinkPHP5框架)时间查询操作实例分析
2019/05/29 PHP
[原创]PHP global全局变量经典应用与注意事项分析【附$GLOBALS用法对比】
2019/07/12 PHP
图片格式的JavaScript和CSS速查手册
2007/08/20 Javascript
jquery下利用jsonp跨域访问实现方法
2010/07/29 Javascript
让浏览器非阻塞加载javascript的几种方法小结
2011/04/25 Javascript
jQuery性能优化28条建议你值得借鉴
2013/02/16 Javascript
js 用CreateElement动态创建标签示例
2013/11/20 Javascript
jQuery实现tab标签自动切换的方法
2015/02/28 Javascript
JS通过ajax动态读取xml文件内容的方法
2015/03/24 Javascript
javascript+HTML5自定义元素播放焦点图动画
2016/02/21 Javascript
浅析Javascript ES6新增值比较函数Object.is
2016/08/24 Javascript
node.js请求HTTPS报错:UNABLE_TO_VERIFY_LEAF_SIGNATURE\的解决方法
2016/12/18 Javascript
Angular2学习教程之TemplateRef和ViewContainerRef详解
2017/05/25 Javascript
AngularJS使用ocLazyLoad实现js延迟加载
2017/07/05 Javascript
javascript中的offsetWidth、clientWidth、innerWidth及相关属性方法
2020/05/14 Javascript
Python 正则表达式操作指南
2009/05/04 Python
Python中使用PDB库调试程序
2015/04/05 Python
Python下载网络文本数据到本地内存的四种实现方法示例
2018/02/05 Python
一个简单的python爬虫程序 爬取豆瓣热度Top100以内的电影信息
2018/04/17 Python
用Python在Excel里画出蒙娜丽莎的方法示例
2020/04/28 Python
详解CSS3新增的背景属性
2019/12/25 HTML / CSS
HTML5验证以及日期显示的实现详解
2013/07/05 HTML / CSS
文秘专业自荐信
2013/10/14 职场文书
毕业自我鉴定范文
2013/11/06 职场文书
保护环境标语
2014/06/09 职场文书
社区志愿者活动总结
2014/06/26 职场文书
运动会广播稿诗歌版
2014/09/12 职场文书
九一八事变纪念日演讲稿
2014/09/14 职场文书
2015政治思想表现评语
2015/03/25 职场文书
Python实现对齐打印 format函数的用法
2022/04/28 Python