基于Redis單例的實現
為了后面更好的了解分布式鎖的實現,我們先來看看如何基于Redis單例實現鎖服務。我們可以用下面方法獲得鎖:
SET resource_name my_random_value NX PX 30000
上面的命令在只有當key不存在的時候會執行成功(NX選項),同時會設置過期時間為30000ms(PX選項)。key的值會被設置為my_random_value。這個值在多個客戶端和鎖中必須是唯一的,我們使用random value是為了方便安全地釋放鎖,看看下面的腳本:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
只有當key存在且值是預期的值的時候才會刪除key。這種方式可以避免誤刪除其他客戶端創建的鎖。例如,當客戶端獲取鎖之后執行一個很長時間的邏輯,一直過了鎖的過期時間,這個時候鎖會被自動釋放掉,而另外一個客戶端又獲取了這個鎖,前一個客戶端終于執行完了邏輯執行,回頭釋放鎖,刪除key,其實這個時候釋放的已經是另外一個客戶端持有的鎖了。使用DEL是不安全的,因為客戶端有可能誤刪其他客戶端持有的鎖。上面腳本的方法的好處是每次獲得鎖的時候加上一個隨機的簽名,當釋放鎖的時候去看看是不是自己持有的鎖,這個時候就不會誤刪。
現在我們學會了如何在Redis單例上獲取鎖和釋放鎖,那么接下來我們看看如何在Redis集群上獲取鎖和釋放鎖。
基于Redlock算法的實現
在分布式環境下,假設我們有N個master,這些節點都是獨立的,因此我們沒有配置復制策略。上面我們已經學會了如何在單機環境下獲取鎖和釋放鎖,我們假設的更具體一些,N=5,為了能獲取鎖,客戶端的步驟為:
- 獲取當前系統的時間,以毫秒為單位。
- 順序的獲取N個Redis實例上的鎖,在每個實例中都用同樣的key和value。在步驟2中,客戶端需要一個比過期時間小很多的超時時間,例如,如果自動過期時間為10s,那么超時時間大概是5~50ms,這樣可以避免客戶端一直被阻塞,而不能繼續請求下一個實例。
- 客戶端每次都要計算已經過去了多長時間,使用的時間是否小于key自動過期的時間同時又獲取了至少3個實例的鎖。如果是,那么我們認為客戶端此次獲取鎖成功。
- 如果鎖被獲取了,鎖的過期時間必須要減去獲取鎖花費的時間。
- 如果當前客戶端獲取鎖失敗,客戶端需要釋放所有之前獲取到的鎖。