前言
上周“被”上线了一个紧急项目,周五下班接到需求,周一开始思考解决方案,周三开发完成,周四走流程上线,也算是面向领导编程了。之前的项目里面由于是自运维,然后大多数又都赶时间,所以在处理定时任务上面基本都是自己在服务器上添加crontab,而不是让多个实例自己去处理定时任务的并发锁,并且Laravel 5.5开始自带并发锁,我们也快升级了。但是这次项目是Python项目,无奈只能自己实现一下,以下这个方案实现起来非常简单且易于理解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import redis r = redis.Redis(...) last_heart = 0 # 记录上一次得到的锁心跳 free_lock_try = 6 # 锁无心跳的最大次数 while not r.setnx( 'mylock' , 1 ): now_heart = r.get( 'mylock' ) print(f "没获取到锁,now_heart={now_heart},last_heart={last_heart},free_lock_try={free_lock_try}" ) if now_heart == last_heart: free_lock_try = free_lock_try - 1 if free_lock_try == 0 : # 锁已经 1 分钟没有心跳了 old_heart = r.getset( 'mylock' , 1 ) # 将lock重置为 1 ,并返回set之前的心跳值 if old_heart < now_heart: time.sleep( 10 ) continue else : break # 成功获取到锁,退出循环 else : free_lock_try = 6 # 锁有心跳,重置free_lock_try值 last_heart = now_heart time.sleep( 10 ) def producer_exit(): "" "程序正常退出时候自动清理锁" "" r.delete( 'mylock' ) import atexit atexit.register(producer_exit) # 业务代码 while True: r.incr( 'mylock' ) # 让锁心跳加一 ... |
我们来看看这段程序都解决了并发锁中的哪些问题
- 高并发下,多个进程无法同时获取到锁。这里使用的是redis.setnx,如果锁已经存在,其他进程是无法重置锁并获取到锁的。另外当多个进程同时发现有锁已经没有心跳了,使用的是redis.getset将心跳重置为1,都能set成功,但是get出来的值多个进程是不一样的,只有真正获取到锁的进程返回的是之前进程的心跳,而其他进程获取到的都是1。
- 有锁进程正常退出,可以使用atexit注册进程退出函数删除锁,这里也可以不要,不过下次启动得等新的进程等待几次心跳
- 有锁进程意外退出,退出后心跳不再增加,超过free_lock_try次数后,其他进程会重新设置并获取锁
- 所有进程全都意外退出,这个问题不是锁来关心的,可以使用supervisor进行守护进程。
导致Redis并发原因解释
正所谓只有知其然才能知其所以然,只有弄明白问题出现的原因所在,才能对症下药,寻找解决问题的良方。众所周知,Redis程序采用单线程模式进行运行,作为单线程程序,Redis客户端的命令是逐条执行,也叫做One by One执行。既然是逐条命令执行,从表面上来看Redis似乎不存在高并发的问题,这一观点论也有道理,原子性的Redis命令本身也确实不存在高并发问题,这与多线程下的程序勃然不同。但是我们项目工作搭建Redis环境之后,通常都会是一组命令集合执行程序,一个请求中就包含了N个Redis执行命令,再加上多个客户端请求,命令就更多了,导致连接超时、数据混乱或错误、请求阻塞等多种问题。
即总结为,产生Redis并发诱因是程序中的业务复杂度导致。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对服务器之家的支持。
原文链接:https://haofly.net/redis-lock-handle-concurrency/