python基於redis實現分佈式鎖

閱讀目錄redis

  1.   什麼事分佈式鎖
  2.   基於redis實現分佈式鎖

 

1、什麼是分佈式鎖

咱們在開發應用的時候,若是須要對某一個共享變量進行多線程同步訪問的時候,可使用咱們學到的鎖進行處理,而且能夠完美的運行,毫無Bug!

注意這是單機應用,後來業務發展,須要作集羣,一個應用須要部署到幾臺機器上而後作負載均衡,大體以下圖:服務器

 

 

上圖能夠看到,變量A存在三個服務器內存中(這個變量A主要體現是在一個類中的一個成員變量,是一個有狀態的對象),若是不加任何控制的話,變量A同時都會在分配一塊內存,三個請求發過來同時對這個變量操做,顯然結果是不對的!即便不是同時發過來,三個請求分別操做三個不一樣內存區域的數據,變量A之間不存在共享,也不具備可見性,處理的結果也是不對的!

若是咱們業務中確實存在這個場景的話,咱們就須要一種方法解決這個問題!

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

分佈式鎖應該具有哪些條件:併發

  一、在分佈式系統環境下,一個方法在同一時間只能被一個機器的一個線程執行;
  二、高可用的獲取鎖與釋放鎖;
  三、高性能的獲取鎖與釋放鎖;
  四、具有可重入特性;
  五、具有鎖失效機制,防止死鎖;
  六、具有非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗負載均衡

2、基於redis實現分佈式鎖

一、選用Redis實現分佈式鎖緣由:

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

二、使用命令介紹:

(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實現分佈式鎖的時候,主要就會使用到這三個命令。

三、實現思想:

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

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

(3)釋放鎖的時候,經過UUID判斷是否是該鎖,如果該鎖,則執行delete進行鎖釋放。分佈式

#鏈接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

if __name__ == __main__:
    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()
相關文章
相關標籤/搜索