在單機的Redis
集羣下,想要實現針對多個key
的複雜原子操做有兩種方法。一種是Watch+Multi
,即監視器加事務方式,另外一種即是經過執行lua
腳本實現。redis
這裏所說的複雜原子性操做好比,扣減某商品的5
個庫存,須要先判斷當前商品的剩餘庫存是否足夠扣減。可是避免不了在判斷足夠的狀況下,再去執行扣減操做時,這個期間庫存沒有被別人修改的狀況。數據庫
Watch
能夠監控多個key
,被監聽的多個key
,只要被監聽的key
其中有一個key
被修改,那麼全部操做都不會被執行,不只僅是對被監聽的key
的操做。Watch
並不能單獨使用,而是要結合事務使用。緩存
接着咱們看下,在Watch
以後,事務執行以前,若是key
被修改,會是什麼狀況。安全
這很好理解,即使事務中各命令的順序不同,由於單線程執行命令的緣由,exec
執行時就已經知道被監聽的哪些key
被修改過了,它只是不執行事務了。服務器
每一個Redis
數據庫中都保存着一個watched_keys
的字典,這個字典的鍵是某個被Watch
命令監視的鍵,如上面例子中的s1
、 s2
,而字典的值則是一個鏈表,鏈表中記錄全部監視該鍵的客戶端,即一個鏈接。多線程
全部寫命令,在執行以後都會對watched_keys
字典進行檢查,查看是否有客戶端正在監視剛剛寫的鍵,若是有,將監視該鍵的客戶端的REDIS_DIRTY_CAS
標識打開,表示該客戶端的事務安全性已經被破壞。在客戶端(一個鏈接)提交(exec
)事務執行時,先檢測該標識是否被打開,若是是,則會拒絕當前客戶端執行提交的事務。分佈式
假設在Cluster
集羣下執行,會是什麼結果呢?我在個人服務器上部署了一個cluster
集羣,這是很早以前就部署了的。來看下在Cluster
集羣下執行Watch
監聽多個key
會怎樣。性能
能夠看到,在Watch
的時候就出錯了,請求不容許key
在不一樣的slot
。即使slot
不一樣但都在同一個節點也是不行的,必須是同一個slot
。下圖中,s4
和s5
這兩個key
是在同一個節點上的。ui
Watch
是真的嚴格,不一樣slot
都不行。有沒有想過爲何監聽多個落在不一樣節點上的key
,會不被容許?在單節點下,Redis
單線程執行,可以保證原子性,但在不一樣節點下,就是多進程多線程的問題,Watch
天然就不能用。lua
Redis
執行lua
腳本是原子性操做,與執行Redis
命令同樣對待。原子性操做得益於Redis
執行命令是單線程的,lua
腳本也會放在命令執行的等待隊列中排隊執行,所以也須要特別注意,lua
腳本中不要執行太多代碼,最好不要寫for
循環語句,不然會阻塞住,嚴重致使redis
節點假死。
在主從Redis
集羣與讀寫分離Redis
集羣下,lua
腳本的編寫不須要考慮太多問題,只需考慮腳本耗時問題。但在分槽位的cluster
集羣下,咱們想要經過lua
腳本實現原子性操做,就必需要確保腳本所要操做的key
都在同一個Redis
節點下,即全部key
計算出來的槽位都落到同一個Redis
節點(小集羣中的master
)下,才能保證命令是原子性的。
事實上,若是要操做的key
不在同一節點上,命令執行也會保錯。下圖是Lua
腳本試圖訪問羣集中的非本地節點拋出的錯誤。
即使是在一個事務中執行多個lua
腳本,只要有一個lua
腳本操做的key
落在不一樣的節點,結果都會執行失敗。
並不僅是lua
腳本,由於事務也不支持multi
與exec
之間的命令,操做的key
落在不一樣節點的狀況。
不一樣slot
也不行。
關於商品庫存的問題,不少視頻教程都在講使用分佈式鎖,你可能也會想到使用分佈式鎖,可是用過度布式鎖的都知道性能是個什麼狀況。
若是商品庫存只使用redis
緩存,在不須要修改數據庫中庫存的狀況下。對商品的庫存實現入庫和出庫,咱們能夠藉助lua
腳本實現原子性修改庫存。在lua
腳本中判斷庫存是否足夠減庫存,足夠扣減狀況下再更新庫存,這一系列操做是原子的。
lua
腳本不難學,我看了一下條件選擇、分支語句以及判斷語句以後就能本身寫出腳本了。由於咱們也並不須要用lua
作多複雜的事情。下面給出一個原子性修改庫存的lua
腳本例子:
local kc=tonumber(redis.call('GET',KEYS[1]));
if kc==nil
then
return -1;
end
local newKc=kc-ARGV[1];
if newKc<0
then
return 0;
else
redis.call('SET',KEYS[1],newKc);
return 1;
end
複製代碼