首頁

目前文章總數:217 篇

  

最後更新:2026年 01月 31日

0026. RedLock.NET 在 Redis Cluster 中能用嗎?實務架構與風險分析

日期:2026年 02月 28日

標籤: Linux Ubuntu Docker C# Docker-Compose Container

摘要:資訊筆記


應用所需:1. 已安裝 Docker
     2. 已安裝 Dotnet SDK 8.0 以上版本(本篇範例使用,用於啟用 Redis 容器)
介紹主旨:為何 Redis 分布式鎖官方推薦會 RedLock
     2. RedLock 解決了原本 Redis 分布式鎖什麼問題
     3. RedLock 遺留了什麼問題,跟現代實務中要怎麼處理
範例檔案:本篇範例代碼
應用參考:Redis RedLock.NET 分布式鎖實戰(C# / .NET)— 阻塞式 vs 非阻塞式互斥寫入應用
基本介紹:本篇分為 4 大部分。
第一部分:RedLock.NET 介紹
第二部分:Redis 現存問題
第三部分:RedLock 解決方案
第四部分:RedLock 致命硬傷 & 爭議






第一部分:RedLock.NET 介紹

Step 1:RedLock.NET 基本介紹

RedLock.NET 是一套開源庫,以下擷取自 RedLock.net Github
※RedLock.NET 是基於 ASP .NET(C#) 構建的開源庫,實現 RedLock 演算法

An implementation of the Redlock distributed lock algorithm in C#.

Makes use of the excellent StackExchange.Redis library.

Distributed locks are useful for ensuring only one process is using a particular resource at any given time (even if the processes are running on different machines).

RedLock.net is available using NuGet - search for RedLock.net.

Note: RedLock 2.2.0+ requires StackExchange.Redis 2.0+ - if you need to use StackExchange.Redis 1.x please continue to use RedLock.net 2.1.0.


內容簡要:依賴於 StackExchange.Redis ,並且使用 Redis 在 C# 上實現分布式鎖



Step 2:Redlock 誕生原由 - 非 Redis 內建功能

RedLock 非 Redis 內建的功能 2016 年 Redis 創辦人 Antirez (本名: Salvatore Sanfilippo) 提出 Redlock 演算法概念
歷史故事可以參考:

https://blog.brachiosoft.com/en/posts/redis/


但實際開發的 RedLock.NET 並非 Antirez 本人

Step 3:Redlock.NET 開源狀況

目前穩定版本為 2022/2/22 日釋出的 Release 版本


並且截止至 2025/9 止,社群還有 Issue 的討論,但已經變少


Step 4:解決的問題 - 實現分布式鎖

分布式環境下,多個應用實例可能同時訪問同一個資源(如數據庫記錄、文件等),如果沒有適當的協調機制,會導致以下狀況:

1. 數據不一致 多個實例同時修改數據,造成相互覆蓋或髒數據
2. 重複執行 同一任務被多個實例重複處理,例如重複發送消息、重複扣款
3. 資源競爭 多個實例爭搶有限資源,造成系統混亂


這時有了 RedisLock 實現的 Redis 分布式鎖,可以防止以上的狀況發生,假設電商庫存系統如圖示意:
沒有分布式鎖的狀況:


有分布式鎖的狀況,確保了原子性:




第二部分:Redis 現存問題

Step 1:不具備分布式原因 - SetNX 鎖

早期官方 Redis 鎖的 SETNX 已棄用 (deprecated)
從 Redis 2.6.12 版本後,官方改用 SET 帶 NX 的方式取代

It can be replaced by SET with the NX argument when migrating or writing new code.



Step 2:不具備分布式原因 - Set + NX 鎖

官方有提供 Lua 腳本 + C# 用鎖的方法,可參考Set + NX 官方文件
往下拉到 Pattern 的地方,可以看到官方的建議(簡化意思): 不建議用這個鎖法,建議用 RedLock 的鎖,有更好的保證與容錯能力

Patterns 
Note: The following pattern is discouraged in favor of the Redlock algorithm which is only a bit more complex to implement,
 but offers better guarantees and is fault tolerant.




Step 3:Set + NX代碼範例

完整實作 Redis 的 Set + NX 代碼可參考 範例代碼
實例呼叫 AcquireAsync() 方法建立鎖,執行完成後,最終呼叫 ReleaseAsync() 釋放鎖

    public class RedisLockService : IRedixLockService
    {
        private readonly IDatabase _db;

        public RedisLockService(IConnectionMultiplexer redis)
        {
            _db = redis.GetDatabase();
        }

        /// <summary>
        /// 取得鎖
        /// </summary>
        public async Task<(bool Acquired, string LockValue)> AcquireAsync(
            string lockKey,
            TimeSpan ttl)
        {
            // 必須唯一,用來做 unlock ownership 驗證
            string lockValue = Guid.NewGuid().ToString("N");

            bool acquired = await _db.StringSetAsync(
                lockKey,
                lockValue,
                expiry: ttl,
                when: When.NotExists
            );

            return (acquired, lockValue);
        }

        /// <summary>
        /// 釋放鎖
        /// </summary>
        public async Task<bool> ReleaseAsync(string lockKey, string lockValue)
        {
            const string lua = @"
if redis.call('GET', KEYS[1]) == ARGV[1] then
    return redis.call('DEL', KEYS[1])
end
return 0
";
            var result = await _db.ScriptEvaluateAsync(
                lua,
                new RedisKey[] { lockKey },
                new RedisValue[] { lockValue }
            );

            return (int)result == 1;
        }
    }




Step 4:Redis - 模式差異

Redis 三大模式對比

特性 主從複製 (Master-Slave) 哨兵模式 (Sentinel) 集群模式 (Cluster)
數據分片 無(單主節點) 無(單主節點) 有(哈希槽分散在多節點)
高可用 無(需人工故障切換) 有(自動故障轉移) 有(自動故障轉移)
寫性能 受單機瓶頸限制 受單機瓶頸限制 水平擴展(高)
讀性能 讀寫分離(從節點讀) 讀寫分離(從節點讀) 節點擴展(高)
部署複雜度
適用場景 數據備份、讀寫分離 高可用性要求高的應用 大數據量、高併發寫入



Step 5:Redis 不具備分布式原因 - 單點失效情境

依照官方代碼圖解說明,為何 Reids 現存的問題
備註1:主要以 Redis 的 主從同步(Master-Slave) 模式說明,因為 哨兵模式 (Sentinel) 本質上與主從同步相同
備註2: 集群模式(Cluster) 模式則屬於分散多點,因此必定只會有一個 Redis 會有鎖,可以不用考慮

情境描述順序:

1. Server A 請求鎖 成功取得,因為這時沒有任何鎖,並且 Server A 執行事務, 不釋放鎖
2. Master 異常 建立鎖後,Master 這時異常,沒有辦法實現資料同步到 Slave
  ※這時 Server A 持續進行事務,不知道 Master Redis 異常
3. Slave 接手 Slave 知道狀況,接手當 Master,實現 從->主主->從 轉換
  ※這時 Server A 持續進行事務,不知道 Master Redis 異常
4. Server B 請求鎖 Server B 也想執行相同的事務,呼叫 Master (原Slave),確認沒有鎖,成功拿到 鎖進行事務



最終結果:Server A 與 Server B 都拿到了鎖,重複執行事務,因此為何 Redis 官方說需要建議用 RedLock.NET

Step 6:單點失效情境圖解 - Server A 請求鎖

Server A 請求鎖:成功取得,因為這時沒有任何鎖,並且 Server A 執行事務, 不釋放鎖


Step 7:單點失效情境圖解 - Master 異常

Master 異常 建立鎖後,Master 這時異常,沒有辦法實現資料同步到 Slave
※這時 Server A 持續進行事務,不知道 Master Redis 異常


Step 8:單點失效情境圖解 - Slave 接手

Slave 接手:Slave 知道狀況,接手當 Master,實現 從->主主->從 轉換
※這時 Server A 持續進行事務,不知道 Master Redis 異常


Step 9:單點失效情境圖解 - Server B 請求鎖

Server B 請求鎖:Server B 也想執行相同的事務,呼叫 Master (原Slave),確認沒有鎖,成功拿到 鎖進行事務

最終結果:Server A 與 Server B 都拿到了鎖,重複執行事務,因此為何 Redis 官方說需要建議用 RedLock.NET 


※此為 Redis 設計架構,而非 BUG,因此官方會建議想要分散式使用 Redis 鎖,請用 RedLock




第三部分:RedLock 解決方案

Step 1:RedLock 解決方案

Redis 當前分布式鎖的主要原因 : 「鎖的正確性」押在「一台 Redis」上
RedLock 因此想要解決上述的問題,進行了以下做法:

1. 準備 N 台(奇數)彼此獨立的 Redis 實例 建議 N = 5,N = 3 亦可成立,用以取得多數決(N/2 + 1),偶數也可以,會忽
2. 各 Redis 實例在失效上必須彼此獨立 主從同步或 Redis Cluster 在 RedLock 中僅視為單一實例
  RedLock 不會、也不能自動將主從節點拆解為獨立鎖節點。
3. 多數決 + 時間限制 Client 對每一個 Redis 實例執行 SET lockKey value NX PX ttl
  若在有效時間內,成功取得 ≥ N/2 + 1 個鎖,並且扣除網路延遲與 clock drift 後仍在 TTL 內,則視為鎖成立。


簡言之 RedLock 核心思想:「不要相信任何一台 Redis,任何時候,最多只有一個 Client 可以認為自己持有鎖」,解決節點之間不共享狀態問題

Step 2:RedLock.NET 機制 - 多節點投票

我們以 3 個獨立的 Redis Master1、2、3 來舉例說明,多節點投票機制
多節點投票機制描述順序:

1. Server A 請求鎖 透過 RedLock.NET 成功取得,因為這時沒有任何鎖,並且 Server A 執行事務中, 不釋放鎖
2. RedLock 取得請求 透過 Server A 傳來的 TTL 存取時間,在 3 台 Master 確認是否可建立鎖
3. RedLock 建立鎖 3 個 Master 都是活的,符合最小多數決 2 (3/2 + 1),允許建鎖



Step 3:多節點投票 - Server A 請求鎖

Server A 請求鎖:透過 RedLock.NET 成功取得,因為這時沒有任何鎖,並且 Server A 執行事務中, 不釋放鎖
※在請求中


Step 4:多節點投票 - RedLock 取得請求

RedLock 取得請求:透過 Server A 傳來的 TTL 存取時間,在 3 台 Master 確認是否可建立鎖


Step 5:多節點投票 - RedLock 建立鎖

RedLock 建立鎖 : 3 個 Master 都是活的,符合最小多數決 2 (3/2 + 1),允許建鎖
※公式 (N / 2 + 1) 符合這個存活機器數才可成功建立分布式鎖


Step 6:多節點投票 - 機器損壞的狀況-1

我們接著假設 3 台 Master Redis 機器有壞掉的情況,現在 Master 2 異常,但 Server A 已經取得鎖在執行事務 未釋放鎖


Step 7:多節點投票 - 機器損壞的狀況-1 - ServerB 想取鎖

這時 Server B 想要取得鎖,雖然當前 Master 1,3 Redis 機器有 2 台存活,符合存取
但是 Server A 尚未釋放此鎖,因此 Server B 無法取得到鎖 (實現分布式鎖)


Step 8:多節點投票 - 機器損壞的狀況-2

我們接著假設 3 台 Master Redis 機器有壞掉的情況,現在 Master 2,3 異常,但 Server A 已經取得鎖在執行事務 未釋放鎖


Step 9:多節點投票 - 機器損壞的狀況-2 - ServerB 想取鎖

這時 Server B 想要取得鎖,當前 Master 2,3 Redis 機器有 1 台存活,不符合存取
無論 Server A 有無釋放此鎖, Server B 永遠都無法取得到鎖,因為現在是 不可用狀態


Step 10:多節點投票 - 機制解釋

RedLock.NET 表示: 現在不安全寧可完全卡死,也不冒險
為了安全的 Redis 分布式事務,必須先將 Master Redis 到最小可用機器數,才能恢復使用


Step 11: TTL 有效時間 - 補充

RedLock.NET 會以請求的 傳來的時間 並且減去 有效存活時間 (也就是相對時間),來做存活的判斷
即使現在有以下時區狀況:

Redis 機器 時間
Master A 2026/1/1 09:00:00
Master B 2026/1/1 01:00:00
Master C 2026/1/2 10:00:00


但實務上還是建議每個 Master Redis 機器上的時間一致
RedLock.NET 官方建議 TTL 要設定足夠大 (TTL ≫ 網路延遲 + Redis 最壞回應時間)
※通常 5 ~ 30 秒間

RedLock 不依賴 Redis 節點之間的時鐘同步,因為鎖的 TTL 是以相對時間計算,且有效性由 Client 的耗時與多數決共同判斷;
但在極端時間漂移或 TTL 設定過小的情況下,安全窗口會縮小,因此實務上仍建議所有節點進行基本的時間同步以降低風險。






第四部分:RedLock 致命硬傷 & 爭議

Step 1:爭議 - 解決 & 沒解決 問題

RedLock 解決了以下問題:

問題類型 描述 RedLock 解法 / 原理 補充說明
單點故障 某台 Redis 掛掉 多數決:只要 ≥ ⌊N/2⌋+1 成功即可取得鎖 例如 5 台 Redis,最多 2 台掛掉仍可安全取得鎖
網路分區 / 短暫不可達 某些節點暫時不可連 多數決:只要多數成功就鎖成立 不需要所有節點都可達,避免單一節點阻塞
Client 當機 / 卡死 Client 拿到鎖但程式崩潰 TTL 時間限制,自動過期 避免死鎖,鎖會在 TTL 後自動釋放
Redis IO 抖動或延遲 某些節點慢 多數決 + TTL 計算有效性 RedLock 計算實際耗時,防止拿到已過期的鎖
時鐘漂移 / NTP 不準 各 Client 或 Redis 時間不同 TTL + 相對耗時判斷 RedLock 不依賴 Redis 之間時鐘同步
Failover / Slave 升級為 Master Redis Master 挂掉,Slave 升級 多數決 + TTL RedLock 保證仍不會出現雙重鎖
鎖唯一性(理論上) 保證同一時間只有一個 Client 可以認為自己持有鎖 多數決 + TTL + value 驗證 Safety 優先於 Availability


但 RedLock 的機制,延伸了以下問題,並未完美解決

問題類型 描述 為什麼 RedLock 無法解決 補充說明
單 Redis Cluster / 分片架構 Key 只存在單個 Master 無法在多個 Master 上寫鎖 → 無法達到多數決 將 RedLock 套在 Cluster 上實際上只是單機鎖
多數節點永久掛掉 過半節點不可用 RedLock 設計上拒絕服務,無法強制降低多數門檻 系統會「鎖不可用」直到節點恢復
絕對強一致性 / Linearizability RedLock 不能保證完全無雙重鎖 多數決 + TTL 是概率安全,不是同步共識 Split-brain 理論上仍有微小可能(延遲極端情況)
極端網路延遲 + TTL 太短 Client 花太久才取得鎖 TTL 到期後判斷失敗 安全性優先,可能造成暫時不可用
Redis 节点时间严重漂移 時間過快或過慢導致 TTL 計算失準 只能靠合理 TTL + 時鐘同步控制風險 官方建議所有節點開啟 NTP,毫秒級漂移可接受



Step 2:爭議 - Martin Kleppmann 與 Antirez

Martin Kleppmann 是 《Designing Data-Intensive Applications》一書作者,曾經在 2016 年發表文章點出 RedLock 的一些問題
Martin Kleppmann 與 Redis 之父 Antirez 關於 Redlock 的辯論持續一段時間

Martin Kleppmann 提出的觀點主要可以拆解為以下三個層次:

項目 扼要內容
1. 演算法對「時間」的過度依賴 (Clock Drift) Redlock 嚴重依賴系統時鐘。但在分散式系統中,時鐘並不可靠
2. 系統停頓問題 (The “Pause” Problem) 客戶端在拿到鎖後發生了停頓
3. 缺乏「防護令牌」機制 (Fencing Token) 他主張一個安全的分散式鎖必須提供一個遞增的令牌


詳細的討論,可以從網路爬文,或檢閱他的文章連結,看看 Martin Kleppmann 對 RedLock 的完整描述內容

https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html



Marketin 最終結論 (Martin 將鎖的用途分為兩類):

為了效率 (Efficiency):為了避免重複執行同一個任務(例如發兩次 Email)。這種情況下,即便 Redlock 偶爾出錯也沒關係,但 Redlock 太重、太慢了。

為了正確性 (Correctness):為了防止資料損壞(例如銀行轉帳)。這種情況下,Redlock 因為時鐘與停頓問題而不夠安全。

他建議:
如果你真的在乎正確性,應該使用像 ZooKeeper 或 Etcd 這種基於共識演算法(Paxos/Raft)的系統,
因為它們不依賴系統時鐘,且能提供單調遞增的節點版本號。



Step 3:致命硬傷 - 成本考量

通常主流為了可擴展性, Redis 的模式都會採用 Cluster ,好處是對開發人員只有一個端點,還能兼具水平擴展
但 Redis Cluster 與 RedLock 設計違背,RedLock.NET 有明確要求:

1. 每個 Redis 節點必須彼此獨立 節點之間沒有複寫關係、沒有分片 slot 依賴。
2. 需要奇數節點(N ≥ 3),多數決門檻 ⌊N/2⌋ + 1 例如 3 台 → 需要 2 台成功。
3. 鎖取得與釋放完全依賴 SET NX + TTL 不依賴任何複寫或 Cluster slot 分配。
4. 不能使用 Cluster 分片 因為同一個 key 只能存在單個 Master,無法跨多個 Master 做多數決。


因此要符合 RedLock 的分布式需要至少 3 台,獨立的 Master Redis ; Cluster Redis 只能視為 1 個
※因此 純 Redis Cluster + RedLock.NET 架構下,RedLock.NET 還是 可以引入系統,但等於沒用



Step 4:RedLock 實務上權衡方案

實務上為了水平擴展 + 使用 RedLock.NET 的 Redis 分布式鎖,通常會採用以下兼容做法:
但成本的費用勢必會增加

1. 建立 Master 1~3 各自獨立機器位置
2. 保留 Cluster Redis 也是獨立的
3. 需要 Redis 分布式鎖時,走向 Master 1~3
4. 不需要 Redis 分布式鎖時,走向 Cluster Redis


※Master 1,2,3 與 Cluster 的機器位置都要獨立分開


Step 5:現實面 - 應依照業務成本考量

雖然 RedLock 在理論上非常優雅且安全,但對於 90% 的 .NET 專案來說,往往有可能變成「過度設計」,現實面中,營運成本增加太大
應先審視根本問題:真的需要 Redlock 嗎? (其實就是先評估業務容錯度)

是否使用 情境 舉例 說明
× 一般併發控制 防止重複發送通知、簡單的排程執行 使用單一 Redis 叢集即可。
      即便萬一 Failover 導致鎖失效(機率極低),對業務影響若只是「多發一封通知」,那就不值得額外成本去跑
核心金流/庫存扣減 虛擬錢包扣款、限時搶購 這時候 Redlock 的成本反而是「最便宜」的。比起因併發導致的帳務錯誤或超賣損失。
      多付 2-3 台微型 Redis 實例的月費(在雲端可能幾十塊美金)是非常划算的。