日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

谷粒商城RabbitMQ锁库存逻辑详解--新理解(长文警告)

發(fā)布時間:2023/12/3 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 谷粒商城RabbitMQ锁库存逻辑详解--新理解(长文警告) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

不廢話,上來就說,代碼我會放挺多,寫過這個項目的自然能懂,如果真的像理解的請認真看哦

分析

/*出現(xiàn)的問題:扣減庫存成功了,但是由于網(wǎng)絡(luò)原因超時,出現(xiàn)異常,導致訂單事務(wù)回滾,庫存事務(wù)不回滾(解決方案:seata)為了保證高并發(fā),不推薦使用seata,因為是加鎖,并行化,提升不了效率,可以發(fā)消息給庫存服務(wù)*/R r = wmsFeignService.orderLockStock(lockVo);if (r.getCode() == 0) {//鎖定成功responseVo.setOrder(order.getOrder());int i = 10/0;//注意這一行!!!!!!!!!!!!!!!!!!!!!!//TODO 訂單創(chuàng)建成功,發(fā)送消息給MQrabbitTemplate.convertAndSend("order-event-exchange","order.create.order",order.getOrder());//刪除購物車里的數(shù)據(jù)redisTemplate.delete(CART_PREFIX+memberResponseVo.getId());return responseVo;} else {//鎖定失敗String msg = (String) r.get("msg");throw new NoStockException(msg);// responseVo.setCode(3);// return responseVo;}

上面的代碼是提交訂單那里的,請仔細看上面的邏輯,首先先遠程調(diào)用wmsFeignService.orderLockStock(lockVo),接下來讓我們看看這個方法

@PostMapping(value = "/lock/order")public R orderLockStock(@RequestBody WareSkuLockVo vo) {try {boolean lockStock = wareSkuService.orderLockStock(vo);return R.ok().setData(lockStock);} catch (NoStockException e) {return R.error(NO_STOCK_EXCEPTION.getCode(),NO_STOCK_EXCEPTION.getMessage());}}

這是它的controller,它通過檢測下面的service方法看有沒有異常,有異常就return R.error
沒有就return R.ok

if (org.springframework.util.StringUtils.isEmpty(wareIds)) {//沒有任何倉庫有這個商品的庫存throw new NoStockException(skuId);}//1、如果每一個商品都鎖定成功,將當前商品鎖定了幾件的工作單記錄發(fā)給MQ//2、鎖定失敗。前面保存的工作單信息都回滾了。發(fā)送出去的消息,即使要解鎖庫存,由于在數(shù)據(jù)庫查不到指定的id,所有就不用解鎖for (Long wareId : wareIds) {//鎖定成功就返回1,失敗就返回0Long count = wareSkuDao.lockSkuStock(skuId,wareId,hasStock.getNum());if (count == 1) {skuStocked = true;WareOrderTaskDetailEntity taskDetailEntity = WareOrderTaskDetailEntity.builder().skuId(skuId).skuName("").skuNum(hasStock.getNum()).taskId(wareOrderTaskEntity.getId()).wareId(wareId).lockStatus(1).build();wareOrderTaskDetailService.save(taskDetailEntity);//TODO 告訴MQ庫存鎖定成功StockLockedTo lockedTo = new StockLockedTo();lockedTo.setId(wareOrderTaskEntity.getId());StockDetailTo detailTo = new StockDetailTo();BeanUtils.copyProperties(taskDetailEntity,detailTo);lockedTo.setDetailTo(detailTo);rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",lockedTo);break;} else {//當前倉庫鎖失敗,重試下一個倉庫}}if (skuStocked == false) {//當前商品所有倉庫都沒有鎖住throw new NoStockException(skuId);}

首先請注意異常拋出的地方

沒有任何倉庫有這個商品的庫存,當前商品所有倉庫都沒有鎖住,才會拋出異常!而拋出異常意味著,提交訂單(請看第一個代碼塊)那邊的 if (r.getCode() == 0) 這個判斷絕對會判斷失敗,從而走else邏輯,此時說明庫存根本沒鎖到(因為異常就是因為沒鎖到或沒庫存才拋出),所以根本不需要額外寫一個邏輯去判斷庫存需不需要解鎖,沒鎖還解鎖啥呀。

其次請注意鎖成功的話會發(fā)生什么

鎖成功就會向消息隊列發(fā)送“這個商品已經(jīng)被鎖上了”的消息,延遲時間50min(在視頻里老師設(shè)置了2min以便觀察現(xiàn)象),請記住這個鎖倉庫成功的操作。此時 if (r.getCode() == 0) 這個判斷絕對為真,于是進入下面的邏輯

請注意,既然進入這個邏輯,說明鎖庫存沒拋異常,說明鎖成功了,那這里的 int i = 10/0 會導致這個方法出現(xiàn)異常。在沒有加入seata的時候,這整個方法只有非遠程方法可以回滾,加入seata后,在入口方法加入@GlobalTransactional,在從屬方法下加入@Transactional,可以做到全局回滾。
但是老師最后不用這個方法,他用了我之前發(fā)的一篇文章:
谷粒商城RabbitMQ設(shè)計思想詳解:消息隊列雙重保險設(shè)計
這種方法來實現(xiàn)解鎖庫存的操作,因為如果用seata會導致吞吐量下降嚴重。

下面我將描述解鎖庫存為什么不需要自己手動做

25號有個同學私信我說,在上面那張圖的else部分,無論成功還是失敗都往消息隊列發(fā)送消息,讓他判斷要不要解鎖庫存。我覺得可能是沒搞懂設(shè)計邏輯。
首先我們必須明確,解鎖庫存是在哪做的,在什么時候做的?
是在submitOrder這個方法完整執(zhí)行后,用消息隊列監(jiān)聽兩個死信隊列做的。
我怕大家忘記老師的設(shè)計模式,我再強調(diào)一次,老師實現(xiàn)的是最終一致性。
我給大家放一個圖片

你看,這么多分支情況,最終都會進入一個判斷“解不解鎖”的邏輯,大家應該聯(lián)系整個系統(tǒng),在所有邏輯走到頭的情況下再個性化地添加不同的解鎖邏輯,如果像私信我的那個同學的解鎖,放在else塊里面,我覺得那個耦合度,應該有點大,而且很不方便維護,我是這樣覺得的哦

如果上面的圖片不夠清晰,那你可以試試下載這個
思維導圖…111
我不知道清晰度是不是一樣的…
我迫不得已才搞了個思維導圖,能想到的基本寫出來了,然后你如果做過項目,你思考一下,會發(fā)現(xiàn)老師基本把百分之90的情況搞定了,也就是大部分地方報錯,庫存那邊都能做到嚴密的自解鎖,可能中途有點一致性錯誤,不過既然是追求最終一致性,所以沒什么所謂。

尾聲

本次要分享的就是這些,我自認為寫的還算詳細,如果說錯了什么,或者有什么要討論的,大可以評論或者私信我,可以一起想哦

總結(jié)

以上是生活随笔為你收集整理的谷粒商城RabbitMQ锁库存逻辑详解--新理解(长文警告)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。