小白de技術哲學(Redis) - 分佈式鎖

一、前言

在談 分佈式鎖 之前不得不聊下 線程鎖進程鎖

  • 線程鎖:在編程時給方法、代碼塊加鎖,使得在同一時刻只能有一線程執行此方法、代碼塊保證線程安全。
  • 進程鎖:控制在同一操作系統中多個進程同時訪問一個共享資源,只是因爲程序的獨立性。

(一)介紹

分佈式鎖是當多個進程不在統一操作系統中,控制分佈式系統或不同系統之間共同訪問共享資源的一種鎖實現,如果不同的系統或同一個系統的不同主機之間共享了某個資源時,往往需要互斥來防止彼此干擾來保證一致性。

(二)分佈式鎖需要具備哪些條件

  1. 互斥性:在任意一個時刻,只有一個客戶端持有鎖。
  2. 無死鎖:即便持有鎖的客戶端崩潰或者其他意外事件,鎖仍然可以被獲取。
  3. 容錯:只要大部分 Redis 節點都活着,客戶端就可以獲取和釋放鎖

(三)分佈式鎖的實現有哪些?

  • 數據庫
  • Redissetnx 命令)
  • Memcachedadd 命令)

二、實現

在實現之前筆者也是網上參考了一些成熟方案的實現,基本都是用 Redis 實現,畢竟 redis的高性能是有目共睹的,ok,講下實現的一個思路:

  1. 獲取鎖的時候,使用 setnx 添加一個鎖,並使用 expire 命令給鎖加個超時時間,超過該時間自動釋放,鎖的 value 值爲隨機生成 Guid ,釋放鎖時驗證。
  2. 當不需要鎖時,要進行釋放鎖,釋放時需要驗證鎖的 value 值釋放正確,避免釋放了別人的鎖。

ps:

  1. 超時時間是避免程序奔潰導致死鎖,別的進程無法正常獲取鎖。
  2. 不論獲取鎖、釋放鎖都必須具備 原子性,避免死鎖的可能,比如:獲取鎖,分步驟先setnx 加鎖,再使用 expire 加超時時間,那就很有可能還沒設置超時時間之前系統宕機導致死鎖。

考慮原子性問題,直接使用 Lua 腳本:

1 獲取鎖

local gva = redis.call('GET', KEYS[1])
if gva == ARGV[1] then
  local ttlva = redis.call('PTTL', KEYS[1])
  redis.call('PEXPIRE', KEYS[1], ARGV[2] + ttlva)
  return 1
end
return 0

2 釋放鎖

local gva = redis.call('GET', KEYS[1])
if gva == ARGV[1] then
  redis.call('DEL', KEYS[1])
  return 1
end
return 0

Lua 語法還是比較簡單,不需要具體的學習也能看個大概。

三、總結

  1. 獲取鎖、釋放鎖操作分別都需要具備 原子性
  2. 加鎖時 key相同、value不同。
  3. 釋放鎖時,根據value判斷,是否是自己的鎖,不能是否他人鎖。
  4. 業務結束時,及時釋放鎖,不能利用超時才進行釋放鎖。
  5. 鎖的超時時間一定要結合業務實際情況設置,不能過長、也不能太短。
  6. 程序出異常時,結合 try{...}catch{...} 捕獲並釋放鎖,如果有必要業務需做回滾,補償動作,保證程序的健壯。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章