1. 程式人生 > 實用技巧 >python基於redis實現分散式鎖

python基於redis實現分散式鎖

python基於redis實現分散式鎖


一、什麼是分散式鎖

我們在開發應用的時候,如果需要對某一個共享變數進行多執行緒同步訪問的時候,可以使用我們學到的鎖進行處理,並且可以完美的執行,毫無Bug!

注意這是單機應用,後來業務發展,需要做叢集,一個應用需要部署到幾臺機器上然後做負載均衡,大致如下圖:

上圖可以看到,變數A存在三個伺服器記憶體中(這個變數A主要體現是在一個類中的一個成員變數,是一個有狀態的物件),如果不加任何控制的話,變數A同時都會在分配一塊記憶體,三個請求發過來同時對這個變數操作,顯然結果是不對的!即使不是同時發過來,三個請求分別操作三個不同記憶體區域的資料,變數A之間不存在共享,也不具有可見性,處理的結果也是不對的!


如果我們業務中確實存在這個場景的話,我們就需要一種方法解決這個問題!

為了保證一個方法或屬性在高併發情況下的同一時間只能被同一個執行緒執行,在傳統單體應用單機部署的情況下,可以使用併發處理相關的功能進行互斥控制。但是,隨著業務發展的需要,原單體單機部署的系統被演化成分散式集群系統後,由於分散式系統多執行緒、多程序並且分佈在不同機器上,這將使原單機部署情況下的併發控制鎖策略失效,單純的應用並不能提供分散式鎖的能力。為了解決這個問題就需要一種跨機器的互斥機制來控制共享資源的訪問,這就是分散式鎖要解決的問題!

分散式鎖應該具備哪些條件:

1、在分散式系統環境下,一個方法在同一時間只能被一個機器的一個執行緒執行;

2、高可用的獲取鎖與釋放鎖;
3、高效能的獲取鎖與釋放鎖;
4、具備可重入特性;
5、具備鎖失效機制,防止死鎖;
6、具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗

回到頂部

二、基於redis實現分散式鎖

1、選用Redis實現分散式鎖原因:

(1)Redis有很高的效能;
(2)Redis命令對此支援較好,實現起來比較方便

2、使用命令介紹:

(1)SETNX

SETNX key val:當且僅當key不存在時,set一個key為val的字串,返回1;若key存在,則什麼都不做,返回0。

(2)expire

expire key timeout:為key設定一個超時時間,單位為second,超過這個時間鎖會自動釋放,避免死鎖。


(3)delete

delete key:刪除key

在使用Redis實現分散式鎖的時候,主要就會使用到這三個命令。

3、實現思想:

(1)獲取鎖的時候,使用setnx加鎖,並使用expire命令為鎖新增一個超時時間,超過該時間則自動釋放鎖,鎖的value值為一個隨機生成的UUID,通過此在釋放鎖的時候進行判斷。

(2)獲取鎖的時候還設定一個獲取的超時時間,若超過這個時間則放棄獲取鎖。

(3)釋放鎖的時候,通過UUID判斷是不是該鎖,若是該鎖,則執行delete進行鎖釋放。

4、 分散式鎖的簡單實現程式碼:

#連線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

5、測試剛才實現的分散式鎖

例子中使用50個執行緒模擬秒殺10張票,使用–運算子來實現商品減少,從結果有序性就可以看出是否為加鎖狀態。

count=10

def seckill(i):
    identifier=acquire_lock('resource')
    print("執行緒:{}--獲得了鎖".format(i))
    time.sleep(1)
    global count
    if count<1:
        print("執行緒:{}--沒搶到,票搶完了".format(i))
        return
    count-=1
    print("執行緒:{}--搶到一張票,還剩{}張票".format(i,count))
    release_lock('resource',identifier)


for i in range(50):
    t = Thread(target=seckill,args=(i,))
    t.start()