Redis CAS樂觀鎖實現

QIANGLU0發表於2017-09-13

隨著業務量的增大,系統必然遇到了併發資源搶佔的問題,也就引發了分散式鎖的討論。在實現了ZK鎖後,雖然解決了部分問題,但總感覺還有更好的方法(Redis鎖效能肯定是比ZK高的,在這裡就不討論了)。所以藉助於CAS理論和Redis實現無鎖併發的念想就慢慢滋生了。順便讀了下Redis官方文件和Redis設計與實現發現Redis已經實現了CAS的操作,也就是我們所說的偽事物。

現狀

先說下目前業務現狀,目前有個動態派工師傅資源搶佔的問題。目前使用的是ZK鎖加資料庫操作,凸顯的問題就是業務漏斗式篩選時間過長,導致資料庫鎖等待增多。在高併發下這簡直就是致命的問題。

優化方案

1、將師傅資料從目前的資料庫操作轉移到Redis快取操作,目的減少資料庫壓力,解決鎖等待資源佔用問題。
2、使用樂觀鎖替代目前的ZK鎖,提高單體併發能力。

實現方案

樂觀鎖實現使用Redis 自有的watch multi exec等命令進行封裝
總體就是一句話概括,使用了相關命令就實現了CAS操作

下面詳細介紹下Redis的CAS方案
說起Redis的CAS就必須要說到它的事物了

Redis事物
Redis 通過MULTI 、EXEC、WATCH等命令來實現事物功能。事物提供了一種將多個命令請求打包,然後一次性、按順序的執行多個命令的機制,並且在事物執行期間,伺服器不會中斷事物而去執行其他客戶端的命令請求,它會將事物中所有的命令都執行完畢。Redis事物不支援回滾操作,所以事物佇列中某個命令執行錯誤,整個事物也會繼續執行下去。

事物階段
- 開始事物
MULTI 代表將客戶端的REDIS__MULTI選項開啟,使客戶端進入事物狀態執行命令返回QUEUED 代表命令已經進入事物佇列了,EXEC命令將這個事物提交給伺服器執行.
- 命令入隊
當Redis處理一個請求時, 其實並不是所有的命令都會被放進事務佇列, 其中的例外就是 EXEC 、 DISCARD 、 MULTI 和 WATCH 這四個命令 。當這四個命令從客戶端傳送到伺服器 時, 它們會像客戶端處於非事務狀態一樣, 直接被伺服器執行。每個Redis客戶端都有自己的事物狀態,這個事物狀態儲存在客戶端狀態的mstate屬性裡面。

流轉圖
- 提交事務
一個處於事物狀態的客戶端向伺服器傳送EXEC命令時,這個EXEC命令將立即被伺服器執行。伺服器會遍歷這個客戶端的事物佇列,執行佇列儲存的所有命令,最後統一返回所有的執行結果。 執行是有序的按照先進先出(FIFO)的順序執行機制。事務在執行過程中不會被中斷,所有命令命令執行之後,事務才結束。

WATCH命令實現
WATCH命令就是一個樂觀鎖,它可以在EXEC命令執行之前,監視任意數量的資料庫鍵,並在EXEC命令執行時,檢查被監視的鍵是否被修改了,如果被修改了伺服器將拒絕執行事物,並向客戶端返回空。

事物執行

通過此圖會發現事物期間,其他客戶端如果執行了相同的key操作,將會被忽略

CAS執行
T4時間客戶端二修改了testkey值,當T6階段客戶端一執行EXEC命令時,伺服器會發現WATCH的鍵已經被修改,因此伺服器拒絕執行客戶端A的事物,並返回空。

WATCH監控
每個Redis資料庫都儲存著一個watched_keys字典來儲存被監控的健,字典值是一個連結串列,連結串列中記錄了所有監視相應鍵的客戶端

watched_keys字典

監控機制觸發
所有對資料庫進行修改的命令,如SET、LPUSH、SADD、ZREM、DEL、FLUSHDB等,在執行之後都會呼叫multi.c/touchWatchKey函式對watched_keys字典進行檢查,檢視是否有客戶端正在監視剛剛被命令修改過的key,有的話touchWatchKey函式會將監視的客戶端的REDIS_DIRTY_CAS標識開啟,表示該客戶端事物安全性已經被破壞。

判斷事物是否安全
當伺服器接收到一個客戶端發來的EXEC命令時,伺服器將會根據這個客戶端是否開啟REDIS_DIRTY_CAS標識來決定是否執行事物

執行流程

總結
- 事物提供了一種將多個命令打包,然後一次性有序(FIFO)執行的機制
- 事物執行過程不會被中斷
- 帶WATCH命令的事物會將客戶端和被監視的鍵在資料庫watched_keys字典中進行關聯,當鍵被修改程式會將所有監視鍵的客戶端REDIS_DIRTY_CAS標識開啟
- 在客戶端提交EXEC命令時,會檢查REDIS_DIRTY_CAS標識,標識開啟事物將不會被執行
- Redis事物具有ACID的特性(當伺服器執行在AOF模式下,並且appendfsync選項值為always時才具有永續性

狍狍的日常生活

相關文章