基於 SpringBoot-RabbitMQ-Redis 開發的秒殺系統
作者:懶蟲蟲~
原文:https://blog.csdn.net/jike11231/article/details/126818020
-
一、簡易版秒殺 SeckillProject 系統簡介
-
開發技術
-
二、實現細節記錄
-
1、用戶密碼兩次 MD5 加密
-
2、分佈式 session 維持會話
-
3、異常統一處理
-
4、頁面緩存 + 對象緩存
-
5、頁面靜態化
-
6、內存標記 + Redis 預減庫存 + RabbitMQ 異步處理
-
7、解決超賣
-
8、接口限流
-
三、效果展示
-
1、SeckillProject 代碼結構
-
2、登錄首頁
-
3、秒殺商品列表
-
4、商品詳情
-
5、點擊秒殺後訂單詳情
-
6、項目源碼下載
一、簡易版秒殺 SeckillProject 系統簡介
本項目是參考網上資料,整理開發而成,項目代碼中加入了自己的理解和實現。基於 SpringBoot 框架開發,實現的功能主要是登錄、商品列表、商品詳情、秒殺商品,訂單詳情等功能,涉及異步下單、熱點數據緩存、解決超賣等技術實現。
在系統業務處理中,使用到分佈式 session 維持會話、Redis 預減庫存降低數據庫訪問壓力,消息隊列異步下單(削峯)、客戶端輪詢結果、接口限流防刷等技術。
開發技術
-
後端:SpringBoot 、MyBatis 、 MySQL、RabbitMQ、Redis
-
前端:Html、JQuery 、Thymeleaf
二、實現細節記錄
1、用戶密碼兩次 MD5 加密
第一次 MD5 加密:防止用戶明文密碼在網絡進行傳輸
第二次 MD5 加密:防止數據庫被盜,避免通過 MD5 反推出密碼,雙重保險
2、分佈式 session 維持會話
後端通過驗證用戶賬號密碼都正確情況下,通過 UUID 生成唯一 id 作爲 token,再將 token 作爲 key、用戶信息對象作爲 value 存儲到 Redis,同時將 token 存儲到 cookie,維持會話狀態。當用戶訪問接口時,只需從 cookie 取出對應的 token 信息,根據 token 鍵值從 Redis 獲取用戶對象。
3、異常統一處理
通過自定義攔截器的方式,對所有所有異常進行攔截,並進行相應的處理,然後把結果信息返回給客戶端處理。
採用 @ControllerAdvice+@ExceptionHandler(value=Exception.class) 方式。
4、頁面緩存 + 對象緩存
- 頁面緩存:
通過在手動渲染得到的 html 頁面緩存到 Redis,下次訪問相同頁面時直接從 Redis 中獲取進行返回,減少服務端處理的壓力。
- 對象緩存:
把相應的熱點對象進行緩存到 Redis,比如:用戶對象、商品對象、訂單對象等,利用緩存來減少對數據庫的訪問,提高系統的響應速度。這裏將參與秒殺的商品在項目啓動後預熱 (寫入) 到 Redis 中。
5、頁面靜態化
使用前後端分離技術,用 ajax 實現異步請求數據,得到數據後再綁定到當前頁面。第一次訪問後,頁面和數據都會緩存到客戶端的瀏覽器,當再次請求相同的頁面時會直接從瀏覽器緩存加載。
6、內存標記 + Redis 預減庫存 + RabbitMQ 異步處理
通過內存標記 + Redis 預減庫存 + RabbitMQ 異步處理下單,最後纔會訪問數據庫,減少對數據庫的訪問,是系統整體負載達到最高。
//系統啓動時會對其初始化,將所有秒殺商品id存入map,庫存爲0是爲true
private Map<Long,Boolean> localOverMap = new HashMap<Long,Boolean>();
//內存標記,減少redis訪問
boolean over = localOverMap.get(goodsId);
if(over) {
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//redis預減庫存
long stock = redisService.decr(GoodsKey.getSeckillGoodsStock, "" + goodsId);//10
if (stock < 0) {
localOverMap.put(goodsId,true);
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//判斷是否已經秒殺到了
SeckillOrder order = orderService.getOrderByUserIdGoodsId(user.getId(), goodsId);
if(order != null) {
return Result.error(CodeMsg.REPEATE_MIAOSHA);
}
//壓入消息隊列
//入隊
SeckillMessage sm = new SeckillMessage();
sm.setUser(user);
sm.setGoodsId(goodsId);
sender.sendSeckillMessage(sm);
1)在用戶發起秒殺訪問時,先訪問本地已經初始化好的 map,看當前秒殺商品 id 的庫存是否已售罄,若已售罄,直接返回秒殺結束異常;若庫存還有,在執行下面的操作。通過內存標記可以減少對後面步驟中的 Redis 訪問操作,降低 Redis 的壓力,不然每個請求都將訪問一次 Redis。
2)系統啓動時,即將商品和庫存數據初始化到 redis 中(通過實現 InitializingBean 接口的 afterPropertiesSet 方法),所有的搶購操作都在 Redis 中進行處理,通過 Redis 預減少庫存來減少數據庫訪問。SpringBoot 啓動後實現自動執行其它業務方法功能
3)通過使用 RabbitMQ 用異步隊列處理下單,實現系統高響應。此處響應客戶端後,一般都是搶購成功了,當然不排除例外,此時客戶端通過 ajax 請求輪詢訪問下單結果接口,直到響應狀態成功或者失敗。
7、解決超賣
(1)更新的 sql 語句,只有當庫存大於 0 才能更新庫存
update seckill_goods set stock_count = stock_count-1 where goods_id = #{goodsId} and stock_count > 0
(2)對用戶 id 和商品 id 建立一個唯一索引,通過這種約束避免同一用戶發同時兩個請求秒殺到兩件相同商品
(3)實現樂觀鎖,給商品信息表增加一個 version 字段,爲每一條數據加上版本。每次更新的時候 version+1,並且更新時候帶上版本號,當提交前版本號等於更新前版本號,說明此時沒有被其他線程影響到,正常更新,如果衝突了則不會進行提交更新。當庫存是足夠的情況下發生樂觀鎖衝突就進行一定次數的重試。
8、接口限流
通過記錄用戶在某一時間內訪問的次數進行拒絕。
自定義 AccessLimit 接口,在 controller 方法上添加。
/**
* 獲取秒殺地址
* 自定義接口限流:5秒內最多訪問5次,並需要爲登錄狀態
* @param user
* @param goodsId
* @return
*/
@AccessLimit(seconds=5, maxCount=5, needLogin=true)
@RequestMapping(value = "/path", method = RequestMethod.GET)
@ResponseBody
public Result<String> getSeckillPath(User user, @RequestParam("goodsId") long goodsId) {
if (user == null) {
return Result.error(CodeMsg.USER_NO_LOGIN);
}
String path = seckillService.createPath(user, goodsId);
return Result.success(path);
}
三、效果展示
1、SeckillProject 代碼結構
2、登錄首頁
賬號 15898989898
密碼 123456
3、秒殺商品列表
4、商品詳情
5、點擊秒殺後訂單詳情
6、項目源碼下載
- https://gitee.com/jike11231/sec-kill-product
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/5bXm7jLykW_2vG9l8lh0NA