需求
实现一个分布式锁
问题
- 分布式锁与传统的锁有和区别
关于这个问题相关的解释不是很多,没看到官方解释和对比;民间的解释是这样的,我们所说的传统的锁,一般指的是线程锁,而分布式锁指的是进程锁。
个人对民间的这种称呼,不是很认同。单主机下的进程锁,和多主机下的进程锁(分布式锁)应该还是区别,至少实现上来说多主机锁,需要比单主机进程所多考虑一点跨主机的网络通信问题。这个网络通信是对进程锁的设计,在不同的场景下,还是有不同的区别的。
我的思路
- 其实我开始想到的第一个点就是,我们现在使用的锁,利用python的fcntl库(文件锁)实现的一种锁,支持区分读写锁、阻塞锁、非阻塞锁;文件锁属于进程锁。
- 我们的web程序是多进程的,Apache开的多个httpd进程,多个进程竞争自愿。使用文件锁能够满足普通的需求。
- 当我被别人问起,分布式锁的时候,我就很诧异,那我用这个本身不就能当作一个分布式锁来用么,不过需要加一些网络接口(比如restful api),读完接下来下面业界常用的实现方法,再来看我自己的这个疑问。
业界常用做法
今天只说一种最常用的,利用redis实现的分布式锁,redis本身(redis操作)的原子性,对锁的支持是天然的。
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,a.通过UUID判断是不是该锁,若是该锁,b.则执行delete进行锁释放。(注意ab操作要保证原子性,否则,可能存在这样的情况,执行步骤a------>锁超时释放--------->另一个任务加锁成功----------->执行步骤b;这样的后果,就是锁的添加释放错乱了;可以利用redis的事务实现多个操作的原子性)
代码实现
#连接redis
import time
import uuid
from threading import Thread
import redis
redis_client = redis.Redis(host="localhost",
port=6379,
# password=123,
db=10)
#获取一个锁
# lock_name:锁定名称
# acquire_time: 客户端等待获取锁的时间
# time_out: 锁的超时时间
def acquire_lock(lock_name, acquire_time=10, time_out=10):
"""获取一个分布式锁"""
identifier = str(uuid.uuid4())
end = time.time() + acquire_time
lock = "string:lock:" + lock_name
while time.time() < end:
if redis_client.setnx(lock, identifier):
# 给锁设置超时时间, 防止进程崩溃导致其他进程无法获取锁
redis_client.expire(lock, time_out)
return identifier
elif not redis_client.ttl(lock):
redis_client.expire(lock, time_out)
time.sleep(0.001)
return False
#释放一个锁
def release_lock(lock_name, identifier):
"""通用的锁释放函数"""
lock = "string:lock:" + lock_name
pip = redis_client.pipeline(True)
while True:
try:
pip.watch(lock)
lock_value = redis_client.get(lock)
if not lock_value:
return True
if lock_value.decode() == identifier:
pip.multi()
pip.delete(lock)
pip.execute()
return True
pip.unwatch()
break
except redis.excetions.WacthcError:
pass
return False