1. 程式人生 > >Redis事務機制和分散式鎖

Redis事務機制和分散式鎖

事務機制

嚴格意義來講,Redis的事務和我們理解的傳統資料庫(如mysql)的事務是不一樣的;Redis的事務實質上是命令的集合,在一個事務中要麼所有命令都被執行,要麼所有事物都不執行。  一個事務從開始到執行會經歷以下三個階段:

開始事務。 命令入隊。 執行事務。 在MySQL中我們使用START TRANSACTION 或 BEGIN開啟一個事務,使用COMMIT提交一個事務;而在Redis中我們使用MULTI 開始一個事務,由 EXEC 命令觸發事務, 一併執行事務中的所有命令。

可以看到,MULTI 開始到 EXEC結束前,中間所有的命令都被加入到一個命令佇列中;當執行 EXEC命令後,將QUEUE中所有的命令執行。

此外我們可以使用DISCARD取消事務。

需要注意的是:  1.Redis的事務沒有關係資料庫事務提供的回滾(rollback),所以開發者必須在事務執行失敗後進行後續的處理;  2.如果在一個事務中的命令出現錯誤,那麼所有的命令都不會執行;  3.如果在一個事務中出現執行錯誤,那麼正確的命令會被執行。

WATCH

研究過java的J.U.C包的人應該都知道CAS,CAS是一種保證原子性的操作。那麼redis也提供了這樣的一個機制,就是利用watch命令來實現的。

WATCH命令可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之後的事務就不會執行,監控一直持續到EXEC命令。

分散式鎖 什麼是分散式鎖?

分散式鎖是控制分散式系統之間同步訪問共享資源的一種方式。如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源,那麼訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分散式鎖。

實現分散式鎖有很多實現方式和工具,如Zookeeper、Redis等。

使用Redis實現分散式鎖原理:

Redis為單程序單執行緒模式,採用佇列模式將併發訪問變成序列訪問,且多客戶端對Redis的連線並不存在競爭關係,基於此,Redis中可以使用SETNX命令實現分散式鎖。

SETNX——SET if Not eXists(如果不存在,則設定):

setnx key value

將 key 的值設為 value ,當且僅當 key 不存在。  若給定的 key 已經存在,則 SETNX 不做任何動作。

如果需要解鎖,使用 del key 命令就能釋放鎖:

這裡寫圖片描述

左圖首先使用setnx對鍵加鎖成功返回1,右圖再次使用setnx命令對鍵加鎖失敗返回0,說明有客戶端持有鎖。使用del釋放鎖以後,右圖就可以使用setnx命令對鍵加鎖。

解決死鎖 如果一個持有鎖的客戶端失敗或崩潰了不能釋放鎖,該怎麼解決?

答:給鎖設定一個過期時間,可以通過兩種方法實現:通過命令 “setnx 鍵名 過期時間 “;或者通過設定鎖的expire時間,讓Redis去刪除鎖。

第一種實現方式:  使用 setnx key “當前系統時間+鎖持有的時間”和getset key “當前系統時間+鎖持有的時間”組合的命令就可以實現。  具體做法如下:

客戶端2傳送SETNX lock.test 想要獲得鎖,由於之前的客戶端1還持有鎖,所以Redis返回一個0  客戶端2傳送GET lock.test 以檢查鎖是否超時了,如果沒超時,則等待或重試。  反之,如果已超時,客戶端2通過下面的操作來嘗試獲得鎖:  GETSET lock.test 過期的時間  通過GETSET,客戶端2拿到的時間戳如果仍然是超時的,那就說明,客戶端2如願以償拿到鎖了。  如果在客戶端2之前,有個客戶端3比客戶端2快一步執行了上面的操作,那麼客戶端2拿到的時間戳是個未超時的值,這時,說明客戶端2沒有如期獲得鎖,需要再次等待或重試。  儘管客戶端2沒拿到鎖,但它改寫了客戶端3設定的鎖的超時值,不過這一點非常微小的誤差帶來的影響可以忽略不計。

第二種就非常簡單了:  通過Redis中expire()給鎖設定最大持有時間,如果超過,則Redis來幫我們釋放鎖。

1.客戶端1使用setnx獲得了鎖,並且使用expire設定一個過期時間,假定是10ms

2.過了4ms後,客戶端1不幸運的宕機了,此時客戶端2想要通過setnx嘗試獲得鎖,但是鎖還沒有過期,任然被客戶端1所持有。

3.到了11ms時,鎖過期了,Redis幫我們刪除了鎖,此時客戶端2想要通過setnx嘗試獲得鎖,此時就能成功獲得鎖。