需求
實現一個分佈式鎖
問題
- 分佈式鎖與傳統的鎖有和區別
關於這個問題相關的解釋不是很多,沒看到官方解釋和對比;民間的解釋是這樣的,我們所說的傳統的鎖,一般指的是線程鎖,而分佈式鎖指的是進程鎖。
個人對民間的這種稱呼,不是很認同。單主機下的進程鎖,和多主機下的進程鎖(分佈式鎖)應該還是區別,至少實現上來說多主機鎖,需要比單主機進程所多考慮一點跨主機的網絡通信問題。這個網絡通信是對進程鎖的設計,在不同的場景下,還是有不同的區別的。
我的思路
- 其實我開始想到的第一個點就是,我們現在使用的鎖,利用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