iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0

Day 16 最後說過,主節點掛了,從節點只會一直重連,不會自己升上去。要有人手動把從節點升成主節點,App 連線的位址也要跟著改。

Sentinel(哨兵)就是來做這件事的:盯著主節點,掛了自動挑一台從節點升上去,再告訴 App 新的主節點在哪。

常用指令

  • sentinel monitor 名字 IP port quorum(要盯哪一台主節點,寫在 Sentinel 的設定裡,quorum 代表法定仲裁人數)
  • SENTINEL get-master-addr-by-name 名字(問 Sentinel 現在的主節點在哪)
  • ROLE(看自己是主節點還是從節點)

開起來

先把 Day 16 的兩台關掉(在主機的終端機下):

docker compose -f docker-compose-replica.yml down

一樣在 Day 2 的 docker-compose.yml 旁邊,另外寫一份 docker-compose-sentinel.yml,一主二從 + 三個 Sentinel:

# Day 17 Sentinel:一主二從 + 三個 Sentinel
name: day17

services:
  net: # 什麼都不做,只負責開網路跟 port,其他台都借它的網路
    image: redis:7-alpine
    container_name: day17-net
    command: sleep infinity
    init: true # 讓 docker stop 不用等 10 秒
    ports:
      - "6380-6382:6380-6382" # 三台 Redis
      - "26379-26381:26379-26381" # 三個 Sentinel

  redis-1:
    image: redis:7-alpine
    container_name: redis-1
    network_mode: service:net # 跟 net 共用網路,彼此用 127.0.0.1 + port 就找得到
    command: redis-server --port 6380

  redis-2:
    image: redis:7-alpine
    container_name: redis-2
    network_mode: service:net
    command: redis-server --port 6381 --replicaof 127.0.0.1 6380

  redis-3:
    image: redis:7-alpine
    container_name: redis-3
    network_mode: service:net
    command: redis-server --port 6382 --replicaof 127.0.0.1 6380

  sentinel-1:
    image: redis:7-alpine
    container_name: sentinel-1
    network_mode: service:net
    command: >
      sh -c "touch /tmp/sentinel.conf &&
      redis-sentinel /tmp/sentinel.conf --port 26379
      --sentinel monitor mymaster 127.0.0.1 6380 2
      --sentinel down-after-milliseconds mymaster 5000"

  sentinel-2:
    image: redis:7-alpine
    container_name: sentinel-2
    network_mode: service:net
    command: >
      sh -c "touch /tmp/sentinel.conf &&
      redis-sentinel /tmp/sentinel.conf --port 26380
      --sentinel monitor mymaster 127.0.0.1 6380 2
      --sentinel down-after-milliseconds mymaster 5000"

  sentinel-3:
    image: redis:7-alpine
    container_name: sentinel-3
    network_mode: service:net
    command: >
      sh -c "touch /tmp/sentinel.conf &&
      redis-sentinel /tmp/sentinel.conf --port 26381
      --sentinel monitor mymaster 127.0.0.1 6380 2
      --sentinel down-after-milliseconds mymaster 5000"
  • sentinel monitor mymaster 127.0.0.1 6380 2:盯 6380 這台主節點,取名叫 mymaster。最後的 2 是 quorum,至少 2 個 Sentinel 都覺得它掛了才算數,確保一個 Sentinel 看錯不會亂切
  • down-after-milliseconds:多久沒回應就當它掛了,預設 30 秒,這邊調成 5 秒比較好觀察
  • Sentinel 會把狀態寫回自己的設定檔,所以先 touch 一個空檔案給它寫
  • network_mode: service:net:Sentinel 會把主節點的 IP 告訴 Java,如果給的是容器自己的 IP(Day 16 ROLE 看到的 172 開頭那種),Docker Desktop 上主機的 Java 連不到。所以讓大家共用 net 的網路,彼此都用 127.0.0.1 + 不同 port 找對方,主機的 Java 連 127.0.0.1 也找得到

跟 Day 16 一樣是在同一台電腦模擬,正式環境每台 Redis 跟 Sentinel 都要放在不同機器上。

開起來,看 sentinel-1 的 log(grep "+" 只留 + 開頭的事件,在主機的終端機下):

docker compose -f docker-compose-sentinel.yml up -d

docker logs sentinel-1 | grep "+"

https://ithelp.ithome.com.tw/upload/images/20260929/20184209EDcDyexWdn.png

設定裡只寫了主節點,兩台從節點(+slave)跟另外兩個 Sentinel(+sentinel)都是它自己找到的。

Java 改問 Sentinel

在 application.yml 旁邊新增一份 application-sentinel.yml:

# Day 17:啟動時加 --spring.profiles.active=sentinel 才會用這份
# 不寫主節點在哪,改問 Sentinel
spring:
  data:
    redis:
      sentinel:
        master: mymaster # 跟 sentinel monitor 取的名字一樣
        nodes: 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381 # 三個 Sentinel

不寫主節點的位址,只寫 Sentinel 在哪,Java 啟動時先問 Sentinel 誰是主節點。

啟動時加上 --spring.profiles.active=sentinel(IntelliJ 在 Run Configuration 的 Program arguments),Java 就改連 Sentinel 這組,不會連 Day 2 的 redis30days(6379)。

https://ithelp.ithome.com.tw/upload/images/20260929/20184209CwA4P6P5qP.png

開另一個終端機,每秒打一次 Day 4 的 /counter/redis(INCR,每次 +1,在主機的終端機下):

while true; do echo "$(date +%T) $(curl -s localhost:8080/counter/redis)"; sleep 1; done

關掉主節點

回到原本的終端機,把主節點關掉(在主機的終端機下):

docker stop redis-1

https://ithelp.ithome.com.tw/upload/images/20260929/20184209piaIpE1AsY.png

24 秒送出去的那一筆,等到 34 秒才回來,下一行已經是 35 秒。中間卡了 10 秒左右,但沒有噴錯,數字也接著往下加。

再看 Java 的 console:

https://ithelp.ithome.com.tw/upload/images/20260929/20184209cx9lk5IEa3.png

斷線後先試著重連 6380,連不上,過了 9 秒自己連到新的主節點。

Sentinel 這邊,三個會先投票選一個出來負責切換(leader),每次選到的不一定,先找出是哪一個(在主機的終端機下):

docker compose -f docker-compose-sentinel.yml logs | grep elected-leader # 誰是 leader

docker logs sentinel-1 | grep -E "\+sdown master|\+odown|elected-leader|selected-slave|switch-master"  # sentinel-1 換成上面找到的那一個

https://ithelp.ithome.com.tw/upload/images/20260929/20184209fT1J87SZKT.png

找到 leader 是 sentinel-1,後面的 127.0.0.1 6380 是這次掛掉、要被換掉的舊主節點。

https://ithelp.ithome.com.tw/upload/images/20260929/201842091IiYUvHZJy.png

  • +sdown:這個 Sentinel 自己覺得主節點掛了(5 秒沒回應)
  • +odown:#quorum 2/2,有 2 個 Sentinel 都這樣覺得,達到 quorum,才確定掛了
  • +elected-leader:自己被選出來負責切換
  • +selected-slave:挑一台從節點當新的主節點
  • +switch-master:舊的 6380 換成新的主節點,切換完成

對一下時間(log 是 UTC,比台灣慢 8 小時):25 秒斷線,Sentinel 等了 5 秒才判定掛了,32 秒切換完,Java 下一次重試才接上,就是 console 上的 34 秒。

舊主節點回來

先問 Sentinel 現在的主節點在哪(在主機的終端機下):

docker exec sentinel-2 redis-cli -p 26380 SENTINEL get-master-addr-by-name mymaster

大家都擠在同一個網路,每台的 port 都不一樣,redis-cli 要加 -p 指定(sentinel-2 是 26380)。

https://ithelp.ithome.com.tw/upload/images/20260929/20184209wGzilm5YRa.png

再把 redis-1 開回來,馬上看它的身分(在主機的終端機下):

docker start redis-1

docker exec redis-1 redis-cli -p 6380 ROLE

https://ithelp.ithome.com.tw/upload/images/20260929/20184209vAgQfr2JDw.png

Sentinel 已經換成 6381 了,redis-1 卻還說自己是 master。它的啟動參數沒有 --replicaof,一開起來就當自己是主節點,Sentinel 過一陣子才會來處理。

等 15 秒左右再看一次(在主機的終端機下):

docker exec redis-1 redis-cli -p 6380 ROLE

https://ithelp.ithome.com.tw/upload/images/20260929/20184209wYZSIcJ7km.png

舊主節點回來不會搶回主節點,Sentinel 把它降成 6381 的從節點。

為什麼要在意

  • 切換的這幾秒,請求是卡住的,不是噴錯。 Java 把指令先留著,接上新的主節點再送出去。卡超過 60 秒(沒設 spring.data.redis.timeout 時的預設)才會噴 RedisCommandTimeoutException。
  • Sentinel 不會救資料。 Day 16 還沒傳到從節點的那幾筆,新的主節點本來就沒有,切換完就沒了。

明天

主從加 Sentinel 解決了「掛了有人接手」,但能寫的還是只有一台主節點,資料也得一台裝得下。明天來看 Cluster,把資料拆到好幾台上/images/emoticon/emoticon12.gif


上一篇
Day 16|主從複製
下一篇
Day 18|Cluster
系列文
從購物車到秒殺:30 天 Redis 高併發自學筆記 共 21 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言