你以為的"高并發(fā)":分布式鎖 VS 真實的高并發(fā):UUID鎖
一、問題背景與核心挑戰(zhàn)
1.1 重復提交現(xiàn)象的產(chǎn)生
在分布式系統(tǒng)架構下,接口重復提交問題普遍存在于以下場景:
? 用戶界面連續(xù)快速點擊觸發(fā)多次請求
? 移動端弱網(wǎng)環(huán)境下的自動重試機制
? 微服務架構中的消息隊列重復消費
? 客戶端與服務器時鐘不同步導致的補償請求
1.2 問題引發(fā)的風險
1. 支付系統(tǒng)中的重復扣款
2. 訂單系統(tǒng)生成重復交易記錄
3. 庫存超賣導致的業(yè)務異常
4. 消息通知騷擾用戶
5. 統(tǒng)計指標數(shù)據(jù)失真
1.3 傳統(tǒng)解決方案的局限性
方案類型 | 優(yōu)點 | 缺點 |
前端防抖 | 實現(xiàn)簡單 | 無法防范繞過客戶端的請求 |
Token機制 | 服務端可控 | 增加一次交互流程 |
數(shù)據(jù)庫唯一索引 | 可靠性高 | 影響寫入性能,無法處理復雜邏輯 |
本地內存鎖 | 零延遲 | 僅限單機環(huán)境 |
二、分布式鎖技術選型
2.1 基于UUID的鍵值設計
采用通用唯一標識符作為鎖的鍵名,保證全局唯一性:
// 使用UUIDv4生成算法
String requestId = UUID.randomUUID().toString().replace("-", "");
2.2 Redis分布式鎖實現(xiàn)方案
使用Redis的原子性操作保障鎖的可靠性:
SET lock_key uuid_value NX EX 30
參數(shù)說明:
? NX:僅當key不存在時設置
? EX:設置過期時間(秒)
? 30:自動釋放鎖的時間窗口
2.3 鎖機制的核心特征
1. 互斥性:同一時刻僅有一個客戶端持有鎖
2. 可重入性:相同客戶端可重復獲取鎖
3. 容錯性:Redis節(jié)點故障時仍能正常運作
4. 超時機制:避免死鎖影響系統(tǒng)可用性
三、技術實現(xiàn)細節(jié)
3.1 系統(tǒng)架構設計
+----------------+ +-----------------+
| Client | | API Gateway |
+-------+--------+ +--------+--------+
| (攜帶Request-ID) |
+--------------------->+
|
v
+--------+--------+
| Redis Cluster |
+--------+--------+
|
v
+--------+--------+
| Business Server |
+-----------------+
3.2 關鍵處理流程
ServiceRedisGatewayClient
Service
Redis
Gateway
Client
alt[首次請求][重復請求]請求攜帶X-Request-ID
EXISTS X-Request-ID
false
SETNX X-Request-ID EX 30
轉發(fā)請求
續(xù)期鎖時間
返回結果
DEL X-Request-ID
true
返回429狀態(tài)碼
3.3 Redis Lua腳本實現(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
end
3.4 Spring Boot實現(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)絡分區(qū)處理
采用Redlock算法增強可靠性:
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock(requestId);
try {
if (lock.tryLock(0, 30, TimeUnit.SECONDS)) {
// 業(yè)務處理
}
} finally {
lock.unlock();
}
4.2 時鐘漂移補償
1. 部署NTP時間同步服務
2. 在鎖過期判斷時增加時間余量
3. 采用CAS(Compare And Set)機制續(xù)期
4.3 鎖自動續(xù)期機制
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
if (lock.isHeldByCurrentThread()) {
redisTemplate.expire(requestId, 30, TimeUnit.SECONDS);
}
}, 10, 10, TimeUnit.SECONDS);
五、性能優(yōu)化方案
5.1 分級存儲策略
請求特征 | 存儲方案 | 超時時間 |
高頻讀操作 | 本地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延遲:110ms
5.3 緩存淘汰策略優(yōu)化
采用LRU(最近最少使用)算法結合TTL:
spring:
redis:
cache:
eviction:
max-size: 100000
time-to-live: 1h
六、安全增強措施
6.1 ID生成安全機制
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)境最佳實踐
7.1 監(jiān)控指標配置
# 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"} 1234
7.2 災難恢復方案
1. 建立Redis哨兵模式集群
2. 定期備份鎖狀態(tài)到持久化存儲
3. 實現(xiàn)降級開關:
@FeatureToggle(name = "lock.enabled", defaultValue = true)
public boolean isLockEnabled() {
// 功能開關實現(xiàn)
}
八、未來演進方向
8.1 區(qū)塊鏈技術應用
將請求指紋上鏈存儲,利用區(qū)塊鏈的不可篡改性增強防重驗證的可信度。
8.2 機器學習預測
通過歷史請求模式分析,動態(tài)調整鎖策略:
- ? 根據(jù)時間段調整過期時間
- ? 識別異常流量模式
- ? 預測性鎖預熱
本文從理論到實踐詳細闡述了基于通用唯一ID的分布式鎖防重方案,通過結合Redis的特性和完善的異常處理機制,構建了高可用的接口防重體系。在具體實施時,建議根據(jù)實際業(yè)務場景調整鎖的粒度和超時時間,并結合監(jiān)控系統(tǒng)持續(xù)優(yōu)化參數(shù)配置。