什麼是Redis事務?
Redis事務不保證原子性, 即事務中的某個命令執行失敗, 事務不會回滾, 且後續命令會繼續執行. 這樣一來, Redis事務的功能就和腳本差不多, 都是將命令打包, Redis事務能做的事, 腳本也能做, 而且腳本速度更快.
(1) Redis事務相關的命令
- MULTI
開啓一個事務, 後續的命令都會添加到一個隊列中, 等待EXEC命令. - EXEC
觸發並執行事務中的所有命令. - DISCARD
清空事務隊列.
(2) 實例一
127.0.0.1:6379> set num 1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr num
QUEUED
127.0.0.1:6379> incrby num 2
QUEUED
127.0.0.1:6379> exec
1) (integer) 2
2) (integer) 4
127.0.0.1:6379> get num
"4"
(3) 實例二
127.0.0.1:6379> set num 1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr num
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get num
"1"
Redis併發控制
redis本身是沒有鎖的概念的, 但由於redis是單進程單線程的, 所以redis內部也不會出現併發衝突. 雖然redis內部不會出現併發衝突, 但這並不代表在業務場景下使用redis, 也不會出現併發衝突, 比如以下業務場景:
- 多個客戶端併發地寫同一個key, 且寫命令的執行順序對結果會有影響
- 多個客戶端併發地讀同一個key, 如果滿足條件就修改值後寫回去.
下面我們就來了解在業務場景下使用redis的併發衝突該如何解決.
樂觀鎖
redis樂觀鎖, 即CAS(check-and-set)機制, 客戶端A在修改key之前先監控key, 如果key被其他客戶端修改, 那麼客戶端A的修改就會失敗, 返回 nil. 注意: 客戶端A的修改要與事務一起使用.
- WATCH
監控key是否被其他客戶端修改, 如果被修改則返回nil. - UNWATCH
取消對key的監控.
實例如下:
127.0.0.1:6379> watch mykey
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set mykey 789
QUEUED
127.0.0.1:6379> exec
(nil)
分佈式鎖
客戶端在讀寫redis之前必須先從redis獲取鎖, 只有獲取到鎖的客戶端才能讀寫redis.
爲什麼不能簡單的使用synchronized互斥鎖? 因爲synchronized只對單機部署有用, 而我們的應用是多機部署的.
(1) 方案一
a. 獲取鎖
SET my_lock 隨機值 PX 5000 NX
PX是設置過期時間, 單位毫秒. NX是僅當key不存在時才設置值.
b. 刪除鎖
只有提供的value值相同才能刪除鎖, 因爲我們不能讓客戶端刪除別人的鎖. 因爲涉及到條件判斷, 爲了保證事務特性, 必須使用Lua腳本.
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
c. 實例
//1. 獲取鎖
127.0.0.1:6379> SET my_lock 123456 PX 5000 NX
OK
//2. 使用lua腳本刪除鎖
[root@master ~]# redis-cli --eval delKey.lua my_lock , 123456
(integer) 1
//3. 查看key
127.0.0.1:6379> get my_lock
(nil)
(2) 方案二
上面方案一的分佈式鎖有個問題, 如果在key同步到子節點之前主節點宕機了, 就會導致鎖丟失. 爲此redis提供了在集羣架構下的分佈式鎖, 即RedLock算法.
- 獲取當前時間戳, 單位是毫秒.
- 跟上面類似, 輪流嘗試在每個master節點上創建鎖, 過期時間較短, 一般就幾十毫秒.
- 嘗試在大多數節點上建立一個鎖, 比如5個節點就要求是3個節點(n/2 + 1).
- 客戶端計算建立好鎖的時間, 如果建立鎖的時間小於超時時間, 就算建立成功了
- 要是鎖建立失敗了,那麼就依次刪除這個鎖
- 只要別人建立了一把分佈式鎖,你就得不斷輪詢去嘗試獲取鎖
這種RedLock算法在業界還存在爭議, 我們只需要瞭解下, 所以真正的方案二應該是使用ZooKeeper分佈式鎖.