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嘗試獲得鎖,此時就能成功獲得鎖。
在實際過程中,我們可以設定一個時間T,用來表示客戶端在初次嘗試獲得鎖失敗以后,在多次嘗試獲得鎖所花的時間。如果次時間為0,表示除此嘗試獲得鎖失敗以后就不會再去嘗試獲得鎖了。