クバネティス(Kubernetes) 環境で Redis-Cluster Java App 構築 ②


Failover テスト

🖧 Redis-Cluster ポッド(Pod) ダイアグラム

┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Redis-0 │ │ Redis-1 │ │ Redis-2 │ │ 🟢Master-1 │ │ 🟢Master-2 │ │ 🟢Master-3 │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ ┌────────────────│──────────────────┘ │ │ └──────────────────┐ └──────────────────┐ │ │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Redis-3 │ │ Redis-4 │ │ Redis-5 │ │ 🟡Replica-1 │ │ 🟡Replica-2 │ │ 🟡Replica-3 │ └──────────────┘ └──────────────┘ └──────────────┘

1. Failover(障害対応)テスト開始

SSH ターミナルを2つ開き、Terminal-1 では App ポッド(Pod) 生成、Redis ポッド(Pod) ダウンなどを実行し、 Terminal-2 で App ログを見ます。

    1) Java(Spring) App ポッド(Pod) 生成

    サーバ (Terminal-1 [~/kube/app/cluster/]) で実行
    $ kubectl apply -f app-deployment.yaml -> ポッド(Pod) 生成

    💻 App ログ

    サーバ (Terminal-2 [~/kube/app/cluster]) で実行
    $ kubectl logs -f -l app=spring-redis-cluster-app
    1秒あたり10件ずつ入力([SUCCESS])されることを確認できます。

    💻 Terminal-2

    2) Redis(Redis-0) 💥 ポッド(Pod) ダウン(delete)

    Terminal-1) $ date; kubectl delete pod redis-cluster-0 -> 時刻: 11:04:49

    💻 Redis-0 ログ

    ① 49秒 Redis ダウン (+0秒)
    ② 50秒 Redis 再起動 (+1秒)
    ③ 54秒 マスター -> 複製へ転換 (+5秒)

    📄 redis.log

    Redis-0 全ログ ▼
    📄 redis.log

    💻 Redis-4 ログ

    ① 49秒 マスターとの接続切断 (+0秒)
    ② 54秒 複製 -> マスターへ転換(昇格) (+5秒)

    📄 redis.log

    Redis-4 全ログ ▼
    📄 redis.log

    💻 App ログ

    Terminal-2) $ kubectl logs -f -l app=spring-redis-sentinel-app
    ① 49秒 Redis 接続切断 -> 再接続試行 (+0秒)
    ② 50秒 Redis-1,2 にはデータ入力される (+1秒) ③ 53秒~54秒 Redis-0 へ4件入力失敗 (+4秒~+5秒) の後、Redis-4 がマスターへ転換(昇格)し正常入力されます
    ④ 59秒 Redis-0 接続成功 -> 入力成功 (+10秒)

    💻 Terminal-2

    App 全ログ ▼
    💻 Terminal-2

    🖧 Redis-Cluster ポッド(Pod) ダイアグラム: Failover 状態 (Redis-4 Master-1)

    ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Redis-0 │ │ Redis-1 │ │ Redis-2 │ │ 🟡Replica-2 │ │ 🟢Master-2 │ │ 🟢Master-3 │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ ▲ │ │ │ ┌────────────────│──────────────────┘ │ │ └──────────────────┐ └──────────────────┐ │ │ │ │ ▼ │ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Redis-3 │ │ Redis-4 │ │ Redis-5 │ │ 🟡Replica-1 │ │ 🟢Master-1 │ │ 🟡Replica-3 │ └──────────────┘ └──────────────┘ └──────────────┘

    2. Failback(原状回復)テスト

    1) Redis(Redis-4) ポッド(Pod) 💥 ダウン(delete)

    💻 Terminal-1

    💻 Redis-4 ログ

    ① 49秒 Redis ダウン (+0秒)
    ② 52秒 Redis 再起動 (+3秒)
    ③ 55秒 マスター -> 複製へ転換 (+6秒)

    📄 redis.log

    Redis-4 全ログ ▼
    📄 redis.log

    💻 Redis-0 ログ

    ① 50秒 マスターとの接続切断 (+1秒)
    ② 55秒 複製 -> マスターへ転換 (+5秒)

    📄 redis.log

    Redis-0 全ログ ▼
    📄 redis.log

    💻 App ログ

    ① 50秒 Redis-4 接続切断 (+1秒)
    ② 54秒~56秒 5件入力失敗 (+4秒~+6秒) の後、Redis-0 がマスターへ転換(昇格)し正常入力されます
    ③ 59秒 Redis-4 接続成功 (+10秒)

    💻 Terminal-2

    App 全ログ ▼
    💻 Terminal-2

    🖧 Redis-Cluster ポッド(Pod) ダイアグラム: 原状回復状態 (Redis-0 Master-1)

    ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Redis-0 │ │ Redis-1 │ │ Redis-2 │ │ 🟢Master-1 │ │ 🟢Master-2 │ │ 🟢Master-3 │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ ┌────────────────│──────────────────┘ │ │ └──────────────────┐ └──────────────────┐ │ │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Redis-3 │ │ Redis-4 │ │ Redis-5 │ │ 🟡Replica-1 │ │ 🟡Replica-2 │ │ 🟡Replica-3 │ └──────────────┘ └──────────────┘ └──────────────┘

    🧑‍💻 テスト検討と核心まとめ

    今回の障害対応(Failover/Failback)テストで、クバネティス(Kubernetes) 環境の Redis-Cluster と Java(Spring Boot / Lettuce) アプリケーション間の実際の障害対応動作とクラスタトポロジー再構成過程を検証しました。

    ⏱️ 障害対応タイムライン (Timeline)

    区分 時刻 (相対時間) Redis Cluster ノード(Node) 動作 Java (Spring) App 動作
    障害発生 11:04:49 (+0秒) Master(Redis-0) ポッド(Pod) 終了 (delete) Redis-0 接続切断とバックグラウンド再接続試行。
    📍正常ノード(Node)(Redis-1, 2)向けデータは Async 処理によりブロッキングなく入力成功し続ける (11:04:50~)
    再試行上限到達 11:04:53~54 (+4~5秒) Replica(Redis-4) が障害検知後、昇格投票(Election) 進行中 Redis-0 スロット向けの一部リクエスト(4件)がバックグラウンド 5回(5秒)再試行しきい値を超えて最終失敗(FAIL)処理
    Failover 完了 11:04:54 (+5秒) 投票完了後 Replica(Redis-4) を新規 Master へ昇格 クラスタトポロジー更新試行 (ClusterTopologyRefresh)
    完全正常化 11:04:55 (+6秒) 新規 Master(Redis-4) が該当スロット(0~5460)の書き込み/読み取り開始 変更トポロジーが適用され すべてのスロットへのデータが再び100%正常(SUCCESS)書き込み再開
    旧マスター再起動 11:04:59 (+10秒) 再起動した旧マスター(Redis-0)が新規マスター(Redis-4)の Replica ノード(Node)へ再構成 Redis-0 ノード(Node) 接続再確立(Reconnected) 完了

    💡 実務適用のための核心示唆 (Key Takeaways) ▼
    • 1. Redis-0 マスターがダウンしても、生きている Redis-1,2 には正常に入力されます
    • 2. Cluster ノード(Node)間の障害検知と昇格時間 (約4~5秒):
      Master ノード(Node)がダウンすると、残りのノード(Node)が cluster-node-timeout の間状態を 確認(FAIL 判定)したあと、Replica ノード(Node)が自動昇格するまで 約4~5秒のダウンタイムが発生します。
    • 3. Spring / Lettuce クライアントの Dynamic Topology Refresh(トポロジー自動更新)の重要性:
      Redis Cluster 環境では Master ノード(Node)が変わると、そのノード(Node)が担当していたハッシュスロット(Hash Slot)の 所有権が移ります。 Spring App(Lettuce)の クラスタトポロジー自動更新オプションが動き、変わった Master IP とスロット情報をリアルタイムで検知したため、別途のサービス再起動なしで連携が維持されました。
    • 4. Client-Side Retry(5秒)の効果と限界:
      1回目のテスト(Redis-0 ダウン)では App の Retry(5秒)範囲内である5秒時点で Failover が 完了し、成功率100%(消失0件)でした。 一方、2回目のテスト(Redis-4 ダウン)ではトポロジー再調整時間の差で1件の失敗 リクエスト(keyA-00000607)が発生しました。 したがって実務では App Retry だけでなく、非同期キュー処理と再試行メカニズムを合わせて設計しなければ 完全な無停止は保証できません。
    • 5. Cluster の自動ノード(Node)役割再構成(Re-configuration):
      復旧した旧 Master(Redis-0) ノード(Node)が再び起動すると、クラスタバス経由で変わったノード(Node)状態を 受け取り、自動で新規 Master(Redis-4)の Replica ノード(Node)へ転換します。 したがって管理者の手動介入なしでも、クラスタ全体の HA(高可用性)ターゲット数が復旧します。
    • 6. ユーザ体験(UX)とタイムアウトの考慮事項:
      クライアント観点では、障害発生時に約4~5秒の応答遅延(Latency)が発生します。 Web/App フロントエンド API 呼び出し時にユーザ画面が止まらないよう、Front-end Timeout と Circuit Breaker(Resilience4j など)を適切に組み合わせることを推奨します。
    🛡️ Front-end Timeout と Circuit Breaker の説明 ▼

    🛡️ Redis 障害時にシステム麻痺を防ぐ安全装置

    Sentinel の障害対応(Failover)中に発生する 4~5秒の応答遅延(Latency) は、バックエンドサーバと ユーザ画面へ障害が伝わる連鎖障害(Cascading Failure)を起こし得ます。 これを防ぐため、実務では次の2つの安全装置を必ず組み合わせて構築します。

    1. Front-end Timeout (フロントエンドタイムアウト)

    ユーザのブラウザやモバイルアプリがバックエンド API を呼んだあと、「応答が来ないときに無限待ちせず、リクエストを諦める最大時間」です。

    • 問題状況 (Timeout 未設定時): Redis 障害中に4秒応答が止まると、焦ったユーザが '再読み込み' や 'ボタン' を連打します。これによりバックエンド Tomcat/Spring スレッド(Thread)が枯渇し、Web サーバ全体がダウンします。
    • 解決策 (2~3秒 Timeout 適用): 2秒以内に応答がなければ直ちにリクエストを止め、「一時的なネットワーク遅延です。しばらくして再試行してください」 という案内を出してバックエンド暴走を遮断します。
    2. Circuit Breaker (サーキットブレーカ - Resilience4j)

    電気盤の '遮断器' のようにバックエンド(Spring) アプリケーション内部に置き、Redis 障害検知時に 遮断器を開いて(Open) Redis への接続試行を直ちに止めるライブラリです。

    [正常状態] 🟢 CLOSED ──(エラー/遅延の急増)──► 🔴 OPEN [遮断状態: 直ちに Fallback 実行] ▲ │ │ │ (設定した待機時間後) └────────────── (成功検証) ─────────── 🟡 HALF-OPEN [少数リクエストのテスト]
    • 🟢 CLOSED (正常): すべてのキャッシュ/データリクエストを Redis へ正常に渡します。
    • 🔴 OPEN (遮断): Redis 遅延/エラー率が指定値を超えると遮断器を開き、 Redis 呼び出しを試みず、あらかじめ準備した代替ロジック(Fallback: 例: Main RDB 直接照会)を 直ちに実行します。
    • 🟡 HALF-OPEN (検証): Redis が復旧したか確認するため少数のリクエストだけ送り、 正常動作が確認できれば再び 🟢 CLOSED 状態へ戻します。

    🔄 障害発生時の2技術の連携プロセス

    • 0秒 (Redis Master ダウン): Sentinel が Failover を始め、応答遅延が発生
    • 1~2秒 (Circuit Breaker 作動): Spring App の Resilience4j がエラー/遅延を検知し、遮断器を 🔴 OPEN へ転換 ➔ Redis の代わりに RDB 照会またはデフォルト値返却(Fallback)
    • 2秒 (Front-end Timeout 作動): フロントエンドがリクエストを安全に切り、無限ローディングとユーザ連打による WAS 暴走を防止
    • 4~5秒 (Failover 完了 & 自動復旧): Sentinel が新規 Master 昇格を完了すると、Resilience4j がそれを検知して 🟢 CLOSED に戻し、キャッシュサービスを自動正常化

    << K8s Redis-Cluster App ① K8s Redis-Sentinel 準備 ① >>

    Email 返事がかかってなれば、メールでお知らせします。