<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Sentinel on Tarragon</title><link>https://tarrragon.github.io/blog/tags/sentinel/</link><description>Recent content in Sentinel on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 16 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/sentinel/index.xml" rel="self" type="application/rss+xml"/><item><title>Redis Sentinel 與 failover 時序：從 master 死掉到 client 重連的每一段</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/vendors/redis/sentinel-ha-failover/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/vendors/redis/sentinel-ha-failover/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/vendors/redis/" data-link-title="Redis" data-link-desc="OSS in-memory data structure store、cache 主流">Redis&lt;/a> overview 的 implementation-layer deep article。Sentinel 處理的是「單 master 容量夠、但 master 不能是單點」的 HA 場景；要橫向擴容超過單機記憶體則走 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/vendors/redis/cluster-resharding/" data-link-title="Redis Cluster Re-sharding：source = target，但 topology 重劃的 5 段流程" data-link-desc="Redis cluster re-sharding 是 5 type migration 漏類實證 — source / target 同 cluster、無 schema / paradigm 差、但 16384 slot 重分配是核心；本文涵蓋 4 種 re-sharding driver、slot migration 機制、redis-cli --cluster rebalance / reshard 工具、5 個 production 踩雷（cluster busy / replica lag / client cache stale / cross-slot transaction / monitor gap）">Cluster re-sharding&lt;/a>，兩者解的問題不同。機制以 &lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/">Redis Sentinel 官方文件&lt;/a> 為準、最後檢查日 2026-06-16。&lt;/p>&lt;/blockquote>
&lt;h2 id="failover-是一條時序鏈不是一個瞬間">Failover 是一條時序鏈、不是一個瞬間&lt;/h2>
&lt;p>「master 掛了 Sentinel 會自動切換」這句話把 failover 講成一個原子動作，但真正在 production 出事時，問題永遠出在這條鏈的某一段卡住。把 failover 攤開成時序，才看得到延遲跟資料遺失藏在哪：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">T0 master 失去回應
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> ↓ (down-after-milliseconds)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">T1 單一 Sentinel 標記 master 為 SDOWN（主觀下線）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> ↓ (Sentinel 之間互問)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">T2 達到 quorum 數量的 Sentinel 同意 → ODOWN（客觀下線）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> ↓ (Sentinel 之間選出 leader 來主導 failover)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">T3 leader Sentinel 從 replica 中挑一個當新 master
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> ↓ (SLAVEOF NO ONE + 其他 replica 改指向新 master)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">T4 新 master 提升完成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> ↓ (Sentinel 廣播新 topology、更新 DNS / 通知 client)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">T5 client 發現新 master、重連、恢復寫入&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>從 T0 到 T5 的總時間決定了「寫入中斷多久」。每一段都有對應的旋鈕跟失敗模式：T0→T1 由 &lt;code>down-after-milliseconds&lt;/code> 控制（太短誤判、太長反應慢）；T1→T2 由 quorum 設定控制（太低腦裂風險、太高切不動）；T4→T5 由 client 的 topology 感知能力控制。理解 failover 就是理解這條鏈的每一段。&lt;/p>
&lt;p>對把 cache 當主要 serving layer 的服務，這條鏈的長度直接是業務中斷時間。&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">Tinder 的配對引擎&lt;/a>每次滑動讀多個 cache、cache miss 是邊緣案例——failover 期間若寫入中斷十幾秒，新寫入的 profile 互動全部 hang，sub-millisecond 的 SLA 在這幾秒徹底失守。這也是為什麼大規模服務多半走 managed multi-AZ failover（見 ElastiCache）而非自管 Sentinel。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/02-cache-redis/vendors/redis/" data-link-title="Redis" data-link-desc="OSS in-memory data structure store、cache 主流">Redis</a> overview 的 implementation-layer deep article。Sentinel 處理的是「單 master 容量夠、但 master 不能是單點」的 HA 場景；要橫向擴容超過單機記憶體則走 <a href="/blog/backend/02-cache-redis/vendors/redis/cluster-resharding/" data-link-title="Redis Cluster Re-sharding：source = target，但 topology 重劃的 5 段流程" data-link-desc="Redis cluster re-sharding 是 5 type migration 漏類實證 — source / target 同 cluster、無 schema / paradigm 差、但 16384 slot 重分配是核心；本文涵蓋 4 種 re-sharding driver、slot migration 機制、redis-cli --cluster rebalance / reshard 工具、5 個 production 踩雷（cluster busy / replica lag / client cache stale / cross-slot transaction / monitor gap）">Cluster re-sharding</a>，兩者解的問題不同。機制以 <a href="https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/">Redis Sentinel 官方文件</a> 為準、最後檢查日 2026-06-16。</p></blockquote>
<h2 id="failover-是一條時序鏈不是一個瞬間">Failover 是一條時序鏈、不是一個瞬間</h2>
<p>「master 掛了 Sentinel 會自動切換」這句話把 failover 講成一個原子動作，但真正在 production 出事時，問題永遠出在這條鏈的某一段卡住。把 failover 攤開成時序，才看得到延遲跟資料遺失藏在哪：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">T0   master 失去回應
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">     ↓ (down-after-milliseconds)
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">T1   單一 Sentinel 標記 master 為 SDOWN（主觀下線）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">     ↓ (Sentinel 之間互問)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">T2   達到 quorum 數量的 Sentinel 同意 → ODOWN（客觀下線）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">     ↓ (Sentinel 之間選出 leader 來主導 failover)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">T3   leader Sentinel 從 replica 中挑一個當新 master
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">     ↓ (SLAVEOF NO ONE + 其他 replica 改指向新 master)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">T4   新 master 提升完成
</span></span><span class="line"><span class="ln">10</span><span class="cl">     ↓ (Sentinel 廣播新 topology、更新 DNS / 通知 client)
</span></span><span class="line"><span class="ln">11</span><span class="cl">T5   client 發現新 master、重連、恢復寫入</span></span></code></pre></div><p>從 T0 到 T5 的總時間決定了「寫入中斷多久」。每一段都有對應的旋鈕跟失敗模式：T0→T1 由 <code>down-after-milliseconds</code> 控制（太短誤判、太長反應慢）；T1→T2 由 quorum 設定控制（太低腦裂風險、太高切不動）；T4→T5 由 client 的 topology 感知能力控制。理解 failover 就是理解這條鏈的每一段。</p>
<p>對把 cache 當主要 serving layer 的服務，這條鏈的長度直接是業務中斷時間。<a href="/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">Tinder 的配對引擎</a>每次滑動讀多個 cache、cache miss 是邊緣案例——failover 期間若寫入中斷十幾秒，新寫入的 profile 互動全部 hang，sub-millisecond 的 SLA 在這幾秒徹底失守。這也是為什麼大規模服務多半走 managed multi-AZ failover（見 ElastiCache）而非自管 Sentinel。</p>
<h2 id="核心概念sentinel-的判定模型">核心概念：Sentinel 的判定模型</h2>
<p>Sentinel 是獨立於 Redis 資料節點的監控進程，它的判定靠兩層共識避免單一 Sentinel 誤判。</p>
<p><strong>SDOWN（Subjectively Down，主觀下線）</strong>：單一 Sentinel 在 <code>down-after-milliseconds</code> 內收不到 master 的有效回應（<code>PING</code>），就主觀認定它下線。這只是一個 Sentinel 的意見，不觸發 failover。</p>
<p><strong>ODOWN（Objectively Down，客觀下線）</strong>：當標記 SDOWN 的 Sentinel 數量達到 <code>quorum</code> 設定值，master 被客觀認定下線。只有 master 的 ODOWN 才會觸發 failover（replica 的下線只標記不 failover）。</p>
<p><code>quorum</code> 是「多少個 Sentinel 同意才算真的下線」，它跟「多少個 Sentinel 同意才能執行 failover」是兩個不同的數字——後者需要 Sentinel 的多數（majority），確保同時只有一個 leader 主導 failover，避免兩個 Sentinel 各自提升不同 replica 造成腦裂。</p>
<p><strong>為什麼 Sentinel 要部署奇數個且至少三個</strong>：quorum 跟 majority 都需要足夠的 Sentinel 投票。兩個 Sentinel 無法在其中一個故障時達成 majority；三個才能容忍一個故障。Sentinel 應部署在不同故障域（不同 AZ / 機架），且不要跟 Redis 資料節點同生共死。</p>
<p><strong>Sentinel 不是 proxy</strong>：client 不透過 Sentinel 讀寫資料。client 向 Sentinel 查詢「現在的 master 是誰」，拿到地址後直連 Redis。failover 後 client 必須重新向 Sentinel 查詢——這是 T4→T5 的關鍵，client library 要支援 Sentinel 模式才能自動完成。</p>
<h2 id="配置sentinel-的設定路徑">配置：Sentinel 的設定路徑</h2>
<p>最小三 Sentinel 配置，每個 Sentinel 一份 <code>sentinel.conf</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># sentinel.conf</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># 監控名為 mymaster 的 master、quorum=2（三個 Sentinel 中兩個同意算 ODOWN）</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">sentinel monitor mymaster 10.0.0.1 <span class="m">6379</span> <span class="m">2</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 多久收不到回應算 SDOWN（5 秒）</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">sentinel down-after-milliseconds mymaster <span class="m">5000</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># failover 後同時最多幾個 replica 去 resync 新 master</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 設 1 = 串行 resync、避免所有 replica 同時 resync 拖垮新 master</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">sentinel parallel-syncs mymaster <span class="m">1</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># failover 整體逾時（三分鐘內沒完成算失敗、可重試）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">sentinel failover-timeout mymaster <span class="m">180000</span></span></span></code></pre></div><p>啟動 Sentinel：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">redis-sentinel /path/to/sentinel.conf
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 或 redis-server /path/to/sentinel.conf --sentinel</span></span></span></code></pre></div><p>client 端要用 Sentinel-aware 連線（以 Python redis-py 為例）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">redis.sentinel</span> <span class="kn">import</span> <span class="n">Sentinel</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">sentinel</span> <span class="o">=</span> <span class="n">Sentinel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="p">[(</span><span class="s2">&#34;10.0.0.10&#34;</span><span class="p">,</span> <span class="mi">26379</span><span class="p">),</span> <span class="p">(</span><span class="s2">&#34;10.0.0.11&#34;</span><span class="p">,</span> <span class="mi">26379</span><span class="p">),</span> <span class="p">(</span><span class="s2">&#34;10.0.0.12&#34;</span><span class="p">,</span> <span class="mi">26379</span><span class="p">)],</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">socket_timeout</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 寫入走 master（failover 後自動重新發現）</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">master</span> <span class="o">=</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">master_for</span><span class="p">(</span><span class="s2">&#34;mymaster&#34;</span><span class="p">,</span> <span class="n">socket_timeout</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">master</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;key&#34;</span><span class="p">,</span> <span class="s2">&#34;value&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 讀取可走 replica</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">replica</span> <span class="o">=</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">slave_for</span><span class="p">(</span><span class="s2">&#34;mymaster&#34;</span><span class="p">,</span> <span class="n">socket_timeout</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">replica</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;key&#34;</span><span class="p">)</span></span></span></code></pre></div><p>關鍵：client 透過 <code>master_for</code> 拿到的是一個會在 failover 後重新查詢 Sentinel 的連線封裝，不是寫死的 IP。直接寫死 master IP 的 client 在 failover 後會持續連到死掉的舊 master。</p>
<h3 id="防腦裂的兩個-master-端設定">防腦裂的兩個 master 端設定</h3>
<p>Sentinel 選主的同時，要防止舊 master 復活後繼續接受寫入（split-brain）。在 Redis master 端設：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 至少要有 1 個 replica 連著、且 replica lag &lt; 10 秒、master 才接受寫入</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">redis-cli CONFIG SET min-replicas-to-write <span class="m">1</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">redis-cli CONFIG SET min-replicas-max-lag <span class="m">10</span></span></span></code></pre></div><p>這讓被網路隔離的舊 master（連不到 replica）自動停止接受寫入，避免它在隔離期間累積的寫入在復活後跟新 master 衝突。</p>
<h2 id="production-故障演練">Production 故障演練</h2>
<h3 id="case-1down-after-太短網路抖動誤觸-failover">Case 1：down-after 太短、網路抖動誤觸 failover</h3>
<p><strong>徵兆</strong>：master 其實沒死，只是一次短暫的網路抖動或 GC 暫停，Sentinel 卻觸發了 failover，造成一次不必要的中斷；甚至反覆 failover（flapping）。</p>
<p><strong>根因</strong>：<code>down-after-milliseconds</code> 設太短（例如 1000ms），master 一個短暫的 STW GC 或跨 AZ 網路抖動就超過閾值，被誤判 SDOWN→ODOWN。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><code>down-after-milliseconds</code> 設成能容忍正常抖動的值（5000-10000ms 是常見起點），用實際 RTT 與 GC pause 分布反推</li>
<li>quorum 設成多數而非 1，要求多個 Sentinel 同時看到下線，過濾單一 Sentinel 的網路問題</li>
<li>Sentinel 跟 Redis 不要跨高延遲鏈路放，網路品質直接影響誤判率</li>
<li>監控 failover 觸發頻率，flapping 是調參訊號</li>
</ol>
<h3 id="case-2failover-後-client-連到死掉的舊-master">Case 2：failover 後 client 連到死掉的舊 master</h3>
<p><strong>徵兆</strong>：failover 完成、Sentinel 日誌顯示新 master 已提升，但部分 application 持續寫入失敗或寫到舊 master（資料進黑洞），<code>CLIENT LIST</code> 在新 master 上看不到這些 client。</p>
<p><strong>根因</strong>：client 寫死了 master IP，或用的 client library 不支援 Sentinel 模式，failover 後不會重新向 Sentinel 查詢新 master。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>client 一律用 Sentinel-aware 連線（<code>master_for</code> / lettuce 的 Sentinel 配置），不寫死 IP</li>
<li>確認 client library 版本支援 Sentinel 且配置正確（連的是 Sentinel port 26379，不是 Redis 6379）</li>
<li>對 latency-sensitive 服務，failover 後可主動 rolling restart application，清掉殘留連線</li>
<li>設 <code>min-replicas-to-write</code> 讓被隔離的舊 master 自動停寫，即使 client 連上去也寫不進，避免資料進黑洞</li>
</ol>
<h3 id="case-3選到-lag-大的-replicafailover-丟資料">Case 3：選到 lag 大的 replica、failover 丟資料</h3>
<p><strong>徵兆</strong>：failover 後發現最近幾秒的寫入不見了，新 master 的資料比預期舊。</p>
<p><strong>根因</strong>：Redis replication 是非同步的，replica 之間 lag 不一。Sentinel 選主會優先選 lag 小的（靠 <code>replica-priority</code> 與複製 offset），但若所有 replica 都 lag 大（master 寫入遠快於複製），無論選哪個都會丟掉未複製的寫入。Sentinel 的 failover 保證可用性，不保證零資料遺失。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>設 <code>min-replicas-to-write</code> + <code>min-replicas-max-lag</code>，lag 過大時 master 主動停寫，限制資料遺失窗口</li>
<li>監控 replication lag（<code>master_repl_offset</code> vs replica 的 offset），lag 持續大代表複製跟不上寫入，要降寫入或擴容</li>
<li>用 <code>replica-priority</code> 把不適合當 master 的 replica（例如做備份的、跨區的）設成 0 排除</li>
<li>需要零資料遺失的場景，Sentinel 的非同步複製不夠，走 <a href="/blog/backend/02-cache-redis/vendors/aws-elasticache/" data-link-title="AWS ElastiCache" data-link-desc="AWS managed Redis / Valkey / Memcached">MemoryDB</a> 的 multi-AZ transaction log（強一致持久性）</li>
</ol>
<h3 id="case-4腦裂舊-master-復活後雙寫衝突">Case 4：腦裂——舊 master 復活後雙寫衝突</h3>
<p><strong>徵兆</strong>：網路分區期間 Sentinel 提升了新 master，分區恢復後舊 master 回來，兩個 master 各自接受過寫入，資料出現衝突或舊 master 的寫入被覆蓋遺失。</p>
<p><strong>根因</strong>：舊 master 在分區期間被隔離（連不到 Sentinel 多數），但 client 若還連得到它且它沒設停寫保護，就繼續接受寫入。分區恢復後舊 master 被降為 replica，它在分區期間的寫入被新 master 的資料覆蓋。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><code>min-replicas-to-write 1</code> + <code>min-replicas-max-lag 10</code> 是核心防護——被隔離的舊 master 連不到 replica，自動停寫</li>
<li>Sentinel 部署在多數能存活的故障域，確保分區時多數 Sentinel 在新 master 那側</li>
<li>接受 Redis 的 CAP 取捨：Sentinel 偏向可用性，極端分區下無法完全避免資料遺失，要強一致走別的儲存層</li>
<li>failover 後監控舊 master 復活的降級流程，確認它正確變成 replica 且 resync</li>
</ol>
<h3 id="case-5parallel-syncs-設太大failover-後新-master-被-resync-拖垮">Case 5：parallel-syncs 設太大、failover 後新 master 被 resync 拖垮</h3>
<p><strong>徵兆</strong>：failover 完成的瞬間新 master 延遲暴增、甚至短暫無回應，所有 replica 同時對它發起全量同步。</p>
<p><strong>根因</strong>：<code>parallel-syncs</code> 設成大於 1（或等於 replica 數），failover 後所有 replica 同時對新 master 做 full resync。full resync 要新 master 做 BGSAVE（fork、見 <a href="/blog/backend/02-cache-redis/vendors/redis/persistence-fork-latency/" data-link-title="Redis 持久化與 fork latency：AOF、RDB 與那一次卡住整個 cluster 的 fork" data-link-desc="Redis 的 RDB save 與 AOF rewrite 都靠一次 fork()，而 fork 在大記憶體實例上會凍結主執行緒數百毫秒、複製分頁讓記憶體逼近翻倍。本文展開 AOF / RDB 的機制與 fsync 取捨、copy-on-write 的記憶體放大、5 個把持久化寫成延遲尖峰與資料遺失的 production 踩坑，以及 cache 場景到底要不要持久化的邊界">persistence deep article</a>）並把 RDB 傳給每個 replica，多個同時進行直接打爆新 master。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><code>parallel-syncs</code> 設 1，replica 串行 resync，犧牲一點恢復速度換新 master 不被拖垮</li>
<li>確認 master 端 <code>repl-backlog-size</code> 夠大，讓短暫斷線的 replica 走部分同步（partial resync）而非全量</li>
<li>監控 failover 後新 master 的 CPU / 記憶體，resync 期間是脆弱窗口</li>
<li>resync 的 fork 成本跟 <a href="/blog/backend/02-cache-redis/vendors/redis/memory-eviction-tuning/" data-link-title="Redis 記憶體與淘汰調校：maxmemory-policy、LFU 與碎片化的實戰判讀" data-link-desc="Redis 的記憶體是一條會在半夜爆掉的曲線：maxmemory 設多少、policy 選 LRU 還 LFU、碎片化什麼時候開始吃掉 30% RAM、OOM 時 noeviction 怎麼讓寫入全部失敗。本文展開 Redis 記憶體會計模型、eviction policy 的選型判讀、5 個把記憶體配置寫成 production 事故的踩坑，以及單機記憶體撞牆後該往 cluster 還是 DragonflyDB 走的邊界">記憶體 headroom</a> 直接相關，新 master 也要留 fork 空間</li>
</ol>
<h2 id="capacity--cost-邊界">Capacity / cost 邊界</h2>
<p>Sentinel 的容量判讀，圍繞 failover 時間與資料遺失窗口：</p>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>健康區間</th>
          <th>警戒與動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>failover 總時間（T0→T5）</td>
          <td>數秒到十幾秒</td>
          <td>過長 → 查 down-after / parallel-syncs / client</td>
      </tr>
      <tr>
          <td>failover 觸發頻率</td>
          <td>罕見（真實故障才觸發）</td>
          <td>flapping → down-after 太短、quorum 太低</td>
      </tr>
      <tr>
          <td>replication lag</td>
          <td>&lt; 1 秒</td>
          <td>持續大 → 寫入超過複製能力、failover 會丟資料</td>
      </tr>
      <tr>
          <td>Sentinel 數量</td>
          <td>奇數、≥ 3、跨故障域</td>
          <td>&lt; 3 或同故障域 → 無法容忍 Sentinel 故障</td>
      </tr>
      <tr>
          <td>寫入中斷可容忍時間</td>
          <td>業務定義</td>
          <td>不可容忍 → Sentinel 不夠、走 managed multi-AZ</td>
      </tr>
  </tbody>
</table>
<p>撞牆後的路由判斷：</p>
<ul>
<li><strong>單 master 容量不夠（記憶體 / 吞吐超過單機）</strong>：Sentinel 解 HA 不解容量。要橫向擴容走 <a href="/blog/backend/02-cache-redis/vendors/redis/cluster-resharding/" data-link-title="Redis Cluster Re-sharding：source = target，但 topology 重劃的 5 段流程" data-link-desc="Redis cluster re-sharding 是 5 type migration 漏類實證 — source / target 同 cluster、無 schema / paradigm 差、但 16384 slot 重分配是核心；本文涵蓋 4 種 re-sharding driver、slot migration 機制、redis-cli --cluster rebalance / reshard 工具、5 個 production 踩雷（cluster busy / replica lag / client cache stale / cross-slot transaction / monitor gap）">Redis Cluster</a>，它自帶 sharding 與 per-shard failover。</li>
<li><strong>不想自己運維 Sentinel 與 failover 演練</strong>：<a href="/blog/backend/02-cache-redis/vendors/aws-elasticache/" data-link-title="AWS ElastiCache" data-link-desc="AWS managed Redis / Valkey / Memcached">ElastiCache</a> 的 Multi-AZ 自動 failover 把這條時序鏈託管，failover ~30 秒到幾分鐘，省掉 Sentinel 部署與調參，代價是 managed premium。</li>
<li><strong>需要零資料遺失的強持久性</strong>：Sentinel 的非同步複製在 failover 時會丟未複製的寫入。要強一致走 <a href="/blog/backend/02-cache-redis/vendors/aws-elasticache/" data-link-title="AWS ElastiCache" data-link-desc="AWS managed Redis / Valkey / Memcached">MemoryDB</a> 的 multi-AZ transaction log。</li>
</ul>
<h2 id="整合--下一步">整合 / 下一步</h2>
<p>Sentinel 是 HA 的一層，但它的每一段都跟其他子系統耦合：</p>
<ul>
<li><strong>跟 <a href="/blog/backend/02-cache-redis/vendors/redis/cluster-resharding/" data-link-title="Redis Cluster Re-sharding：source = target，但 topology 重劃的 5 段流程" data-link-desc="Redis cluster re-sharding 是 5 type migration 漏類實證 — source / target 同 cluster、無 schema / paradigm 差、但 16384 slot 重分配是核心；本文涵蓋 4 種 re-sharding driver、slot migration 機制、redis-cli --cluster rebalance / reshard 工具、5 個 production 踩雷（cluster busy / replica lag / client cache stale / cross-slot transaction / monitor gap）">Cluster re-sharding</a></strong>：Sentinel 是「不分片的 HA」，Cluster 是「分片 + 每 shard 自帶 failover」。容量需求決定走哪條，本文是前者。</li>
<li><strong>跟 <a href="/blog/backend/02-cache-redis/vendors/redis/persistence-fork-latency/" data-link-title="Redis 持久化與 fork latency：AOF、RDB 與那一次卡住整個 cluster 的 fork" data-link-desc="Redis 的 RDB save 與 AOF rewrite 都靠一次 fork()，而 fork 在大記憶體實例上會凍結主執行緒數百毫秒、複製分頁讓記憶體逼近翻倍。本文展開 AOF / RDB 的機制與 fsync 取捨、copy-on-write 的記憶體放大、5 個把持久化寫成延遲尖峰與資料遺失的 production 踩坑，以及 cache 場景到底要不要持久化的邊界">persistence / fork latency</a></strong>：failover 後的 resync 靠 BGSAVE（fork），新 master 的 fork 成本是 resync 期間的脆弱點。</li>
<li><strong>跟 <a href="/blog/backend/02-cache-redis/vendors/redis/memory-eviction-tuning/" data-link-title="Redis 記憶體與淘汰調校：maxmemory-policy、LFU 與碎片化的實戰判讀" data-link-desc="Redis 的記憶體是一條會在半夜爆掉的曲線：maxmemory 設多少、policy 選 LRU 還 LFU、碎片化什麼時候開始吃掉 30% RAM、OOM 時 noeviction 怎麼讓寫入全部失敗。本文展開 Redis 記憶體會計模型、eviction policy 的選型判讀、5 個把記憶體配置寫成 production 事故的踩坑，以及單機記憶體撞牆後該往 cluster 還是 DragonflyDB 走的邊界">記憶體調校</a></strong>：新 master 提升後要承接全部寫入並支援 replica resync 的 fork，記憶體 headroom 不能少。</li>
<li><strong>跟 <a href="/blog/backend/02-cache-redis/cases/meta-cache-consistency-upgrade/" data-link-title="2.C1 Meta：Cache Consistency 升級" data-link-desc="快取 invalidation 一致性如何從常見錯誤演進到高可信治理。">Meta cache consistency</a></strong>：failover / replica promotion 期間的 stale read 與一致性議題，是大規模 cache 治理的核心，Sentinel 的非同步複製是 stale window 的來源之一。</li>
</ul>
<h2 id="相關連結">相關連結</h2>
<ul>
<li>上游 vendor 頁：<a href="/blog/backend/02-cache-redis/vendors/redis/" data-link-title="Redis" data-link-desc="OSS in-memory data structure store、cache 主流">Redis</a></li>
<li>同 vendor deep article：<a href="/blog/backend/02-cache-redis/vendors/redis/cluster-resharding/" data-link-title="Redis Cluster Re-sharding：source = target，但 topology 重劃的 5 段流程" data-link-desc="Redis cluster re-sharding 是 5 type migration 漏類實證 — source / target 同 cluster、無 schema / paradigm 差、但 16384 slot 重分配是核心；本文涵蓋 4 種 re-sharding driver、slot migration 機制、redis-cli --cluster rebalance / reshard 工具、5 個 production 踩雷（cluster busy / replica lag / client cache stale / cross-slot transaction / monitor gap）">Cluster re-sharding</a>、<a href="/blog/backend/02-cache-redis/vendors/redis/persistence-fork-latency/" data-link-title="Redis 持久化與 fork latency：AOF、RDB 與那一次卡住整個 cluster 的 fork" data-link-desc="Redis 的 RDB save 與 AOF rewrite 都靠一次 fork()，而 fork 在大記憶體實例上會凍結主執行緒數百毫秒、複製分頁讓記憶體逼近翻倍。本文展開 AOF / RDB 的機制與 fsync 取捨、copy-on-write 的記憶體放大、5 個把持久化寫成延遲尖峰與資料遺失的 production 踩坑，以及 cache 場景到底要不要持久化的邊界">persistence 與 fork latency</a>、<a href="/blog/backend/02-cache-redis/vendors/redis/memory-eviction-tuning/" data-link-title="Redis 記憶體與淘汰調校：maxmemory-policy、LFU 與碎片化的實戰判讀" data-link-desc="Redis 的記憶體是一條會在半夜爆掉的曲線：maxmemory 設多少、policy 選 LRU 還 LFU、碎片化什麼時候開始吃掉 30% RAM、OOM 時 noeviction 怎麼讓寫入全部失敗。本文展開 Redis 記憶體會計模型、eviction policy 的選型判讀、5 個把記憶體配置寫成 production 事故的踩坑，以及單機記憶體撞牆後該往 cluster 還是 DragonflyDB 走的邊界">記憶體與淘汰調校</a></li>
<li>平行 vendor：<a href="/blog/backend/02-cache-redis/vendors/aws-elasticache/" data-link-title="AWS ElastiCache" data-link-desc="AWS managed Redis / Valkey / Memcached">AWS ElastiCache</a>（managed multi-AZ failover）</li>
<li>Methodology：<a href="/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">Vendor 深度技術文章寫作方法論</a></li>
</ul>
]]></content:encoded></item></channel></rss>