設計Redis鎖的思路
Redis鎖
當Redis被用來做分布式鎖、限流、去重的時候,key應該如何設計?
建立模型
在建立鎖之前,先根據我們要解決的問題想一下以下幾個點:
- 什麼東西會被加鎖?
- 在做什麼的時候需要加鎖?
- 在什麼時間窗口內需要加鎖?
- 在什麼環境下鎖生效?
可以把key想像為一個業務範圍的名字。
例如: rate_limit:prod:payment:user:12345:create_order:2026-04-13T12:00
這串key的意思是在 prod 環境,payment 業務裡,user 123 在 2026-04-13 12:00 這一分鐘內 create_order 的次數。
在比如說: lock:prod:order_id:0519:pay
表示在prod環境裡,對 order_id 0519 的 pay 操作加鎖。
所以 key 設計的本質是:把抽象的控制範圍變成一個穩定、唯一、可過期、可觀察的名字。
一個好的key
應該設計有如下結構的鎖:
{purpose}:{env}:{domain}:{scope_type}:{scope_id}:{action}:{window / version}
purpose: lock / rate_limit / deduplication 表示key做什麼?
env: prod / staging / dev 表示在哪個環境?
domain: payment / user / inventory 表示業務範圍?
scope_type: user_id / order_id / ip 表示鎖的範圍類型?
scope_id: 12345 / 0519
action: create_order / pay 表示鎖的具體行為?
window / version: 2026-04-13T12:00 / v1 表示鎖的時間窗口或者版本?
rate_limit:prod:auth:user:123:login:202604211035
rate_limit:prod:auth:ip:1.2.3.4:login:202604211035
lock:prod:payment:order:98765:capture
dedupe:prod:notification:user:123:send_email:template_abc
一些設計
鎖的粒度
lock:prod:order:pay
表示所有訂單使用一把鎖,會導致大量的業務邏輯阻塞。
lock:prod:order:0519:pay:request_id:abc
每個請求都有不同的request_id,那就失去了鎖的意義,因為每次都會拿到不同的鎖。
鎖的粒度應該有如下的考慮:
哪些請求同時執行,會互相破環狀態?
Redis鎖
Redis做鎖的時候,常見的命令是
SET key value NX PX
其中:
key是鎖的名字,根據上面的設計原則來定義。value是鎖的持有者,可以是請求ID、主機ID等唯一標識,方便後續的解鎖和觀察。NX表示只有當鍵不存在時才會設置成功,適合用於鎖的場景。PX表示設置鍵的過期時間,適合用於自動解鎖。
刪除的時候
DEL key
這樣會出現問題,因為會發生:
- A請求獲取鎖成功,設置過期時間為10秒。
- A請求處理業務邏輯,可能需要5秒。
- A請求完成業務邏輯,準備釋放鎖,
- 此時鎖已經過期了,A請求刪除的其實是別人的鎖,導致其他請求的鎖被誤刪。
正確的做法應該是compare token and delete,確保只有鎖的持有者才能釋放鎖。
DynamoDB鎖
DynamoDB做鎖的時候,通常一個lock key就是一條item。
PK = LOCK#prod#order#order_id#123#pay
{
"PK": "LOCK#prod#order#order#98765#pay",
"owner_token": "uuid",
"lease_until": 1776748500,
"created_at": 1776748470,
"fencing_token": 1024
}
獲取鎖的時候用:
attribute_not_exists(PK) OR lease_until < :now
如果item不存在,可以拿鎖。
如果item存在但是過期了,也可以拿鎖。
比較
Redis 很適合:
- 秒級 / 分鐘級限流。
- 短租約分布式鎖。
- 高頻快速計數。
- 需要低延遲的控制面。
Redis key 設計時特別注意:
- key 數量會不會爆炸。
- 每個 key 是否都有合理 TTL。
- 熱 key 是否會集中在少數幾個 key 上。
- Redis Cluster 中多 key Lua 操作是否落在同一 hash slot。
Redis實戰
第一步
在本地docker中開啟一個Redis容器

第二步
開啟兩個終端,在終端中分別輸入
docker exec -it redis-1 redis-cli
第三步
在第一個終端中輸入
SET lock:prod:order:order_id:0001:pay owner-A NX PX 10000
返回OK,表示鎖獲取成功。
同時在第二個終端中輸入
SET lock:prod:order:order_id:0001:pay owner-B NX PX 10000
返回(nil),表示鎖已經被A持有,B獲取鎖失敗。

十秒後,鎖過期了,B再次嘗試獲取鎖:
SET lock:prod:order:order_id:0001:pay owner-B NX PX 10000
返回OK,表示B獲取鎖成功。
錯誤的刪除鎖的方式
如果B現在持有鎖,A嘗試刪除鎖:
DEL lock:prod:order:order_id:0001:pay
這樣會導致B的鎖被誤刪,A應該先檢查鎖的持有者是否是自己:
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" 1 lock:demo:payment:order:1001:pay owner-A