NodeJS 基於redis的分佈式鎖的實現(Redlock算法)

1. 前言

開發時,碰到互斥問題,須要保證在分佈式環境下,避免重複性操做修改用戶狀態,如:用戶訂單狀態,購票時,修改票的餘額等javascript

2. 分佈式鎖的條件

  • 分佈式鎖須要知足下列條件
    • 鎖須要有充足的可訪問的存儲空間
    • 鎖必須被惟一標識
    • 鎖至少要有兩種狀態
  • 同時,要保證
    • 安全特性:互斥訪問,永遠只有一個client能拿到鎖
    • 避免死鎖:client最後能夠拿到鎖,不會出現死鎖,即便本來上鎖的client出現問題沒法解鎖
    • 容錯性:容錯,只要大多數redis節點可以正常工做,客戶端端都能獲取和釋放鎖。

3.Redis單節點上鎖的實現

  • 使用下列語句獲取一個不存在的key,若是能夠已存在則建立失敗,確保key值惟一, 加上過時時間,確保系統錯誤後及時解鎖,避免死鎖
SET key value NX PX 30000
複製代碼

可是這種方法在主庫錯誤時,會發生錯誤,redis主從同步是異步,主庫錯誤時,從庫若尚未鎖的信息,則會致使多個進程持有鎖java

3. Redlock算法(官方文檔

在分佈式版本的算法裏咱們假設咱們有N個Redis master節點,這些節點都是徹底獨立的,咱們不用任何複製或者其餘隱含的分佈式協調算法。咱們已經描述瞭如何在單節點環境下安全地獲取和釋放鎖。所以咱們理所固然地應當用這個方法在每一個單節點裏來獲取和釋放鎖。在咱們的例子裏面咱們把N設成5,這個數字是一個相對比較合理的數值,所以咱們須要在不一樣的計算機或者虛擬機上運行5個master節點來保證他們大多數狀況下都不會同時宕機。一個客戶端須要作以下操做來獲取鎖:node

  • 獲取當前時間(單位是毫秒)。
  • 輪流用相同的key和隨機值在N個節點上請求鎖,在這一步裏,客戶端在每一個master上請求鎖時,會有一個和總的鎖釋放時間相比小的多的超時時間。好比若是鎖自動釋放時間是10秒鐘,那每一個節點鎖請求的超時時間多是5-50毫秒的範圍,這個能夠防止一個客戶端在某個宕掉的master節點上阻塞過長時間,若是一個master節點不可用了,咱們應該儘快嘗試下一個master節點。
  • 客戶端計算第二步中獲取鎖所花的時間,只有當客戶端在大多數master節點上成功獲取了鎖(在這裏是3個),並且總共消耗的時間不超過鎖釋放時間,這個鎖就認爲是獲取成功了。
  • 若是鎖獲取成功了,那如今鎖自動釋放時間就是最初的鎖釋放時間減去以前獲取鎖所消耗的時間。
  • 若是鎖獲取失敗了,不論是由於獲取成功的鎖不超過一半(N/2+1)仍是由於總消耗時間超過了鎖釋放時間,客戶端都會到每一個master節點上釋放鎖,即使是那些他認爲沒有獲取成功的鎖。

4.使用示例

  • 首先安裝redis庫與redlock庫(官方庫,在git上名叫node-redlock),在node項目中,直接使用npm或者yarn下載安裝便可,redis庫我使用的是ioredis(直接安裝redis庫也可)
npm i --save ioredis
  npm i --save redlock
複製代碼
/** 請求鎖 */
Redlock.prototype.lock(resource, ttl, ?callback) 
// resource鎖的名稱
// ttl鎖的有效期
// callback 回調函數,當使用promise的寫法的時候,能夠填寫這個參數
/** 釋放鎖 */
Redlock.prototype.unlock(lock, ?callback)
/** 延長鎖的有效時間 */
Lock.prototype.extend(lock, ttl, ?callback)
// 都支持callback、promise、yield寫法
複製代碼
  • 代碼示例
const ioredis = require('ioredis')
const Redlock = require('redlock')
const client = new ioredis(REDIS_SERVER)
const redlock = new Redlock([client])
co(function* () {
   while (true) {
       let lock = null
       try {
           lock = yield redlock.lock('lock', 1000) // 這種寫法取不到鎖時會直接拋出錯誤
       } catch (error) {
           lock = null
       }
       yield sleep(30 * 1000)
       // 處理邏輯
       lock.unlock()
   }
})
複製代碼
相關文章
相關標籤/搜索