使用redis设计一个简单的分布式锁

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 原文:使用redis设计一个简单的分布式锁最近看了有关redis的一些东西,了解了redis的一下命令,就记录一下: redis中的setnx命令: 关于redis的操作命令,我们一般会使用set,get等一系列操作,数据结构也有很多,这里我们使用最简单的string来存储锁。
原文: 使用redis设计一个简单的分布式锁

最近看了有关redis的一些东西,了解了redis的一下命令,就记录一下:

redis中的setnx命令:

关于redis的操作命令,我们一般会使用set,get等一系列操作,数据结构也有很多,这里我们使用最简单的string来存储锁。

redis下提供一个setnx命令用来将key值设为value,类似于set功能,但是它和set是有区别的,在于后面的nx,setnx是SET if Not eXists。就是:当且仅当key值不存在的时候,将该key值设置为value。

也就是说使用setnx加入元素的时候,当key值存在的时候,是没有办法加入内容的。

加锁:

下面将使用python控制redis,python控制redis的方式和命令一样,有一个setnx(key, value)方法,通过这个方法可以实现setnx命令的效果。

首先连接redis:

文件connect.py

1 import redis
2 
3 def connect_redis():
4     return redis.Redis(host='127.0.0.1', port=6379)

然后就可以使用setnx加锁了,key对应的value中需要填入相应的值,这里设置Value为一个uuid值。获取到uuid之后,将key和value填入redis,其他的客户端想要访问并获取到锁,也使用setnx方式,key值只要相同就好。那么代码如下:

文件operate.py

1 # 加锁的过程
2 def acquire_lock(conn, lockname):
3     identifier = str(uuid.uuid4())
4     conn.setnx('lock:' + lockname, identifier): 

这样就通过setnx将key值写入了。但是,这样显然是不合理的,客户端可以等待一会再次获取锁,这样,不至于每次请求都会出现问题。那可以设置30秒的时间,让程序在30秒内不停的尝试获取锁,知道30秒的时间过了或者由其他客户端释放了锁。

所以加锁可以变为如下:

 1 # 加锁的过程
 2 def acquire_lock(conn, lockname, args, acquite_timeout = 30):
 3     identifier = str(uuid.uuid4())
 4     end = time.time() + acquite_timeout
 5     while time.time() < end:
 6         # 这里尝试取得锁 setnx 设置-如果不存在的时候才会set
 7         if conn.setnx('lock:' + lockname, identifier): 
 8             # 获得锁之后输出获得锁的‘进程’号
 9             print('获得锁:进程'+ str(args))
10             return identifier
11     return False

这样就可以通过setnx加锁了,这个加锁的过程实际上就是在redis中存入了一个值,之后当其他的客户端再次想要通过这个key加入这个值的时候,发现这个key已经存在就不往进写值了,但是在这30秒内还会不断尝试的去获取锁,也就是不断的尝试写入这个值,一旦key被删除——也就是锁被释放,则该客户端就可以竞争获取锁——进程写入这个值。这种方式就像是操作系统中的自旋锁。

自旋锁

这里先简单介绍一下自旋锁。

和自旋锁对应的还有一种锁,叫做对于互斥锁。

互斥锁:如果资源已经被占用,资源申请者只能进入睡眠状态。

自旋锁:自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

释放锁:

既然锁可以添加,那么也就可以释放。释放锁实际上就是将redis中的数据删除。这里可以使用redis提供的事务流水线去执行。为了保障在执行的时候确确实实释放的所示没有问题的。

代码如下,调用delete()方法——del命令删除redis中这个key的元素。

 1 # 释放锁
 2 def release_lock(conn, lockname, identifier):
 3     pipe = conn.pipeline(True)
 4     lockname = 'lock:' + lockname
 5     while True:
 6         try:
 7             pipe.watch(lockname)
 8             identifier_real = pipe.get(lockname)
 9             if identifier_real == identifier:
10                 pipe.multi()
11                 pipe.delete(lockname)
12                 pipe.execute()
13                 return True;
14             pipe.unwatch()
15             break
16         except redis.exceptions.WatchError:
17             pass
18     return False

这里就遇到了python3中的一个坑,获取到的数据为byte类型,所以identifier_real这个变量实际上是这个字符串之前加了个b的,例如b'xxxx',所以这和传入的identifier的类型为string,这样比较当然会出现Fasle,所以我们在这里将这个字符串类型转码:

1 pipe.get(lockname).decode()

这样才是整整的字符串类型的字符串,最终的代码如下:

 1 def release_lock(conn, lockname, identifier):
 2     pipe = conn.pipeline(True)
 3     lockname = 'lock:' + lockname
 4     while True:
 5         try:
 6             pipe.watch(lockname)
 7             identifier_real = pipe.get(lockname).decode()
 8             if identifier_real == identifier:
 9                 pipe.multi()
10                 pipe.delete(lockname)
11                 pipe.execute()
12                 return True;
13             pipe.unwatch()
14             break
15         except redis.exceptions.WatchError:
16             pass
17     return False

执行:

为了验证这个分布式锁的正确性,可以写一个测试的方法进行测试,先去获取锁,如果获取到之后,则sleep三秒,等待3秒之后,执行释放锁。

模拟过程入下:

文件operate.py

1 # 模拟加锁解锁的过程
2 def exec_test(conn, lockname, args):
3     id = acquire_lock(conn, lockname, args)
4     if id != False:
5         print(id)
6         time.sleep(3)
7         release_lock(conn, lockname, id)

这样我们就可以使用多进程并发访问的方式进行对锁进行抢占和释放:

使用9个进程进行测试,操作方式如下:

1 from connect import connect_redis
2 from operate import acquire_lock
3 from multiprocessing import Process
4 from operate import exec_test
5 
6 if __name__ == '__main__':
7     redis = connect_redis()
8     for i in range(0, 9):
9         Process(target = exec_test, args = (redis, 'test', i)).start()

执行结果如下所示:

注:以上python运行环境为python3.6

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
NoSQL 算法 安全
Redlock 算法-主从redis分布式锁主节点宕机锁丢失的问题
Redlock 算法-主从redis分布式锁主节点宕机锁丢失的问题
155 0
|
1月前
|
NoSQL 关系型数据库 MySQL
分布式锁(redis/mysql)
分布式锁(redis/mysql)
59 1
|
30天前
|
NoSQL Java Redis
如何通俗易懂的理解Redis分布式锁
在多线程并发的情况下,我们如何保证一个代码块在同一时间只能由一个线程访问呢?
38 2
|
1月前
|
人工智能 监控 NoSQL
【万字长文 一文搞定】Redis:从新手村到大师殿堂的奥德赛之旅 9种实现分布式锁的全技术指南
【万字长文 一文搞定】Redis:从新手村到大师殿堂的奥德赛之旅 9种实现分布式锁的全技术指南
83 4
|
1月前
|
消息中间件 存储 NoSQL
【Redis项目实战】使用Springcloud整合Redis分布式锁+RabbitMQ技术实现高并发预约管理处理系统
【Redis项目实战】使用Springcloud整合Redis分布式锁+RabbitMQ技术实现高并发预约管理处理系统
|
1月前
|
NoSQL API Redis
Redis分布式锁实现的三个核心
Redis分布式锁实现的三个核心
|
1月前
|
NoSQL Java Redis
Redis分布式锁和Java锁的区别
Redis分布式锁和Java锁的主要区别在于它们的适用范围和实现机制。
41 2
|
NoSQL Java 关系型数据库
浅谈Redis实现分布式锁
浅谈Redis实现分布式锁
|
存储 canal 缓存
|
NoSQL PHP Redis
redis实现分布式锁
redis实现分布式锁
140 0
redis实现分布式锁

热门文章

最新文章