你以為的"高并發(fā)":分布式鎖 VS 真實(shí)的高并發(fā):UUID鎖
一、問題背景與核心挑戰(zhàn)
1.1 重復(fù)提交現(xiàn)象的產(chǎn)生
在分布式系統(tǒng)架構(gòu)下,接口重復(fù)提交問題普遍存在于以下場景:
? 用戶界面連續(xù)快速點(diǎn)擊觸發(fā)多次請求
? 移動(dòng)端弱網(wǎng)環(huán)境下的自動(dòng)重試機(jī)制
? 微服務(wù)架構(gòu)中的消息隊(duì)列重復(fù)消費(fèi)
? 客戶端與服務(wù)器時(shí)鐘不同步導(dǎo)致的補(bǔ)償請求
1.2 問題引發(fā)的風(fēng)險(xiǎn)
1. 支付系統(tǒng)中的重復(fù)扣款
2. 訂單系統(tǒng)生成重復(fù)交易記錄
3. 庫存超賣導(dǎo)致的業(yè)務(wù)異常
4. 消息通知騷擾用戶
5. 統(tǒng)計(jì)指標(biāo)數(shù)據(jù)失真
1.3 傳統(tǒng)解決方案的局限性
方案類型 | 優(yōu)點(diǎn) | 缺點(diǎn) |
前端防抖 | 實(shí)現(xiàn)簡單 | 無法防范繞過客戶端的請求 |
Token機(jī)制 | 服務(wù)端可控 | 增加一次交互流程 |
數(shù)據(jù)庫唯一索引 | 可靠性高 | 影響寫入性能,無法處理復(fù)雜邏輯 |
本地內(nèi)存鎖 | 零延遲 | 僅限單機(jī)環(huán)境 |
二、分布式鎖技術(shù)選型
2.1 基于UUID的鍵值設(shè)計(jì)
采用通用唯一標(biāo)識(shí)符作為鎖的鍵名,保證全局唯一性:
// 使用UUIDv4生成算法
String requestId = UUID.randomUUID().toString().replace("-", "");2.2 Redis分布式鎖實(shí)現(xiàn)方案
使用Redis的原子性操作保障鎖的可靠性:
SET lock_key uuid_value NX EX 30參數(shù)說明:
? NX:僅當(dāng)key不存在時(shí)設(shè)置
? EX:設(shè)置過期時(shí)間(秒)
? 30:自動(dòng)釋放鎖的時(shí)間窗口
2.3 鎖機(jī)制的核心特征
1. 互斥性:同一時(shí)刻僅有一個(gè)客戶端持有鎖
2. 可重入性:相同客戶端可重復(fù)獲取鎖
3. 容錯(cuò)性:Redis節(jié)點(diǎn)故障時(shí)仍能正常運(yùn)作
4. 超時(shí)機(jī)制:避免死鎖影響系統(tǒng)可用性
三、技術(shù)實(shí)現(xiàn)細(xì)節(jié)
3.1 系統(tǒng)架構(gòu)設(shè)計(jì)
+----------------+ +-----------------+
| Client | | API Gateway |
+-------+--------+ +--------+--------+
| (攜帶Request-ID) |
+--------------------->+
|
v
+--------+--------+
| Redis Cluster |
+--------+--------+
|
v
+--------+--------+
| Business Server |
+-----------------+3.2 關(guān)鍵處理流程
ServiceRedisGatewayClient
Service
Redis
Gateway
Client
alt[首次請求][重復(fù)請求]請求攜帶X-Request-ID
EXISTS X-Request-ID
false
SETNX X-Request-ID EX 30
轉(zhuǎn)發(fā)請求
續(xù)期鎖時(shí)間
返回結(jié)果
DEL X-Request-ID
true
返回429狀態(tài)碼3.3 Redis Lua腳本實(shí)現(xiàn)原子操作
鎖獲取腳本:
local key = KEYS[1]
local value = ARGV[1]
local ttl = ARGV[2]
local result = redis.call('SET', key, value, 'NX', 'EX', ttl)
if result then
return 1
else
return 0
end鎖釋放腳本:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end3.4 Spring Boot實(shí)現(xiàn)示例
@Aspect
@Component
public class RepeatSubmitAspect {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Around("@annotation(noRepeatSubmit)")
public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String requestId = request.getHeader("X-Request-ID");
if (StringUtils.isEmpty(requestId)) {
throw new IllegalArgumentException("Missing request ID");
}
Boolean locked = redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) {
return connection.set(
requestId.getBytes(),
"LOCKED".getBytes(),
Expiration.seconds(30),
RedisStringCommands.SetOption.SET_IF_ABSENT
);
}
});
if (!locked) {
throw new RepeatSubmitException("Duplicate request detected");
}
try {
return pjp.proceed();
} finally {
redisTemplate.delete(requestId);
}
}
}四、異常場景處理策略
4.1 網(wǎng)絡(luò)分區(qū)處理
采用Redlock算法增強(qiáng)可靠性:
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock(requestId);
try {
if (lock.tryLock(0, 30, TimeUnit.SECONDS)) {
// 業(yè)務(wù)處理
}
} finally {
lock.unlock();
}4.2 時(shí)鐘漂移補(bǔ)償
1. 部署NTP時(shí)間同步服務(wù)
2. 在鎖過期判斷時(shí)增加時(shí)間余量
3. 采用CAS(Compare And Set)機(jī)制續(xù)期
4.3 鎖自動(dòng)續(xù)期機(jī)制
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
if (lock.isHeldByCurrentThread()) {
redisTemplate.expire(requestId, 30, TimeUnit.SECONDS);
}
}, 10, 10, TimeUnit.SECONDS);五、性能優(yōu)化方案
5.1 分級存儲(chǔ)策略
請求特征 | 存儲(chǔ)方案 | 超時(shí)時(shí)間 |
高頻讀操作 | 本地Guava Cache | 5秒 |
低頻寫操作 | Redis集群 | 30秒 |
持久化需求 | MySQL + 唯一索引 | 永久 |
5.2 壓力測試數(shù)據(jù)對比
優(yōu)化前:
吞吐量:1200 req/s
平均延遲:85ms
P99延遲:320ms優(yōu)化后:
吞吐量:4500 req/s
平均延遲:28ms
P99延遲:110ms5.3 緩存淘汰策略優(yōu)化
采用LRU(最近最少使用)算法結(jié)合TTL:
spring:
redis:
cache:
eviction:
max-size: 100000
time-to-live: 1h六、安全增強(qiáng)措施
6.1 ID生成安全機(jī)制
public class SecureUUID {
private static final SecureRandom secureRandom = new SecureRandom();
public static String generate() {
byte[] randomBytes = new byte[16];
secureRandom.nextBytes(randomBytes);
UUID uuid = UUID.nameUUIDFromBytes(randomBytes);
return uuid.toString().replace("-", "");
}
}6.2 限流熔斷配置
resilience4j:
ratelimiter:
instances:
apilimit:
limitForPeriod: 100
limitRefreshPeriod: 1s
timeoutDuration: 50ms七、生產(chǎn)環(huán)境最佳實(shí)踐
7.1 監(jiān)控指標(biāo)配置
# HELP api_duplicate_requests Total duplicate requests
# TYPE api_duplicate_requests counter
api_duplicate_requests{service="order"} 142
# HELP lock_acquisition_time Lock wait duration
# TYPE lock_acquisition_time histogram
lock_acquisition_time_bucket{le="0.1"} 12347.2 災(zāi)難恢復(fù)方案
1. 建立Redis哨兵模式集群
2. 定期備份鎖狀態(tài)到持久化存儲(chǔ)
3. 實(shí)現(xiàn)降級開關(guān):
@FeatureToggle(name = "lock.enabled", defaultValue = true)
public boolean isLockEnabled() {
// 功能開關(guān)實(shí)現(xiàn)
}八、未來演進(jìn)方向
8.1 區(qū)塊鏈技術(shù)應(yīng)用
將請求指紋上鏈存儲(chǔ),利用區(qū)塊鏈的不可篡改性增強(qiáng)防重驗(yàn)證的可信度。
8.2 機(jī)器學(xué)習(xí)預(yù)測
通過歷史請求模式分析,動(dòng)態(tài)調(diào)整鎖策略:
- ? 根據(jù)時(shí)間段調(diào)整過期時(shí)間
- ? 識(shí)別異常流量模式
- ? 預(yù)測性鎖預(yù)熱
本文從理論到實(shí)踐詳細(xì)闡述了基于通用唯一ID的分布式鎖防重方案,通過結(jié)合Redis的特性和完善的異常處理機(jī)制,構(gòu)建了高可用的接口防重體系。在具體實(shí)施時(shí),建議根據(jù)實(shí)際業(yè)務(wù)場景調(diào)整鎖的粒度和超時(shí)時(shí)間,并結(jié)合監(jiān)控系統(tǒng)持續(xù)優(yōu)化參數(shù)配置。
































