分享程式代碼相關筆記
目前文章總數:217 篇
最後更新:2026年 01月 31日
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# 上實現分布式鎖
RedLock 非 Redis 內建的功能 2016 年 Redis 創辦人 Antirez (本名: Salvatore Sanfilippo) 提出 Redlock 演算法概念
歷史故事可以參考:
https://blog.brachiosoft.com/en/posts/redis/
但實際開發的 RedLock.NET 並非 Antirez 本人
目前穩定版本為 2022/2/22 日釋出的 Release 版本
並且截止至 2025/9 止,社群還有 Issue 的討論,但已經變少
分布式環境下,多個應用實例可能同時訪問同一個資源(如數據庫記錄、文件等),如果沒有適當的協調機制,會導致以下狀況:
| 1. 數據不一致 | 多個實例同時修改數據,造成相互覆蓋或髒數據 |
| 2. 重複執行 | 同一任務被多個實例重複處理,例如重複發送消息、重複扣款 |
| 3. 資源競爭 | 多個實例爭搶有限資源,造成系統混亂 |
這時有了 RedisLock 實現的 Redis 分布式鎖,可以防止以上的狀況發生,假設電商庫存系統如圖示意:
沒有分布式鎖的狀況:
有分布式鎖的狀況,確保了原子性:
早期官方 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.
官方有提供 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.
完整實作 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;
}
}
Redis 三大模式對比
| 特性 | 主從複製 (Master-Slave) | 哨兵模式 (Sentinel) | 集群模式 (Cluster) |
| 數據分片 | 無(單主節點) | 無(單主節點) | 有(哈希槽分散在多節點) |
| 高可用 | 無(需人工故障切換) | 有(自動故障轉移) | 有(自動故障轉移) |
| 寫性能 | 受單機瓶頸限制 | 受單機瓶頸限制 | 水平擴展(高) |
| 讀性能 | 讀寫分離(從節點讀) | 讀寫分離(從節點讀) | 節點擴展(高) |
| 部署複雜度 | 低 | 中 | 高 |
| 適用場景 | 數據備份、讀寫分離 | 高可用性要求高的應用 | 大數據量、高併發寫入 |
依照官方代碼圖解說明,為何 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
Server A 請求鎖:成功取得,因為這時沒有任何鎖,並且 Server A 執行事務, 不釋放鎖
Master 異常 建立鎖後,Master 這時異常,沒有辦法實現資料同步到 Slave
※這時 Server A 持續進行事務,不知道 Master Redis 異常
Slave 接手:Slave 知道狀況,接手當 Master,實現 從->主 與 主->從 轉換
※這時 Server A 持續進行事務,不知道 Master Redis 異常
Server B 請求鎖:Server B 也想執行相同的事務,呼叫 Master (原Slave),確認沒有鎖,成功拿到 鎖進行事務
最終結果:Server A 與 Server B 都拿到了鎖,重複執行事務,因此為何 Redis 官方說需要建議用 RedLock.NET
※此為 Redis 設計架構,而非 BUG,因此官方會建議想要分散式使用 Redis 鎖,請用 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 可以認為自己持有鎖」,解決節點之間不共享狀態問題
我們以 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),允許建鎖 |
Server A 請求鎖:透過 RedLock.NET 成功取得,因為這時沒有任何鎖,並且 Server A 執行事務中, 不釋放鎖
※在請求中
RedLock 取得請求:透過 Server A 傳來的 TTL 存取時間,在 3 台 Master 確認是否可建立鎖
RedLock 建立鎖 : 3 個 Master 都是活的,符合最小多數決 2 (3/2 + 1),允許建鎖
※公式 (N / 2 + 1) 符合這個存活機器數才可成功建立分布式鎖
我們接著假設 3 台 Master Redis 機器有壞掉的情況,現在 Master 2 異常,但 Server A 已經取得鎖在執行事務 未釋放鎖
這時 Server B 想要取得鎖,雖然當前 Master 1,3 Redis 機器有 2 台存活,符合存取
但是 Server A 尚未釋放此鎖,因此 Server B 無法取得到鎖 (實現分布式鎖)
我們接著假設 3 台 Master Redis 機器有壞掉的情況,現在 Master 2,3 異常,但 Server A 已經取得鎖在執行事務 未釋放鎖
這時 Server B 想要取得鎖,當前 Master 2,3 Redis 機器有 1 台存活,不符合存取
無論 Server A 有無釋放此鎖, Server B 永遠都無法取得到鎖,因為現在是 不可用狀態
RedLock.NET 表示: 現在不安全寧可完全卡死,也不冒險
為了安全的 Redis 分布式事務,必須先將 Master Redis 到最小可用機器數,才能恢復使用
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 解決了以下問題:
| 問題類型 | 描述 | 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,毫秒級漂移可接受 |
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)的系統,
因為它們不依賴系統時鐘,且能提供單調遞增的節點版本號。
通常主流為了可擴展性, 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 還是 可以引入系統,但等於沒用
實務上為了水平擴展 + 使用 RedLock.NET 的 Redis 分布式鎖,通常會採用以下兼容做法:
但成本的費用勢必會增加
| 1. 建立 Master 1~3 各自獨立機器位置 |
| 2. 保留 Cluster Redis 也是獨立的 |
| 3. 需要 Redis 分布式鎖時,走向 Master 1~3 |
| 4. 不需要 Redis 分布式鎖時,走向 Cluster Redis |
※Master 1,2,3 與 Cluster 的機器位置都要獨立分開
雖然 RedLock 在理論上非常優雅且安全,但對於 90% 的 .NET 專案來說,往往有可能變成「過度設計」,現實面中,營運成本增加太大
應先審視根本問題:真的需要 Redlock 嗎? (其實就是先評估業務容錯度)
| 是否使用 | 情境 | 舉例 | 說明 |
|---|---|---|---|
| × | 一般併發控制 | 防止重複發送通知、簡單的排程執行 | 使用單一 Redis 叢集即可。 |
| 即便萬一 Failover 導致鎖失效(機率極低),對業務影響若只是「多發一封通知」,那就不值得額外成本去跑 | |||
| ○ | 核心金流/庫存扣減 | 虛擬錢包扣款、限時搶購 | 這時候 Redlock 的成本反而是「最便宜」的。比起因併發導致的帳務錯誤或超賣損失。 |
| 多付 2-3 台微型 Redis 實例的月費(在雲端可能幾十塊美金)是非常划算的。 |