iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0

之前的鎖都只放在一台 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

https://ithelp.ithome.com.tw/upload/images/20261011/20184209EYigyQK6Ms.png

https://ithelp.ithome.com.tw/upload/images/20261011/20184209er96z4JEJw.png

https://ithelp.ithome.com.tw/upload/images/20261011/20184209Nya24fwpiu.png

這次換成 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

https://ithelp.ithome.com.tw/upload/images/20261011/20184209bD3Ei0gruQ.png

https://ithelp.ithome.com.tw/upload/images/20261011/20184209ZGis7bMtse.png

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

https://ithelp.ithome.com.tw/upload/images/20261011/20184209cgvdM9tDwJ.png

https://ithelp.ithome.com.tw/upload/images/20261011/20184209XJGMpFbZZL.png

redis-1 被降成 6382 的從節點,會先清空自己再同步,B 的鎖就沒了。B 不會收到任何通知,還以為鎖在自己手上。

主節點直接掛掉也一樣:鎖回了 OK、還沒傳到從節點就換手,新的主節點沒有這把鎖,別人就搶得到。

測完關掉(在主機的終端機下):

docker compose -f docker-compose-sentinel.yml down

Redlock

問題出在一把鎖只放在一台主節點上。Redis 的作者 antirez 提過一個做法,叫 Redlock:

  • 準備 5 台各自獨立的 Redis,不做主從
  • 用同一個 key、同一個 value,同時對 5 台下 SET NX
  • 3 台以上搶到,而且花的時間沒超過鎖的過期時間,才算拿到鎖。沒拿到就把每一台都刪掉

例如 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 回了一篇:

  • App 卡住、鎖過期,是所有會自己過期的鎖都有的問題,不只 Redlock
  • DB 都能比號碼擋掉舊的寫入了,本身就能保證不出錯,不太需要一把很強的鎖。真的要擋,Redlock 的隨機 value 也能用:寫入時確認還是自己的 value 才寫
  • 時鐘不用每台對得一模一樣,只要走得差不多快。時鐘亂跳可以靠維運避免,例如不要手動改伺服器時間

現在 Redis 官方文件講 Redlock 的那一頁,最後也建議要做 fencing token,也提醒時鐘被調動可能讓兩個人同時拿到鎖。

我目前的理解

兩邊都有道理。我目前的理解是,先看這把鎖是為了什麼:

  • 為了不要重複做事:像只讓一個人去查 DB、排程只跑一次。偶爾兩個人都拿到,頂多多做一次,一台 Redis 加 Sentinel 就夠了。
  • 為了不能出錯:像扣錢、扣庫存。鎖會在上面那些情況失手,換成 Redlock 也一樣,最後一道要放在 DB:同一筆交易編號只能寫一次(唯一索引)、UPDATE ... WHERE stock > 0、版本號(寫入時比對版本,跟 fencing token 的想法一樣)。

Redisson 有 RFencedLock,搶到鎖會多回一個號碼,每搶到一次就 +1。號碼有了,DB 那邊還是要自己比。

為什麼要在意

  • 跟錢有關的,鎖只能讓大家少撞在一起。 金融相關的系統,帳對不對通常不會只交給一把鎖,DB 的交易、唯一索引,加上事後對帳,才是最後一道。
  • min-replicas-to-write 能擋掉剛剛的 B。 主節點設了 min-replicas-to-write 1,最近 10 秒內沒有從節點回報,就不收寫入,B 會拿到 NOREPLICAS 錯誤,不會回 OK。但主節點回了 OK、還沒傳到從節點就掛掉那種,它一樣擋不了。

明天

明天是最後一天,回頭整理這 30 天/images/emoticon/emoticon12.gif


上一篇
Day 28|監控與調校
系列文
從購物車到秒殺:30 天 Redis 高併發自學筆記 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言