之前的鎖都只放在一台 Redis 上。正式環境怕主節點掛掉,通常會加從節點跟 Sentinel。主節點出事、Sentinel 換人的時候,鎖還靠得住嗎?
把 Day 17 的 Sentinel 開回來,等 10 秒左右,讓 Sentinel 先找到大家(在主機的終端機下):
docker compose -f docker-compose-sentinel.yml up -d
這次不關掉主節點,讓它卡住(在主機的終端機下):
docker pause redis-1
docker pause 是把容器凍住:Redis 還在,連線也還在,只是完全不動,像是主節點卡住或網路斷了。之前用的 docker stop 是直接關掉。
Sentinel 分不出卡住還是掛掉,5 秒沒回應就換人。等 10 秒左右,看新的主節點是誰,再讓 A 搶鎖。升上來的是哪一台每次不一定,所以兩台都下(在主機的終端機下):
docker exec sentinel-1 redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
docker exec redis-2 redis-cli -p 6381 SET lock:job A NX EX 60
docker exec redis-3 redis-cli -p 6382 SET lock:job A NX EX 60



這次換成 6382。redis-3 回 OK,A 搶到了;redis-2 還是從節點,回 READONLY。
接著讓 redis-1 醒來,B 馬上在它上面搶同一把鎖(在主機的終端機下):
docker unpause redis-1
docker exec redis-1 redis-cli -p 6380 SET lock:job B NX EX 60
docker exec redis-1 redis-cli -p 6380 ROLE


B 也回 OK。redis-1 剛醒來,還不知道自己被換掉了,ROLE 還是 master,只是底下已經沒有從節點。同時有兩台覺得自己是主節點,這叫腦裂。現在 A 跟 B 都拿著同一把鎖。
等 20 秒左右再看一次(在主機的終端機下):
docker exec redis-1 redis-cli -p 6380 ROLE
docker exec redis-1 redis-cli -p 6380 GET lock:job


redis-1 被降成 6382 的從節點,會先清空自己再同步,B 的鎖就沒了。B 不會收到任何通知,還以為鎖在自己手上。
主節點直接掛掉也一樣:鎖回了 OK、還沒傳到從節點就換手,新的主節點沒有這把鎖,別人就搶得到。
測完關掉(在主機的終端機下):
docker compose -f docker-compose-sentinel.yml down
問題出在一把鎖只放在一台主節點上。Redis 的作者 antirez 提過一個做法,叫 Redlock:
SET NX
例如 A 在第 1、2、3 台搶到,就算第 1 台掛掉,第 2、3 台上還是 A 的鎖,B 最多搶到第 4、5 台,湊不到 3 台。代價是一把鎖要問 5 台。
Redisson 以前有現成的 RedissonRedLock,現在已經標成 deprecated,不建議再用。
《Designing Data-Intensive Applications》的作者 Martin Kleppmann 寫了一篇文章,說 Redlock 不安全。
第一個問題跟 Redis 有幾台無關:
A:搶到鎖
A:卡住了(例如 Java 的 GC),卡太久,鎖過期
B:搶到鎖,寫入 DB
A:醒來,以為鎖還在,也寫入 DB
--------------------
A 跟 B 都寫了
看門狗也救不了,A 整個卡住,看門狗也跟著停。
第二個是時間:每台 Redis 用自己的時鐘算過期。其中一台時鐘突然往前跳,鎖提早過期,別人就可能也湊到 3 台。
他的建議是 fencing token:每次拿到鎖都給一個越來越大的號碼,寫入時帶著號碼。DB 記住看過最大的號碼,比它小的就擋掉:
A:搶到鎖,號碼 33
A:卡住了,鎖過期
B:搶到鎖,號碼 34,寫入 DB,DB 記下 34
A:醒來,帶著 33 寫入 DB
--------------------
33 比 34 小,DB 擋掉 A
Redlock 的 value 是隨機的,比不出大小,給不了這種號碼。所以他的結論是:只是怕重複做事,一台 Redis 就夠了,用不到 5 台;不能出錯的話,Redlock 又不夠安全,要換 ZooKeeper 這類工具,搭配 fencing token。
antirez 回了一篇:
現在 Redis 官方文件講 Redlock 的那一頁,最後也建議要做 fencing token,也提醒時鐘被調動可能讓兩個人同時拿到鎖。
兩邊都有道理。我目前的理解是,先看這把鎖是為了什麼:
UPDATE ... WHERE stock > 0、版本號(寫入時比對版本,跟 fencing token 的想法一樣)。Redisson 有 RFencedLock,搶到鎖會多回一個號碼,每搶到一次就 +1。號碼有了,DB 那邊還是要自己比。
min-replicas-to-write 能擋掉剛剛的 B。 主節點設了 min-replicas-to-write 1,最近 10 秒內沒有從節點回報,就不收寫入,B 會拿到 NOREPLICAS 錯誤,不會回 OK。但主節點回了 OK、還沒傳到從節點就掛掉那種,它一樣擋不了。明天是最後一天,回頭整理這 30 天![]()