Redis如何实现分布式锁
今天我们来聊一聊分布式锁的那些事。
相信大家对锁已经不陌生了,我们在多线程环境中,如果需要对同一个资源进行操作,为了避免数据不一致,我们需要在操作共享资源之前进行加锁操作。在计算机科学中,锁(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中我们创建一个key来代表一个锁变量,然后对应的值来表示锁变量的值。我们来看一下如何进行加锁。
假设1000个客户端同时进行加锁请求。因为redis使用单线程来处理请求,所以redis会串行执行他们的请求操作。假设redis先处理客户端2的请求,读取lock_key的值,发现lock_key为0,所以客户端2就把lock_key的value设置成1,表示已经进行了加锁操作。如果此时客户端3被处理,发现lock_key的值已经为1了,所以就返回加锁失败的信息。

当拿到锁的客户端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实现的分布式锁
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.conne ...
- 基于Redis的简单分布式锁的原理
参考资料:https://redis.io/commands/setnx 加锁是为了解决多线程的资源共享问题.Java中,单机环境的锁可以用synchronized和Lock,其他语言也都应该有自己的 ...
- redis客户端、分布式锁及数据一致性
Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...
- Redis系列(二)--分布式锁、分布式ID简单实现及思路
分布式锁: Redis可以实现分布式锁,只是讨论Redis的实现思路,而真的实现分布式锁,Zookeeper更加可靠 为什么使用分布式锁: 单机环境下只存在多线程,通过同步操作就可以实现对并发环境的安 ...
- 在redis上实现分布式锁
/** *在redis上实现分布式锁 */ class RedisLock { private $redisString; private $lockedNames = []; public func ...
- 如何用redis正确实现分布式锁?
先把结论抛出来:redis无法正确实现分布式锁!即使是redis单节点也不行!redis的所谓分布式锁无法用在对锁要求严格的场景下,比如:同一个时间点只能有一个客户端获取锁. 首先来看下单节点下一般r ...
- redis系列:分布式锁
redis系列:分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
- 一般实现分布式锁都有哪些方式?使用redis如何设计分布式锁?使用zk来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?
#(1)redis分布式锁 官方叫做RedLock算法,是redis官方支持的分布式锁算法. 这个分布式锁有3个重要的考量点,互斥(只能有一个客户端获取锁),不能死锁,容错(大部分redis节点创建了 ...
随机推荐
- Blazor 组件入门指南
翻译自 Waqas Anwar 2021年3月19日的文章 <A Beginner's Guide to Blazor Components> [1] Blazor 应用程序是组件的组合, ...
- java_线程创建的三种方式及区别
java中关于线程的创建有三种: (1)通过继承Thread类创建线程. (2)通过实现Runnable接口创建线程. (3)通过Callable 和 Future 接口创建线程. * * * * * ...
- php curl处理get和post请求
CURL 是一个利用URL语法规定来传输文件和数据的工具,支持很多协议,如HTTP.FTP.TELNET等.最爽的是,PHP也支持 CURL 库.使用PHP的CURL 库可以简单和有效地去抓网页.你只 ...
- kong的管理UI选择-konga
目录 npm方式安装 1. 准备依赖环境 2. 安装konga 3. 配置 4. 环境变量(more) 5. 数据库 配置 初始化/迁移 6. 运行 Docker方式安装 关于Kong-Dashboa ...
- Adaptive AUTOSAR 学习笔记 6 - 架构 - 方法论和 Manifest
本系列学习笔记基于 AUTOSAR Adaptive Platform 官方文档 R20-11 版本 AUTOSAR_EXP_PlatformDesign.pdf 缩写 AP:AUTOSAR Adap ...
- Django基础013--redis开发
1.redis配置 在settings.py中加入以下代码块,可支持多个redis的配置 1 CACHES = { 2 "default": { 3 "BACKEND&q ...
- 实验 1 Linux 系统的安装和常用命令
实验 1 Linux 系统的安装和常用命令 (题目) 一.实验目的 (1)掌握 Linux 虚拟机的安装方法.Spark 和 Hadoop 等大数据软件在 Linux 操作系统 上运行可以发挥最佳性能 ...
- airodump-ng的使用及显示
PWR 表示所接收的信号的强度.表示为负数,数值赿大表示信号赿强.(绝对值赿大,数据赿值小) beacons 表示网卡接收到的AP发出的信号个数
- Mybatis order by 动态传参出现的一个小bug
大家好,我是老三,一个平平无奇的CRUD仔. 今天,我正在愉快地CRUD,突然发现出现一个Bug,我们来看看是怎么回事吧! 问题由来 一个简单的需求,要求把和当前用户相关的数据置顶展示. 这里,我用了 ...
- Lesson 12 Life on a desert island
Lesson 12 Life on a desert island desert island ['dezət 'ailənd] n. 荒岛 uninhabited island coral isla ...