1. 前端攔截?
首先因為是10萬QPS的高并發請求,我們要保護好系統,那就是盡可能減少用戶無效請求。
1.1 按鈕置灰
很多用戶搶票、搶購、搶紅包等時候,為了提高搶中的概率,都是瘋狂點擊按鈕。會觸發多次請求,導致重復下單。
因此,在用戶點擊過搶購按鈕后,我們可以給按鈕置灰,不讓用戶重復點擊。
const submitButton = document.getElementById('submit-order');
submitButton.disabled =?true; ?// 禁用按鈕
// 提交訂單的異步操作
submitOrder().then(response => {submitButton.disabled =?false; ?// 請求完成后恢復按鈕
}).catch(error => {submitButton.disabled =?false; ?// 請求失敗也恢復按鈕
});
如果你的系統希望設計得友好一點,可以前端提示個文案,比如:已經收到你的請求,請耐心等待搶購結果。
1.2 Token機制
-
前端加載頁面的時候,獲取一個全局唯一標記的token,如UUID。
-
在表單提交時,用該Token來標識該請求。每次請求都附帶該Token,后端驗證Token是否唯一。如果已提交過該Token的請求,則直接返回錯誤或無效響應,防止重復提交。
2.后端設計?
2.1 NGINX 限流
請求從前端到后端,首先是先到nginx ,我們可以在nginx做一下限流。
因為有些用戶不懷好意,通過腳本繞過前端,瘋狂請求。這類用戶的IP和用戶ID,我們都可以做一下限流的限制的。
一個Nginx限流配置:
http {# 核心配置:IP+用戶ID雙因子限流 (按業務需求二選一)## 選項1:基礎版(純IP限制)limit_req_zone?$binary_remote_addr?zone=order_base:10m rate=3r/m; ?# 每IP每分鐘3次## 選項2:增強版(IP+用戶ID,需前端傳遞UserID)limit_req_zone?$binary_remote_addr$http_user_id?zone=order_enhanced:20m rate=5r/m;server {listen 80;server_name tianluoboy.com;# 訂單提交接口location = /v1/order/create {# 啟用限流(示例用增強版)limit_req zone=order_enhanced burst=3 nodelay; ?# 返回429時強制JSON響應error_page 429 @toofast;location @toofast {default_type application/json;return?200?'{"code":429,"msg":"操作過于頻繁,請稍后再試"}';}# 反向代理到業務服務器proxy_pass http://order_backend;}# 其他接口不限流location / {proxy_pass http://default_backend;}}
}
2.2 網關(Spring Cloud Gateway)
網關層可以做的事情很多,比如
Token 校驗:在網關層攔截請求,驗證 Token 是否在 Redis 中存在.
// 偽代碼示例:網關過濾器校驗 Token
if?(redis.get(token) != null) {return?error("重復請求");
}?else?{redis.setex(token, 60,?"1"); // Token 有效期 60 秒//請求到后臺passToBackend();
}
當然,網關也可以做限流的:
-
令牌桶算法:通過Redis + Lua 實現集群級限流(如 redis-cell 模塊)。
-
用戶維度限流:基于用戶 ID或設備指紋限制并發請求數。
網關層可以做一下請求排隊:
-
對高并發請求放到消息隊列,削峰填谷(如 Kafka/RabbitMQ)
2.3 冪等設計
下單業務接口,我們一般要做冪等的。
一般用唯一索引做冪等設計。
唯一索引:比如使用用戶ID + 商品ID + 時間戳組合生成唯一訂單號。
一般的冪等處理就是這樣啦,如下:
2.4 分庫分表
分庫分表:按用戶 ID 分片,分散寫壓力,避免單表成為瓶頸。
當業務量暴增的話,MySQL單機磁盤容量會撐爆。并且,我們知道數據庫連接數是有限的。在高并發的場景下,大量請求訪問數據庫,MySQL
單機是扛不住的!高并發場景下,會出現too many connections
報錯。
所以高并發的系統,需要考慮拆分為多個數據庫,來抗住高并發的毒打。而假如你的單表數據量非常大,存儲和查詢的性能就會遇到瓶頸了,如果你做了很多優化之后還是無法提升效率的時候,就需要考慮做分表了。一般千萬級別數據量,就需要分表,每個表的數據量少一點,提升SQL查詢性能。
2.5 分布式鎖
既然是防止重復下單,一般都需要加Redis分布式鎖的。
// 偽代碼:Redisson 分布式鎖
RLock lock = redisson.getLock("order:lock:"?+ userId);
if?(lock.tryLock(0, 30, TimeUnit.SECONDS)) {try {createOrder();} finally {lock.unlock();}
}
使用Redis分布式鎖一般需要知道存在哪些坑,有需要的伙伴可以購買我的踩坑專欄哈:血與淚的教訓,盤點我工作七年所踩的坑(更新到90篇啦~)
2.6 樂觀鎖兜底
如果分布式鎖失效了呢?怎么辦呢?
我們可以加數據庫樂觀鎖兜底。比如
在數據訂單表中添加 version 字段,每次更新都version+1,更新時校驗版本號
通過原子操作?
UPDATE ... SET version=version+1 WHERE order_id=xx AND version=old_version
實現
2.7 日志與監控
要打印好日志,和做好監控指標,如果發現日志或者監控異常,可以快速介入排查~
2.8 核對數據
我們要做好核對數據,比如做個定時任務,核對訂單數據和交易金額是否對得上,如果發現數據異常,就人工快速介入排查和修復。