集群環境下Redis 商品庫存系統設計

目錄

  • 環境
  • 實現
    • 基本結構代碼
      • 業務代碼主體
      • 庫存管理模塊
    • 后續問題
      • 高并發
      • 臨界值與樂觀鎖問題
  • 完整代碼總結
  • 后話

環境

我們現在要做商品秒殺系統。功能很簡單,就是庫存刪減。用戶先下單減庫存,之后再進行扣款

實現

基本結構代碼

那么我們先看下如何搭建好基本的代碼。

業務代碼主體

基本步驟就以下幾點

  1. 刪減庫存
  2. 填寫訂單基本信息

public class SecKillBusinessService {// 庫存 serviceprivate StockDataService stockService;// 訂單 serviceprivate OrderService orderService;public Response order( String userId , String productId ){// 獲取當前時間點LocalDateTime  time = LocalDateUtils.now();// 1. 刪減庫存this.stockService.reduceStock( userId , productId ,  1 );//2. 下單OrderEntity order = new OrderEntity();order.setId(xxxx);order.setUserId(userId);order.setTime( time );this.orderService.add( order );return Response.success();}
}

庫存管理模塊


public class StockService {// 庫存底層數據private StockDataService dataService;public void reduceStock(String userId , String productId , Integer number ){// 獲取剩余庫存數量int surplusStock = this.dataService.getStock( productId );if( surplusStock == 0 || surplusStock-number < 0 ){throw new ResponseFailedExpection( "庫存不足");}// 自減數量 , 當庫存不足時扣減失敗,當前失敗碼暫定為-1int surplusNumber = this.dataService.decrementStock( productId ,  number );if( surplusNumber < 0 ){throw new ResponseFailedExpection("庫存不足");}}}

StockDataService 我們先通過查詢Mysql來實現。


public class StockDataServiceRedisImpl implement StockmentDataService {public int getStock( String productId ){// SELECT * FROM t_a_product WHERE product_id = #{productId}}@Transactionpublic int decrmentStock( String productId , Integer number ){// 簡單的樂觀鎖// UPDATE t_a_product SET stock-=#{number} WHERE product_id = #{productId} AND stock>=#{number} 	}
}

后續問題

秒殺的主要問題復雜代碼集中在如何在高并發環境下扣減庫存,庫存不會出現庫存數據計數錯誤,且更高效。

高并發

當數據量上來的時候,我們很快就會發現問題。當流量大的時候,數據庫IO很快就會打滿。然后查詢慢,插入慢。最后Mysql掛掉,服務不可用。

主要的問題,就是數據庫難以應付高并發。那么我們如何處理?
很簡單,我們使用Redis來替代Mysql , 我們新建一個新的StockDataService來進行替換。
為了保證計數問題,我們無非要么用樂觀鎖要么用悲觀鎖要么二者都用。 高并發情況下,我們不可能用悲觀鎖來讓程序在同一時間只允許一個請求在運行。(因為會引發大規模排隊)因此我們采用樂觀鎖

public class StocklDataServiceRedisImpl implement StockmentDataService {private RedisService redisService;private static final String GET_STOCK_KEY = "GET_STOCK";private String getStockRedisKey( String productId ){return GET_STOCK_KEY + productId;}/**redis之中的庫存數在其他模塊便填充,我們可以放在后臺配置的時候,也可以通過定時任務在商品生效一個小時之前。*/public int getStock( String productId ){return redisService.get(this.getStockRedisKey(productId) , Integer.class);}public int decrmentStock( String productId , Integer number ){String redisKey  = this.getStockRedisKey(productId);int surplusNumber = this.redisSerivce.decrement(redisKey  ,number);// 如果減少的數量超過庫存上限,那么歸還庫存if( surplusNumber <0 ){this.redisService.incrment(redisKey ,number);return -1;}return surplusNumber;}

我們簡單的用redis做了一個減庫存的相關功能, 并且還簡單做了一個樂觀鎖邏輯。 來處理臨界值時庫存扣減超量問題。

臨界值與樂觀鎖問題

在討論當前情況之前, 我們得先對臨界值有一個簡單的認識。 就是一個商品的臨界值時多少?
由于本人水平有限,我先簡單的做個定義。 0.8 * 當前剩余庫存數 = 當前所需的數量
簡單的說,假設當前庫存10000份,當前庫存數已經只剩下了500,當前服務器內計算到的所需要的總數達到400甚至更多時,我們就需要,那么我們就到達了臨界值狀態。

那么現在我們回到問題,
雖然我們樂觀鎖能簡單解決大部分問題,但是當庫存來到臨界值的時候,我們就會悲傷的發現。 大量的請求會失效。這些請求即無用又會給redis造成極大的壓力。

問題的本質是什么呢?是因為查詢+查庫存的這兩步驟無法原子化,庫存數量在刪減庫存的時候并不可靠。

我們就直接說Redis的解決方案。

Redis Lua 腳本

不認識的可以簡單的這樣認為,他會把不同的腳本原子化處理。也可以說Redis會自己將一連串的Lua用分布式鎖鎖住然后執行。只是用它來實現分布式事務鎖不太容易出現性能問題。

-- 方式 2:Lua 腳本實現原子扣減
local stockKey = KEYS[1]
local number = KEYS[2]
local stock = tonumber(redis.call('GET', stockKey))if stock >= number  thenredis.call('DECR', stockKey)return stock - number 
elsereturn -1
end

我們可以直接更改 StocklDataServiceRedisImpl

public class StocklDataServiceRedisImpl implement StockmentDataService {private RedisService redisService;private static final String GET_STOCK_KEY = "GET_STOCK";private String getStockRedisKey( String productId ){return GET_STOCK_KEY + productId;}public int decrmentStock( String productId , Integer number ){// Lua 腳本String script = "xxx";// 通過 Lua 一次性扣減庫存DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>(script, Integer.class);List<String> keys = Arrays.asList(this.getStockRedisKey(productId , new StringBuilder.append(number).toString()));return this.redisService.executeLua(redisScript, keys);}

并且由于Redis Lua 能保證原子性,甚至能更改 StockService 邏輯 不需要對當前庫存進行校驗。僅處理一個Redis命令即可。
自然可能由于其他因素,是否如此憑個人好惡


public class StockService {// 庫存底層數據private StockDataService dataService;public void reduceStock(String userId , String productId , Integer number ){// 自減數量 , 當庫存不足時扣減失敗,當前失敗碼暫定為-1int surplusNumber = this.dataService.decrementStock( productId ,  number );if( surplusNumber < 0 ){throw new ResponseFailedExpection("庫存不足");}}}

完整代碼總結

完善之后,當前代碼為

SecKillBusinessService .java


public class SecKillBusinessService {// 庫存 serviceprivate StockDataService stockService;// 訂單 serviceprivate OrderService orderService;public Response order( String userId , String productId ){// 獲取當前時間點LocalDateTime  time = LocalDateUtils.now();// 1. 刪減庫存this.stockService.reduceStock( userId , productId ,  1 );//2. 下單OrderEntity order = new OrderEntity();order.setId(xxxx);order.setUserId(userId);order.setTime( time );this.orderService.add( order );return Response.success();}
}

StockService .java


public class StockService {// 庫存底層數據private StockDataService dataService;public void reduceStock(String userId , String productId , Integer number ){// 獲取剩余庫存數量int surplusStock = this.dataService.getStock( productId );if( surplusStock == 0 || surplusStock-number < 0 ){throw new ResponseFailedExpection( "庫存不足");}// 自減數量 , 當庫存不足時扣減失敗,當前失敗碼暫定為-1int surplusNumber = this.dataService.decrementStock( productId ,  number );if( surplusNumber < 0 ){throw new ResponseFailedExpection("庫存不足");}}}

StocklDataServiceRedisImpl .java

public class StocklDataServiceRedisImpl implement StockmentDataService {private RedisService redisService;private static final String GET_STOCK_KEY = "GET_STOCK";private String getStockRedisKey( String productId ){return GET_STOCK_KEY + productId;}/**redis之中的庫存數在其他模塊便填充,我們可以放在后臺配置的時候,也可以通過定時任務在商品生效一個小時之前。*/public int getStock( String productId ){return redisService.get(this.getStockRedisKey(productId) , Integer.class);}public int decrmentStock( String productId , Integer number ){// Lua 腳本String script = "xxx";// 通過 Lua 一次性扣減庫存DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>(script, Integer.class);List<String> keys = Arrays.asList(this.getStockRedisKey(productId , new StringBuilder.append(number).toString()));return this.redisService.executeLua(redisScript, keys);}

后話

我們可以想象一下,如果沒有Redis Lua 功能, 我們需要做什么?
為了減少樂觀鎖出現的大面積下單失敗,我們只能依賴于悲觀鎖。
但是悲觀鎖嚴重影響性能不可取,因此我們只能折中。設置一個危險值,當庫存大于危險值時使用樂觀鎖,低于危險值時采用悲觀鎖。
危險值應該大于接口請求數上限,且為了不讓大量蜂擁而入的無用請求排隊。我們需要登記每個請求,且當請求量大于庫存數就直接拒絕服務。

這應該就是我們常說的,少即是多,以及磨刀不誤砍柴工吧。

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

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

相關文章

Spring MVC響應數據

handler方法分析 /*** TODO: 一個controller的方法是控制層的一個處理器,我們稱為handler* TODO: handler需要使用RequestMapping/GetMapping系列,聲明路徑,在HandlerMapping中注冊,供DS查找!* TODO: handler作用總結:* 1.接收請求參數(param,json,pathVariable,共享域等…

基于圖像識別的醫學影像大數據診斷系統的設計與實現

標題:基于圖像識別的醫學影像大數據診斷系統的設計與實現 內容:1.摘要 隨著醫學影像技術的快速發展&#xff0c;醫學影像數據量呈爆炸式增長&#xff0c;傳統的人工診斷方式在處理海量數據時效率低下且容易出現誤差。本研究的目的是設計并實現一個基于圖像識別的醫學影像大數據…

Python散點圖(Scatter Plot):數據探索的“第一張圖表”

在數據可視化領域,散點圖是一種強大而靈活的工具,它能夠幫助我們直觀地理解和探索數據集中變量之間的關系。本文將深入探討散點圖的核心原理、應用場景以及如何使用Python進行高效繪制。 后續幾篇將介紹高級技巧、復雜應用場景。 Python散點圖(Scatter Plot):高階分析、散點…

【redis】在 Spring中操作 Redis

文章目錄 基礎設置依賴StringRedisTemplate庫的封裝 運行StringList刪庫 SetHashZset 基礎設置 依賴 需要選擇這個依賴 StringRedisTemplate // 后續 redis 測試的各種方法&#xff0c;都通過這個 Controller 提供的 http 接口來觸發 RestController public class MyC…

微服務》》Kubernetes (K8S) 集群 安裝

關閉交換空間 # 切換 超級管理員身份 # 查看交換空間 free -h # 關閉交換空間 swapoff -a避免開啟啟動交換空間 # 注釋swap開頭的行 vim /etc/fstab關閉防火墻 # 關閉防火墻 # 因為K8S 是集群形式存在的 至少三臺 一主二從 &#xff08;一個master 兩個node&#xff09…

HTTP和RPC的區別

RPC和 HTTP是兩種常見的通信方式&#xff0c;它們在設計目標、使用場景和技術實現上有顯著區別。以下是它們的詳細對比&#xff1a; 1. 定義與核心思想 特性RPCHTTPRemote Procedure Call遠程過程調用HyperText Transfer Protocol超文本傳輸協議定義一種協議或框架&#xff0…

MySQL 簡記

MySQL 簡記 mysql中的數據存儲的結構是B樹 其與B樹的相同點是&#xff0c;B樹一個節點也可以存放多條數據&#xff0c;并且從左到右依次增大&#xff1b;不同點是&#xff0c;B樹的葉子結點之間也能相互連接。那么實際上是采取利用空間換區時間的策略。 那么B樹的樹結構like…

十七、實戰開發 uni-app x 項目(仿京東)- 后端指南

前面我們已經用uniappx進行了前端實戰學習 一、實戰 開發uni-app x項目(仿京東)-規劃-CSDN博客 二、實戰 開發uni-app x項目(仿京東)-項目搭建-CSDN博客 三、實戰開發 uni-app x 項目(仿京東)- 技術選型-CSDN博客 四、實戰開發 uni-app x 項目(仿京東)- 頁面設計-C…

Infura 簡介

文章目錄 Infura 簡介Infura 的主要功能Infura 的替代方案&#xff08;類似服務&#xff09;AlchemyQuickNodeAnkrMoralisPocket Network 什么時候選擇 Infura&#xff1f; Infura 簡介 Infura 是一個 區塊鏈基礎設施即服務&#xff08;BaaS, Blockchain as a Service&#xf…

TouchSocket TcpService:構建高性能Tcp服務的終極利器

這里寫目錄標題 TouchSocket TCPService&#xff1a;構建高性能TCP服務的終極利器引言TCPService核心特性快速入門&#xff1a;5分鐘搭建TCP服務1. 創建基礎TCP服務2. 自定義插件處理數據 高級用法實戰1. 客戶端連接管理 性能與穩定性保障示例與源碼結語 TouchSocket TCPServic…

Android Fresco 框架緩存模塊源碼深度剖析(二)

一、引言 在 Android 應用開發中&#xff0c;圖片加載和處理是常見且重要的功能。頻繁的圖片加載不僅會消耗大量的網絡流量&#xff0c;還會影響應用的性能和響應速度。因此&#xff0c;有效的緩存機制對于提升圖片加載效率和用戶體驗至關重要。Fresco 是 Facebook 開源的一款…

springboot使用163發送自定義html格式的郵件

springboot使用163發送html格式的郵件 效果: 下面直接開始教學 注冊郵箱&#xff0c;生成授權碼 獲取163郵箱的授權碼&#xff0c;可以按照以下步驟操作&#xff1a; 登錄163郵箱 打開瀏覽器&#xff0c;訪問 163郵箱登錄頁面。 使用你的郵箱賬號和密碼登錄。進入郵箱設置 登…

【Kafka】深入了解Kafka

集群的成員關系 Kafka使用Zookeeper維護集群的成員信息。 每一個broker都有一個唯一的標識&#xff0c;這個標識可以在配置文件中指定&#xff0c;也可以自動生成。當broker在啟動時通過創建Zookeeper的臨時節點把自己的ID注冊到Zookeeper中。broker、控制器和其他一些動態系…

C#使用SnsPictureBox.dll繪制點,線段、圓、折線、多邊形、測量尺等多種圖形。

CSDN下載地址&#xff1a;https://download.csdn.net/download/sns1991sns/87726867 gitee下載地址:https://gitee.com/linsns/SnsPictrueBox 支持2種繪制方式&#xff1a;響應式和等待式。 一、使用響應式繪制圖形 1、在窗口構造函數里添加繪制圖形的完成響應函數 public…

Hugging Face預訓練GPT微調ChatGPT(微調入門!新手友好!)

Hugging Face預訓練GPT微調ChatGPT&#xff08;微調入門&#xff01;新手友好&#xff01;&#xff09; 在實戰中&#xff0c;?多數情況下都不需要從0開始訓練模型&#xff0c;?是使?“??”或者其他研究者開源的已經訓練好的?模型。 在各種?模型開源庫中&#xff0c;最…

Redis BitMap 用戶簽到

Redis Bitmap Bitmap&#xff08;位圖&#xff09;是 Redis 提供的一種用于處理二進制位&#xff08;bit&#xff09;的特殊數據結構&#xff0c;它基于 String 類型&#xff0c;每個 bit 代表一個布爾值&#xff08;0 或 1&#xff09;&#xff0c;可以用于存儲大規模的二值狀…

Spring Boot 3 新特性實戰:從理論到實踐

引言 Spring Boot 自發布以來&#xff0c;憑借其簡潔的配置和強大的功能&#xff0c;迅速成為 Java 開發者的首選框架。隨著 Spring Boot 3 的發布&#xff0c;開發者們迎來了更多令人興奮的新特性。本文將深入探討 Spring Boot 3 的新特性&#xff0c;并通過實戰示例展示如何…

Nodejs使用redis

框架&#xff1a;koa&#xff0c;通過koa-generator創建 redis: 本地搭建&#xff0c;使用默認帳號&#xff0c;安裝說明地址以及默認啟動設置&#xff1a;https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/ 中間件&#x…

調研報告:Hadoop 3.x Ozone 全景解析

Ozone 是 Hadoop 的分布式對象存儲系統,具有易擴展和冗余存儲的特點。 Ozone 不僅能存儲數十億個不同大小的對象,還支持在容器化環境(比如 Kubernetes)中運行。 Apache Spark、Hive 和 YARN 等應用無需任何修改即可使用 Ozone。Ozone 提供了 Java API、S3 接口和命令行接口…

AI學習——卷積神經網絡(CNN)入門

作為人類&#xff0c;我們天生擅長“看”東西&#xff1a;一眼就能認出貓狗、分辨紅綠燈、讀懂朋友的表情……但計算機的“眼睛”最初是一片空白。直到卷積神經網絡&#xff08;CNN&#xff09;?的出現&#xff0c;計算機才真正開始理解圖像。今天&#xff0c;我們就用最通俗的…