高并發秒殺系統(Redis分布式鎖優化與庫存防超賣實戰)

本文通過日活百萬級的電商秒殺案例,深度剖析分庫分表路由算法在高并發場景下的落地實踐。結合Redis分布式鎖的優化方案解決庫存超賣問題,包含完整架構設計、代碼實現及壓測數據對比。全文包含12個核心代碼片段和8類技術圖表,來自線上生產環境的實戰經驗總結。

一、秒殺系統的破局思路

業務場景:某電商平臺「iPhone 16限時秒殺」活動,峰值QPS 12萬+,庫存量10萬臺,活動持續30分鐘。

1.1 架構瓶頸分析
客戶端
Nginx集群 LVS負載
API網關層
秒殺服務集群
數據庫集群
緩存集群

核心痛點診斷

  1. 數據庫瓶頸:單MySQL實例TPS僅5000,連接池最大1500
  2. 超賣問題:壓測中100并發時超賣率15.2%
  3. 熱點競爭:95%請求集中在10%的熱門商品
  4. 擴容失效:單純增加服務節點無法提升數據庫處理能力
// 初始架構下單點更新庫存(問題代碼)
public boolean deductStock(Long itemId) {Item item = itemMapper.selectById(itemId);if (item.getStock() > 0) {item.setStock(item.getStock() - 1);itemMapper.update(item); // 并發場景下產生超賣return true;}return false;
}
1.2 破局方案設計
水平擴展
分庫分表
并發控制
分布式鎖優化
性能提升
緩存預減庫存
容錯機制
降級+熔斷

技術選型矩陣

組件選型優勢
分庫分表ShardingSphere生態完善,兼容MySQL協議
分布式鎖Redis+Lua高性能,原子操作
緩存層Redis集群+持久化支持高并發讀寫
監控體系Prometheus+Grafana實時流量觀測

二、分庫分表路由算法核心設計

2.1 分片策略深度對比

分片鍵選擇黃金法則

  1. 離散度高(如用戶ID優于手機號)
  2. 業務查詢頻次匹配
  3. 避免跨分片事務
  4. 預留擴容空間
2.2 哈希分片算法實現
/*** 用戶ID分片路由算法(含虛擬節點)* @param userId 用戶ID* @param dbCount 物理分庫數* @param tableCount 每庫分表數* @param virtualFactor 虛擬節點因子*/
public class UserShardingRouter {// 物理節點到虛擬節點映射private static final SortedMap<Integer, String> virtualNodes = new TreeMap<>();private static final int VIRTUAL_FACTOR = 160; // 每個物理節點虛擬節點數static {// 初始化虛擬節點環for (int i = 0; i < dbCount; i++) {for (int j = 0; j < VIRTUAL_FACTOR; j++) {String node = "db_" + i;String vnode = node + "#vnode_" + j;int hash = MurmurHash.hash32(vnode);virtualNodes.put(hash, node);}}}public static String route(String userId) {// 計算用戶哈希值int hash = MurmurHash.hash32(userId);// 獲取大于該哈希值的子集SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);if (subMap.isEmpty()) {return virtualNodes.get(virtualNodes.firstKey());}String physicalNode = subMap.get(subMap.firstKey());// 計算表路由int tableIdx = Math.abs(userId.substring(0, 8).hashCode()) % tableCount;return physicalNode + ".tb_" + String.format("%03d", tableIdx);}
}
2.3 分片元數據管理架構
發布配置
配置變更通知
拉取最新配置
ConfigServer
+version: String
+shardRules: Map
+refreshRules() : void
+getRouteInfo() : RouteConfig
Zookeeper
+persist(config) : void
+watchChanges() : void
Gateway
-routeCache: ConcurrentMap
+routeRequest(userId) : String

配置熱更新流程

  1. 運維修改分片規則
  2. ConfigServer生成新版本配置
  3. Zookeeper通知所有網關節點
  4. 網關異步加載新配置(不影響在線流量)

三、Redis分布式鎖深度優化

3.1 基礎鎖的致命缺陷
// 典型錯誤實現 - 鎖續期失敗風險
public boolean tryLock(String key, String clientId, int expireSec) {if (redis.set(key, clientId, "NX", "EX", expireSec)) {// 啟動續期線程new Thread(() -> {while (locked) {Thread.sleep(expireSec * 1000 / 3);redis.expire(key, expireSec);  // 非原子操作!}}).start();return true;}return false;
}

基礎鎖的三大陷阱

  1. 非原子操作(setnx+expire分離)
  2. 鎖誤刪(未驗證客戶端標識)
  3. 續期失敗(線程異常終止)
3.2 生產級分布式鎖實現
public class RedisDistributedLock {private final JedisPool jedisPool;private final String lockKey;private final String lockValue;private final int expireTime;private volatile boolean locked = false;private ScheduledExecutorService renewExecutor;public RedisDistributedLock(JedisPool jedisPool, String lockKey, int expireTime) {this.jedisPool = jedisPool;this.lockKey = lockKey;this.lockValue = UUID.randomUUID().toString() + Thread.currentThread().getId();this.expireTime = expireTime;}public boolean tryLock(long waitMillis) {long start = System.currentTimeMillis();try (Jedis jedis = jedisPool.getResource()) {// Lua腳本保證原子性String script = "if redis.call('exists', KEYS[1]) == 0 then " +"   redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2]) " +"   return 1 " +"end " +"return 0";while (true) {Object result = jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(lockValue, String.valueOf(expireTime)));if ("1".equals(result.toString())) {locked = true;startRenewal(); // 啟動續期return true;}if (System.currentTimeMillis() - start >= waitMillis) {return false;}Thread.sleep(50); // 避免CPU空轉}}}private void startRenewal() {renewExecutor = Executors.newSingleThreadScheduledExecutor();renewExecutor.scheduleAtFixedRate(() -> {try (Jedis jedis = jedisPool.getResource()) {// 續期前驗證鎖持有者String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"   return redis.call('pexpire', KEYS[1], ARGV[2]) " +"else " +"   return 0 " +"end";jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(lockValue, String.valueOf(expireTime)));}}, expireTime / 3, expireTime / 3, TimeUnit.MILLISECONDS);}public void unlock() {if (!locked) return;try (Jedis jedis = jedisPool.getResource()) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"   return redis.call('del', KEYS[1]) " +"else " +"   return 0 " +"end";jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(lockValue));}if (renewExecutor != null) {renewExecutor.shutdownNow();}locked = false;}
}
3.3 鎖性能優化對比
水平擴容能力測試
2分片 18K
1分片 9K
4分片 36K
8分片 72K
16分片 144K

四、庫存防超賣全鏈路設計

4.1 三級庫存防護體系
請求
通過
成功
獲取
客戶端
網關層
庫存預檢
秒殺服務
Redis預減
獲取分布式鎖
DB扣減
創建訂單

防護要點

  • 網關層:令牌桶限流 + 庫存狀態緩存
  • 服務層:Redis原子操作預減
  • DB層:數據庫樂觀鎖保證最終一致
4.2 Redis庫存管理核心模塊
public class StockManager {private static final String STOCK_PREFIX = "sec_stock:";private static final String STOCK_SOLD = "sec_sold:";private final JedisCluster jedisCluster;// 初始化商品庫存public void initStock(String itemId, int total) {String key = STOCK_PREFIX + itemId;jedisCluster.set(key, String.valueOf(total));}// 預減庫存(返回剩余庫存)public long preDeduct(String itemId) {String script = "local key = KEYS[1] " +"local change = tonumber(ARGV[1]) " +"local stock = tonumber(redis.call('get', key)) " +"if stock < change then " +"   return -1 " +  // 庫存不足"end " +"local newStock = stock - change " +"redis.call('set', key, newStock) " +"return newStock";return (Long) jedisCluster.eval(script, Collections.singletonList(STOCK_PREFIX + itemId),Collections.singletonList("1"));}// 真實扣減(數據庫操作后)public boolean confirmDeduct(String itemId, int quantity) {String script = "local stockKey = KEYS[1] " +"local soldKey = KEYS[2] " +"local quantity = tonumber(ARGV[1]) " +"redis.call('incrby', soldKey, quantity) " +"return 1";jedisCluster.eval(script, Arrays.asList(STOCK_PREFIX + itemId, STOCK_SOLD + itemId),Collections.singletonList("1"));return true;}// 獲取已售數量public long getSoldCount(String itemId) {String val = jedisCluster.get(STOCK_SOLD + itemId);return val == null ? 0 : Long.parseLong(val);}
}

五、分庫分表+分布式鎖聯調

5.1 秒殺完整業務流程
Client Gateway SeckillService Redis Database POST /seckill?itemId=1001 庫存預檢(stock:1001) 返回錯誤碼 秒殺結束 預減成功 轉發請求 獲取分布式鎖(lock:1001) 獲取成功 開啟事務 查詢用戶資格 扣減真實庫存 創建訂單 提交事務 操作成功 確認扣減庫存 釋放鎖 返回成功 秒殺成功 alt [庫存不足] [庫存足夠] Client Gateway SeckillService Redis Database
5.2 分庫分表事務處理
@Service
public class SeckillServiceImpl implements SeckillService {@Autowiredprivate DynamicDataSource dataSource;@Autowiredprivate StockManager stockManager;@Autowiredprivate RedisDistributedLock lock;@Transactional(rollbackFor = Exception.class)public SeckillResponse seckill(SeckillRequest request) {// 1. Redis預減庫存long remain = stockManager.preDeduct(request.getItemId());if (remain < 0) {throw new BusinessException("庫存不足");}// 2. 獲取分布式鎖String lockKey = "lock_item:" + request.getItemId();if (!lock.tryLock(lockKey, 2000)) {throw new BusinessException("系統繁忙請重試");}try {// 3. 路由計算String dsKey = UserShardingRouter.route(request.getUserId());dataSource.setCurrent(dsKey);// 4. 數據庫操作ItemStock stock = stockMapper.selectForUpdate(request.getItemId());if (stock.getAvailable() < 1) {// 庫存補償stockManager.revertDeduct(request.getItemId());throw new BusinessException("庫存不足");}// 扣減庫存stockMapper.deduct(request.getItemId());// 創建訂單Order order = new Order();order.setItemId(request.getItemId());order.setUserId(request.getUserId());orderMapper.insert(order);// 5. 確認扣減stockManager.confirmDeduct(request.getItemId());return SeckillResponse.success(order.getOrderId());} finally {lock.unlock(lockKey);dataSource.clear();}}
}

六、壓測結果與性能分析

6.1 性能指標對比(集群模式)
方案QPS平均響應99分位超賣率資源成本
原始架構9,200420ms1.2s15.2%1x
分庫分表基礎版68,00085ms230ms0.3%1.8x
優化版(本文)182,00032ms68ms0%2.1x
6.2 資源消耗對比

在這里插入圖片描述

6.3 擴容能力線性測試

在這里插入圖片描述

七、深度優化技巧

7.1 熱點商品探測與隔離
// 基于滑動窗口的熱點檢測
public class HotItemDetector {private static final Map<String, AtomicLong> counter = new ConcurrentHashMap<>();private static final Map<String, Boolean> hotItems = new ConcurrentHashMap<>();@Scheduled(fixedRate = 1000)public void detect() {counter.forEach((itemId, count) -> {long qps = count.getAndSet(0);if (qps > 5000) { // 熱點閾值hotItems.put(itemId, true);// 動態增加該商品的分桶addItemBucket(itemId);}});}// 熱點商品特殊路由public String routeHotItem(String itemId, String userId) {if (!hotItems.containsKey(itemId)) {return defaultRoute(userId);}// 對熱點商品進行分桶隔離int bucket = userId.hashCode() % hotBucketCount;return "hot_db_" + bucket + ".tb_" + itemId;}
}
7.2 動態擴容方案
監控中
流量突增:
檢測到QPS增長300%
流量突增
擴容決策:
持續5分鐘
擴容決策
增加虛擬節點:
選擇負載最低分片
增加虛擬節點
數據遷移:
僅遷移部分數據
數據遷移
路由更新:
通知配置中心
路由更新
流量回落:
低于閾值50%
流量回落
縮容決策:
持續30分鐘
縮容決策
標記虛擬節點:
設為待回收
標記虛擬節點
流量遷移:
遷移至其他節點
流量遷移
移除節點:
物理刪除
移除節點

八、總結與避坑指南

核心經驗總結

  1. 分片鍵選擇:優先選擇離散度高的業務字段(如用戶ID),避免使用枚舉類字段
  2. 分布式鎖三原則:
    • 加鎖原子性(SET NX PX 單命令)
    • 鎖標識唯一(UUID+線程ID)
    • 續租可靠性(后臺守護線程)
  3. 庫存分層校驗:
    提交
    預減
    扣減
    前端
    網關層庫存緩存
    Redis原子操作
    數據庫最終確認
  4. 熱點處理:建立實時監控+動態分桶機制

生產環境踩坑實錄

  1. 分片鍵選擇不當

    • 場景:使用手機尾號做分片鍵
    • 問題:數據傾斜嚴重(尾號6/8占比40%)
    • 解決:改用用戶ID哈希+虛擬節點
  2. 鎖續期故障

    • 場景:續期線程池被OOM殺死
    • 現象:鎖提前釋放導致數據不一致
    • 解決:增加續期線程心跳監控
  3. 緩存與DB不一致

    • 場景:Redis預減成功但DB事務失敗
    • 解決:引入庫存回補機制+對賬任務

完整實現代碼已開源:github.com/seckill-optimization
壓測腳本路徑:/pressure-test/jmeter_cluster.jmx

架構演進方向
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/85428.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/85428.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/85428.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從loader和plugin開始了解webpack

目錄 一、webpack中loader和plugin的區別1. Loader&#xff08;每個 Loader 是一個函數或對象&#xff09;2.plugin&#xff08;每個 Plugin 是一個實例&#xff09;3.自定義loader和plugin 二、Babel的功能三、Plugin中的compiler和compilation對象1. compiler對象2. compilat…

36-Oracle Statistics Gathering(統計信息收集)

小伙伴們&#xff0c;有沒有因為統計信息不準&#xff0c;導致了業務卡頓&#xff0c;各種狀況頻出&#xff0c;這幾天在實踐和實操的過程中&#xff0c;時不時就需要進行統計信息的收集。同時統計信息收集的動作也是OCM必考內容。 數據庫中的數據是地圖&#xff0c;統計信息是…

Linux驅動程序(PWM接口)與超聲波測距

一、利用阿里云服務器實現樹莓派外網訪問&#xff08;SSH 反向代理&#xff09; 1. 樹莓派端配置 步驟 1&#xff1a;安裝 SSH 服務&#xff08;若未安裝&#xff09; sudo apt-get install openssh-server 步驟 2&#xff1a;創建反向代理連接 -p 22&#xff1a;指定阿里…

Web攻防-XSS跨站文件類型功能邏輯SVGPDFSWFHTMLXMLPMessageLocalStorage

知識點&#xff1a; 1、Web攻防-XSS跨站-文件類型-html&pdf&swf&svg&xml 2、Web攻防-XSS跨站-功能邏輯-postMessage&localStorage 一、演示案例-WEB攻防-XSS跨站-文件類型觸發XSS-SVG&PDF&SWF&HTML&XML等 1、SVG-XSS SVG(Scalable Vect…

強大模型通過自我和解進步——Unsupervised Elicitation of Language Models——論文閱讀筆記

本周關注的工作是&#xff1a;Unsupervised Elicitation of Language Models 這篇文章通篇體現了這樣一件事——香蕉皮大需要香蕉大&#xff01; 一句話總結 首先注意&#xff1a;這個工作不是面向對齊的&#xff0c;而是寫【如何準備】對齊任務的Reward Model需要的數據集的…

Qt—(Qt初識,槽,信號,事件)

一 Qt初識 暫時不寫了 我的理解是類似于c#&#xff0c;是一個組件庫&#xff0c;不局限是一個組件框架。 二 Qt Core Qt Core 是 Qt 框架的基礎模塊&#xff0c;提供非 GUI 的核心功能&#xff1a; 核心類&#xff1a;QObject&#xff08;信號槽機制&#xff09;、QEvent&…

深度學習——基于卷積神經網絡實現食物圖像分類【2】(數據增強)

文章目錄 引言一、項目概述二、環境準備三、數據預處理3.1 數據增強與標準化3.2 數據集準備 四、自定義數據集類五、構建CNN模型六、訓練與評估6.1 訓練函數6.2 評估函數6.3 訓練流程 七、關鍵技術與優化八、常見問題與解決九、完整代碼十、總結 引言 本文將詳細介紹如何使用P…

詳細說說分布式Session的幾種實現方式

1. 基于客戶端存儲&#xff08;Cookie-Based&#xff09; 原理&#xff1a;將會話數據直接存儲在客戶端 Cookie 中 實現&#xff1a; // Spring Boot 示例 Bean public CookieSerializer cookieSerializer() {DefaultCookieSerializer serializer new DefaultCookieSerializ…

用mac的ollama訪問模型,為什么會出現模型胡亂輸出,然后過一會兒再訪問,就又變成正常的

例子&#xff1a;大模型推理遇到內存不足 1. 場景還原 你在Mac上用Ollama運行如下代碼&#xff08;以Python為例&#xff0c;假設Ollama有API接口&#xff09;&#xff1a; import requestsprompt "請寫一首關于夏天的詩。" response requests.post("http:…

簡說 Linux 用戶組

Linux 用戶組 的核心概念、用途和管理方法&#xff0c;盡量簡明易懂。 &#x1f31f; 什么是 Linux 用戶組&#xff1f; 在 Linux 系統中&#xff1a; &#x1f449; 用戶組&#xff08;group&#xff09; 是一組用戶的集合&#xff0c;用來方便地管理權限。 &#x1f449; 用…

S32DS上進行S32K328的時鐘配置,LPUART時鐘配置步驟詳解

1&#xff1a;S32K328的基礎信息 S32K328官網介紹 由下圖可知&#xff0c;S32K328的最大主頻為 240MHz 2&#xff1a;S32K328時鐘樹配置 2.1 system clock node 節點說明 根據《S32K3xx Reference Manual》資料說明 Table 143 各個 系統時鐘節點 的最大頻率如下所示&#…

wordpress小語種網站模板

wordpress朝鮮語模板 紫色風格的韓語wordpress主題&#xff0c;適合做韓國、朝鮮的外貿公司官方網站使用。 https://www.jianzhanpress.com/?p8486 wordpress日文模板 綠色的日語wordpress外貿主題&#xff0c;用來搭建日文外貿網站很實用。 https://www.jianzhanpress.co…

網絡:Wireshark解析https協議,firefox

文章目錄 問題瀏覽器訪問的解決方法python requests問題 現在大部分的網站已經切到https,很多站點即使開了80的端口,最終還是會返回301消息,讓客戶端轉向到https的一個地址。 所以在使用wireshark進行問題分析的時候,解析tls上層的功能,是必不可少的,但是這個安全交換的…

ollama部署開源大模型

1. 技術概述 Spring AI&#xff1a;Spring 官方推出的 AI 框架&#xff0c;簡化大模型集成&#xff08;如文本生成、問答系統&#xff09;&#xff0c;支持多種 LLM 提供商。Olama&#xff1a;開源的本地 LLM 推理引擎&#xff0c;支持量化模型部署&#xff0c;提供 REST API …

Kafka 可靠性保障:消息確認與事務機制(二)

Kafka 事務機制 1. 冪等性與事務的關系 在深入探討 Kafka 的事務機制之前&#xff0c;先來了解一下冪等性的概念。冪等性&#xff0c;簡單來說&#xff0c;就是對接口的多次調用所產生的結果和調用一次是一致的。在 Kafka 中&#xff0c;冪等性主要體現在生產者端&#xff0c…

使用 React.Children.map遍歷或修改 children

使用場景&#xff1a; 需要對子組件進行統一處理&#xff08;如添加 key、包裹額外元素、過濾特定類型等&#xff09;。 動態修改 children 的 props 或結構。 示例代碼&#xff1a;遍歷并修改 children import React from react;// 一個組件&#xff0c;給每個子項添加邊框…

智能體三階:LLM→Function Call→MCP

哈嘍&#xff0c;我是老劉 老劉是個客戶端開發者&#xff0c;目前主要是用Flutter進行開發&#xff0c;從Flutter 1.0開始到現在已經6年多了。 那為啥最近我對MCP和AI這么感興趣的呢&#xff1f; 一方面是因為作為一個在客戶端領域實戰多年的程序員&#xff0c;我覺得客戶端開發…

flutter的常規特征

前言 Flutter 是由 Google 開發的開源 UI 軟件開發工具包&#xff0c;用于構建跨平臺的高性能、美觀且一致的應用程序。 一、跨平臺開發能力 1.多平臺支持&#xff1a;Flutter 支持構建 iOS、Android、Web、Windows、macOS 和 Linux 應用&#xff0c;開發者可以使用一套代碼庫在…

【Git】代碼托管服務

博主&#xff1a;&#x1f44d;不許代碼碼上紅 歡迎&#xff1a;&#x1f40b;點贊、收藏、關注、評論。 格言&#xff1a; 大鵬一日同風起&#xff0c;扶搖直上九萬里。 文章目錄 Git代碼托管服務概述Git核心概念主流Git托管平臺Git基礎配置倉庫創建方式Git文件狀態管理常用…

Android 網絡請求的選擇邏輯(Connectivity Modules)

代碼分析 ConnectivityManager packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java 許多APN已經棄用,應用層統一用 requestNetwork() 來請求網絡。 [ConnectivityManager] example [ConnectivityManager] requestNetwork() [Connectivi…