生活随笔
收集整理的這篇文章主要介紹了
小红书-内卖秒杀项目总结
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
文章目錄
- 一、背景介紹
- 二、鳴謝
- 三、為什么會決定參與內賣?
- 四、第一次內賣
- 1. 前言
- 2. 技術方案設計
- 3、內賣過程中遇到的問題
- 4、回顧總結
- 四、第二次內賣
一、背景介紹
公司內部福利社的同事牽頭組織,將福利社的退貨商品,低價售賣給公司內部員工,算是員工福利吧。
內賣舉辦過挺多次了,這里僅記錄我參與的兩次內賣。
二、鳴謝
特別感謝雪兔、小伊、白明、安迪,留白、云流,公生、阿力、路飛、莫一兮
感謝其他在內賣過程中給與各種各樣支持的同事們
三、為什么會決定參與內賣?
套用經典名言:如今機會就在眼前,我不知道何時才能再有機會去參與一個真實的秒殺項目。
對于程序猿來說,秒殺是一個經典的高技術難度場景,絕佳的鍍金項目。
在這里要感謝公司的ExtraMile計劃,才能給與我這個機會,在公司內,去做一些非本職工作的項目,去做一些技術、業務上的挑戰。紙上得來終覺淺,看再多的書籍、博客,如果沒有在真實業務場景下去實現過,就只能叫紙上談兵,吹牛都沒人信的。
四、第一次內賣
1. 前言
第一次內賣,后端開發人員只有我一個。
因為倉庫那邊堆積壓力很大,所以從我接到任務開始,開發時間只有一周多,還要盡量不影響工作,所以技術方案設計的時候,快速實現就很重要,一切不穩定因素都應當剔除。
2. 技術方案設計
緩存:不使用redis,采用單機內存做緩存
我當時剛跑路到小紅書,在內部基礎設施服務的使用上,接連踩了各種各樣的坑,在時間如此緊張的情況下,我對于接入公司內部redis實在是沒有信心。不僅是redis,內部基礎設施服務都是能不用就不用。
采用java的Semaphore來做限購。
內賣限制每個人只能購買四件商品,那么就用Semaphore來做令牌發放,獲取到令牌的請求才能進行購買,購買失敗就返回令牌,購買成功則不返回。
限流:用guava的RateLimiter。
因為是單機器,怕撐不住,所以得加一個限流才行。
內賣整體業務流程
內賣原則上不支持退貨,用戶收到貨物之后,根據實際到貨情況和貨物質量,掃描官方支付寶付款。
(流程圖鏈接)
秒殺流程
每人最多購買四件商品,每件商品限購一件
(流程圖鏈接)
核心秒殺代碼:
public static volatile Integer userBuyLimit
= 0;
public static final RateLimiter qpsLimter
= RateLimiter
.create(100);
private static final Vector emptyVector
= new Vector();
private static volatile Map
<Integer
, Vector
<Integer>> userGoodsMap
= new ConcurrentHashMap<>();
public static volatile Map
<Integer, AtomicInteger> goodsRemainCountMap
= new HashMap<>();
private static volatile Map
<Integer, Semaphore> userTokenMap
= new HashMap<>();
private static volatile Map
<Integer, Semaphore> goodsTokenMap
= new HashMap<>();
public void buy(User operator
, Integer goodsId
) {if (!BatchStatusEnum
.SEC_kILL
.equals(Constant
.CURRENT_BATCH_STATUS
)) {throw new RuntimeException("內賣秒殺還未開始");}Vector vector
= userGoodsMap
.getOrDefault(operator
.getId(), emptyVector
);if (vector
.size() >= userBuyLimit
) {throw new RuntimeException(String
.format("您已購買%s件商品,無法再購買", userBuyLimit
));}if (vector
.contains(goodsId
)) {throw new RuntimeException("每件商品限購一件,您已購買該商品,無法再購買");}if (goodsRemainCountMap
.get(goodsId
).get() < 1) {throw new RuntimeException("該商品已被搶購一空");}boolean pass
= qpsLimter
.tryAcquire();if (!pass
) {throw new RuntimeException("競爭太激烈了,請重試");}Semaphore userTokens
= userTokenMap
.get(operator
.getId());try {userTokens
.tryAcquire();Semaphore goodsTokens
= goodsTokenMap
.get(goodsId
);try {goodsTokens
.tryAcquire(10);} catch (Exception ex
) {goodsTokens
.release();throw ex
;}} catch (Exception ex
) {logger
.warn("秒殺請購失敗:" + ex
.getMessage(), ex
);userTokens
.release();throw new RuntimeException("競爭太激烈了,請重試");}userGoodsMap
.get(operator
.getId()).add(goodsId
);goodsRemainCountMap
.get(goodsId
).decrementAndGet();
}
3、內賣過程中遇到的問題
內賣剛開始就崩掉了,原因是前端資源加載有瓶頸。
從來沒寫過前端的我,從來沒想過,前端加載竟然竟然會是個瓶頸,我一直以為只要我后端hold住就萬事大吉了。然后能怎么辦呢?大家就隨緣進入購物頁面了。
秒殺購物體驗很差。
商品列表沒有展示剩余庫存,也沒有展示已購買訂單,所以大家的購物體驗就是:進入商品列表,然后點點點,買到沒,不知道。
因為都使用機器內存做緩存,所以服務如果重啟就會丟失數據。可是最后生成訂單數據時,意外報錯了。幸好排查之后發現是數據異常導致的,刪除異常數據之后,就能正常生成訂單了。如果是代碼bug的話,那我給大家伙跪下求原諒了。
只關注了主要的秒殺流程,做了各種并發控制,但是用戶注冊、地址填寫等沒有做并發控制,導致一個人多個賬戶、一個賬戶多個收貨地址等數據異常情況。
4、回顧總結
不可重復操作,一定要做好并發控制。
不能認為在業務流程上不存在并發問題,就不需要做并發限制處理。
完善的測試與壓測。
問題無法完全避免,但是完善的測試與壓測能幫助我們盡量去避免問題。
測試與壓測,需要盡可能的去模擬真實用戶的使用場景,這樣才能發現更多的問題,比如前端資源瓶頸。
迫不得已的情況下,選擇機器內存做緩存,這個可以理解,但是沒有做好緩存持久化,導致秒殺開始后,重啟項目就會丟失數據,一切歸零,這是整個方案的最大風險點。
沒有做完善的數據監控,導致時候無法回顧整個秒殺過程中的各種性能指標,尤其是qps,幸好還有第二次內賣,不然裝逼都沒機會了。
四、第二次內賣
1、前言
這次時間充裕,后端還有三個人,人力是足夠的。美中不足的是,直到秒殺開始前,也沒找到前端小伙伴,導致只能在以前的后端接口基礎上做修改,原本設想的所有設計前端的優化、新功能點都無法做。
2、技術方案設計
內賣整體業務流程(鏈接)
秒殺下單流程(鏈接)
MySQL訂單入庫限流,主要是為了避免MySQL被壓垮
實際下單落庫,只需要在關系表中增加一個用戶id與商品id的關聯關系即可,MySQL語句是極其簡單的,考慮到用戶量與商品都不是特別大,而且限流之后qps也不會特別高,MySQL的處理速度足以滿足下單需求,所以這里直接同步方式落庫,而不采用異步落庫方式。
核心秒殺代碼:
public void buy(User operator
, Integer goodsId
) {checkBatchProcess();String goodsCountKey
= String
.format(GOODS_REMAIN_FORMAT
, goodsId
);Integer goodsRemain
= cacheService
.getIntOrDefault0(goodsCountKey
);if (goodsRemain
< 1) {throw new RuntimeException("該商品已無庫存");}BatchConfig currentBatch
= batchConfigService
.getCurrentBatch();String userBuyCountKey
= String
.format(USER_BUY_COUNT
, operator
.getId(), currentBatch
.getId());Integer userBuyCount
= cacheService
.getIntOrDefault0(userBuyCountKey
);if (userBuyCount
>= currentBatch
.getBuyLimit()) {throw new RuntimeException("您已達到購買限額,無法再購買商品");}String boughtGoodsKey
= String
.format(USER_BOUGHT_GOODS
, operator
.getId());if (cacheService
.isMember(boughtGoodsKey
, goodsId
)) {throw new RuntimeException("您已購買過該商品,每人每件商品限購一件");}String goodsLockKey
= String
.format(GOODS_COUNT_MODIFY_LOCK
, goodsId
);boolean success
= false;if (cacheService
.set(goodsLockKey
, String
.valueOf(System
.currentTimeMillis()), "NX", "PX", DEFAULT_GODOS_EXPIRE_TIME
)) {try {goodsRemain
= cacheService
.getIntOrDefault0(goodsCountKey
);if (goodsRemain
< 1) {throw new RuntimeException("該商品已無庫存");}String userLock
= String
.format(USER_BUY_LOCK
, operator
.getId(), currentBatch
.getId());if (cacheService
.set(userLock
, String
.valueOf(System
.currentTimeMillis()), "NX", "PX", DEFAULT_USER_EXPIRE_TIME
)) {try {userBuyCount
= cacheService
.getIntOrDefault0(userBuyCountKey
);if (userBuyCount
>= currentBatch
.getBuyLimit()) {throw new RuntimeException("您已達到購買限額,無法再購買商品");}int mysqlQpsLimit
= ConfigService
.getAppConfig().getIntProperty("redersale.mysql.qps.limit", 500);try {if (cacheService
.incrBy(MYSQL_QPS_LIMIT
, 1) > mysqlQpsLimit
) {throw new RuntimeException("購買失敗,請重試");}cacheService
.setByDefaultExpire(goodsCountKey
, goodsRemain
- 1);cacheService
.setByDefaultExpire(userBuyCountKey
, userBuyCount
+ 1);cacheService
.addMemberToSet(boughtGoodsKey
, goodsId
);goodsOrderMapper
.insertOrder("秒殺下單", currentBatch
.getId(), operator
.getId(),operator
.getRedName(), goodsId
);success
= true;} finally {cacheService
.incrBy(MYSQL_QPS_LIMIT
, -1);}} finally {cacheService
.delete(userLock
);}}} finally {cacheService
.delete(goodsLockKey
);}}if (!success
) {throw new RuntimeException("搶購失敗,請重試");}
}
3、回顧總結
主要指標: - 峰值qps:3k
- 賣出商品sku數量:2313
- 生成訂單數量:11238
- 售賣總金額:609,653
相比第一次,不僅秒殺期間,整個app沒有崩潰,而且秒殺使用體驗也比第一次好了很多,算是比較成功了。數據監控
做任何活動,需要充分考慮到運維監控的需求,作為業務方,需要知道當前關鍵業務數據的趨勢情況,作為技術方,需要知道當前關鍵技術指標情況(判斷服務是否還能支撐的住)。在第二次內賣中,考慮到了數據監控的需求,這點不錯,但是部分數據監控是人工每次sql查詢的,可以改成程序自動查詢并在相關業務群里報數會更好點。異常處理預案
這次內賣提前準備了預案,比如當用戶鎖未正常釋放時,手動為用戶釋放等等,這也是比較可喜的進步了。技術評審
復雜、重要的業務,需要有技術評審環節,群策群力,獨自一人制定方案,難免會有各種遺漏。信息溝通
要有一個統一的群,用來活動各方及時溝通各種信息。所有相關信息,要有文檔記錄,并且文檔目錄要放在溝通群的公告中,方便隨時查找。
總結
以上是生活随笔為你收集整理的小红书-内卖秒杀项目总结的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。