使用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

原文地址:https://www.cnblogs.com/Summer7C/p/8277286.html

时间: 2024-10-12 15:36:47

使用redis设计一个简单的分布式锁的相关文章

利用consul在spring boot中实现最简单的分布式锁

因为在项目实际过程中所采用的是微服务架构,考虑到承载量基本每个相同业务的服务都是多节点部署,所以针对某些资源的访问就不得不用到用到分布式锁了. 这里列举一个最简单的场景,假如有一个智能售货机,由于机器本身的原因不能同一台机器不能同时出两个商品,这就要求在在出货流程前针对同一台机器在同一时刻出现并发 创建订单时只能有一笔订单创建成功,但是订单服务是多节点部署的,所以就不得不用到分布式锁了. 以上只是一种简单的业务场景,在各种大型互联网实际应用中,需要分布式锁的业务场景会更多,综合比较了业界基于各种

Redis中是如何实现分布式锁的?

分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 本地面试考点是,你对Redis使用熟悉吗?Redis中是如何实现分布式锁的. 要点 Redis要实现分布式锁,以下条件应该得到满足 互斥性 在任意时刻,只有一个客户端能持有锁. 不能死锁 客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁. 容错性 只要大部分的Redis节点正常运行,客户端就可以加锁和解锁. 实现 可以直接通过 set key value px mil

常用的分布式锁和redis和zk两种分布式锁的对比

常用的分布式锁 一..基于数据库实现分布式锁 1. 悲观锁 利用select … where … for update 排他锁 注意: 其他附加功能与实现一基本一致,这里需要注意的是“where name=lock ”,name字段必须要走索引,否则会锁表.有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题. 2. 乐观锁 所谓乐观锁与前边最大区别在于基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有update version失败后才

一个简单的分布式爬虫

下载scrapy-redis: https://github.com/rmax/scrapy-redis 下载zip文件之后解压 建立两个批处理文件,start.bat和clear.batstart.bat的内容为redis-server redis.windows.confclear.bat的内容为redis-cli flushdb双击start.bat启动 这样就说明下好了,运行正常. 我们需要构建一个分布式爬虫系统:由一个master爬虫和slave爬虫组成,master端部署了redis

基于zookeeper简单实现分布式锁

这里利用zookeeper的EPHEMERAL_SEQUENTIAL类型节点及watcher机制.来简单实现分布式锁. 主要思想: 1.开启10个线程.在disLocks节点下各自创建名为sub的EPHEMERAL_SEQUENTIAL节点. 2.获取disLocks节点下全部子节点,排序,假设自己的节点编号最小,则获取锁: 3.否则watch排在自己前面的节点,监听到其删除后,进入第2步(又一次检測排序是防止监听的节点发生连接失效.导致的节点删除情况): 4.删除自身sub节点,释放连接: 这

基于redis集群实现的分布式锁,可用于秒杀商品的库存数量管理,有測试代码(何志雄)

转载请标明出处. 在分布式系统中,常常会出现须要竞争同一资源的情况,本代码基于redis3.0.1+jedis2.7.1实现了分布式锁. redis集群的搭建,请见我的另外一篇文章:<><redis3.0.1集群环境搭建> 可用于比如秒杀系统中的商品库存的管理.付完整代码及測试用例. package com.gaojiasoft.gaojiaRedis; import java.util.UUID; import java.util.concurrent.LinkedBlockin

Redis如何实现高并发分布式锁?

众所周知,分布式锁在微服务架构中是重头戏,尤其是在互联网公司,基本上企业内部都会有自己的一套分布式锁开发框架.本文主要介绍使用Redis如何构建高并发分布式锁. 假设 存在一个SpringBoot的控制器,其扣减库存的业务逻辑如下: @Autowired private StringRedisTemplate stringRedisTemplate; @RequestMapping(value = "/deduct-stock") public String deductSotck()

一个简单的线程锁------pthread和win32的临界区(Critical Section)

临界区: 临界区是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权.这是让若干代码能够"以原子操作方式"来使用资源的一种方法. 所谓原子(atomic)操作方式,是指这段代码知道没有别的线程要访问这个资源. 说明: 1.  MacOSX,Windows有自己的线程模型, pthread可以说是跨平台的线程编程模型解决方案,当然对pthread不熟悉的也可以使用本地线程模型, 其实pthread的win32版本也是基于本地线程模型的, pthread-win32的mutex

设计一个简单的mvc映射

在工作时,有一套框架,并不是很适合做后台,为此我在这个框架加了简单的mvc映射 首先为方便可以随时new自己想要的类,注册自己的__autoload方法,这里封装成一个类,当然类里面要封装好查找路径,注册的时候就是调用内部的loader成员方法: spl_autoload_register(array($this, 'loader')); 现在,我在Action文件夹(前面已经会自动加载这个文件夹相应的类文件),. 创建一个抽象类Action_Abstract,里面有抽象方法run,后期有act