Redis分布式鎖服務(八)

來源:互聯網
上載者:User

標籤:

閱讀目錄:

  1. 概述
  2. 分布式鎖
  3. 多執行個體分布式鎖
  4. 總結
概述

在多線程環境下,通常會使用鎖來保證有且只有一個線程來操作共用資源。比如:

object obj = new object();lock (obj) { //操作共用資源 }

利用作業系統提供的鎖機制,可以確保多線程或多進程下的並發唯一操作。但如果在多機環境下就不能滿足了,當A,B兩台機器同時操作C機器的共用資源時,就需要第三方的鎖機制來保證在分布式環境下的資源協調,也稱分布式鎖。

Redis有三個最基本屬性來保證分布式鎖的有效實現:

  • 安全性: 互斥,在任何時候,只有一個用戶端能持有鎖。
  • 活躍性A:沒有死結,即使用戶端在持有鎖的時候崩潰,最後也會有其他用戶端能獲得鎖,逾時機制。
  • 活躍性B:故障容忍,只有大多數Redis節點時存活的,用戶端仍可以獲得鎖和釋放鎖。
分布式鎖

由於Redis是單執行緒模式,命令操作原子性,所以利用這個特性可以很容易的實現分布式鎖。 獲得一個鎖

SET key uuid NX PX timeoutSET resource_name uniqueVal NX PX 30000

命令中的NX表示如果key不存在就添加,存在則直接返回。PX表示以毫秒為單位設定key的到期時間,這裡是30000ms。 設定到期時間是防止獲得鎖的用戶端突然崩潰掉或其他異常情況,導致redis中的對象鎖一直無法釋放,造成死結。
Key的值需要在所有請求鎖服務的用戶端中,確保是個唯一值。 這是為了保證拿到鎖的用戶端能安全釋放鎖,防止這個鎖對象被其他用戶端刪除。
舉個例子:

  1. A用戶端拿到對象鎖,但在因為一些原因被阻塞導致無法及時釋放鎖。
  2. 因為到期時間已到,Redis中的鎖對象被刪除。
  3. B用戶端請求擷取鎖成功。
  4. A用戶端此時阻塞操作完成,刪除key釋放鎖。
  5. C用戶端請求擷取鎖成功。
  6. 這時B、C都拿到了鎖,因此分布式鎖失效。

要避免例子中的情況發生,就要保證key的值是唯一的,只有拿到鎖的用戶端才能進行刪除。 基於這個原因,普通的del命令是不能滿足要求的,我們需要一個能判斷用戶端傳過來的value和鎖對象的value是否一樣的命令。遺憾的是Redis並沒有這樣的命令,但可以通過Lua指令碼來完成:

if redis.call("get",KEYS[1]) == ARGV[1] then    return redis.call("del",KEYS[1]) else 
return 0 end

邏輯很簡單,擷取key中的值和參數中的值相比較,相等刪除,不相等返回0。

多執行個體分布式鎖

上面是在單個Redis執行個體實現分布式鎖的,這存在一個問題就是,如果這台執行個體因某些原因崩潰掉,那麼所有用戶端的鎖服務全部失效。
Redis本身支援Master-Slave結構,可以一主多從,採用高可用方法,可以保證在master掛的時候自動切換到slave。 但是由於主從之間是非同步同步資料的,所以redis並不能完全的實現鎖的安全性。 舉個例子來說:

  1. A用戶端在master執行個體上獲得一個鎖。
  2. 在對象鎖key傳送到slave之前,master崩潰掉。
  3. 一個slave被選舉成master。
  4. B用戶端可以擷取到同個key的鎖,但A也已經拿到鎖,導致鎖失效。

在多台master情況下實現這個演算法,並保證鎖的安全性。 步驟如下:

  1. 用戶端以毫秒為單位擷取目前時間。
  2. 使用同樣key和值,迴圈在多個執行個體中獲得鎖。 為了獲得鎖,用戶端應該設定個位移時間,它小於鎖自動釋放時間(即key的到期時間)。 舉個例子來說,如果一個鎖自動釋放時間是10秒,那位移時間應該設定在5~50毫秒的範圍。 防止因為某個執行個體崩潰掉或其他原因,導致client在擷取鎖時耗時過長。
  3. 計算擷取所有鎖的耗時,即目前時間減去開始時間,得到a值。 用鎖自動釋放時間減去a值,在減去位移時間,得到c值,如果擷取鎖成功的執行個體數量大於實際的數量一半,並且c大於0,那麼鎖就被擷取成功。
  4. 鎖擷取成功,鎖對象的有效時間是上面的c值。
  5. 若是用戶端因為一些原因擷取失敗,原因可能是上面的c值為負數或者鎖成功的數量小於執行個體數,以用N/2+1當標準(N為執行個體數)。 那麼會釋放所有執行個體上的鎖。

上面描述可能不方便理解,用代碼錶示如下:

//鎖自動釋放時間TimeSpan ttl=new TimeSpan(0,0,0,30000)//擷取鎖成功的數量 int n = 0; //記錄開始時間 var startTime = DateTime.Now;  //在每個執行個體上擷取鎖                for_each_redis(                    redis =>                    {                        if (LockInstance(redis, resource, val, ttl)) n += 1;                    }                );//位移時間是鎖自動釋放時間的1%,根據上面10s是5-50毫秒推出。 var drift = Convert.ToInt32(ttl.TotalMilliseconds * 0.01); //鎖對象的有效時間=鎖自動釋放時間-(目前時間-開始時間)-位移時間 var validity_time = ttl - (DateTime.Now - startTime) - new TimeSpan(0, 0, 0, 0, drift);//判斷成功的數量和有效時間c值是否大於0 if (n >= (N/2+1) && validity_time.TotalMilliseconds > 0) { }
總結

用Redis做分布式鎖相比其他分布式鎖(zookeeper)實現更簡單,速度更快。
在ServiceStack.Redis用戶端組件上是直接支援鎖實現的。
或者用stackexchange用戶端組件,鎖實現及範例程式碼:https://github.com/kidfashion/redlock-cs。
官方介紹文檔:http://redis.io/topics/distlock。

http://www.cnblogs.com/mushroom/p/4752499.html

Redis分布式鎖服務(八)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.