電商平台庫存扣減設計思路


業務場景

一般來說,電商平台涉及到減庫存的場景為:提交訂單--收銀台支付,這里會有減庫存時機問題,主流使用第三種方案。

  • 下單減庫存。即提交訂單后就用商品總庫存-訂單庫存數量。用事務控制訂單生成和庫存更新,不會存在超賣問題。但是這里有個問題,下單后並不一定付款,如果存在惡意刷單會影響正常交易,且事務內生成訂單且更新庫存,業務量大會有性能問題。
  • 付款減庫存。提交訂單后,並不扣減庫存,直到支付成功后真正扣減庫存。但是這里也有個問題,成功下單的用戶,到支付時沒有庫存可用,導致交易失敗。
  • 預扣庫存。提交訂單后,庫存保留一定時間,比如10分鍾,超過這個時間庫存釋放。付款時真正完成庫存扣減。這種方案並未完全解決超賣和刷單問題

庫存扣減

庫存扣減准確,支持高並發,滿足高可用性(這個可以從保證整條鏈路上不存在單點,做兜底方案考慮,這里不做重點討論)

這里需要關注以下點

  • 庫存剩余數量需大於扣減數量,庫存不能扣成負數
  • 扣減多條sku,需要保證原子性,一條扣減失敗,全部回滾
  • 有庫存扣減才能加庫存
  • 保證冪等性

 

實現方案

實現思路總體分為兩大類,第一種是基於Mysql,另一種基於緩存實現比如Redis

1.基於緩存實現

適合於高流量,對庫存准確性要求不是非常高的場景下使用。

利用redis的incrby特性扣減庫存。但是需要考慮緩存丟失恢復場景。舉例一個發獎場景,Redis初始庫存 = 總庫存數量 - 已發放獎勵數量,如果使用異步發獎,需要等待MQ中發獎消息消費完畢才能重新初始化Redis庫存。否則會有不一致問題。

  • 使用lua實現扣減邏輯
  • 分布式環境下需使用分布式鎖控制只有一個服務初始化庫存,且需要注意初始化時機。
  • 不具備數據庫事務特性(比如批量扣減,只能將扣減的sku打包在lua腳本內,lua腳本循環做扣減,雖然保證原子性但是中途扣減失敗無法回滾。假如最后一條sku扣減失敗,結果返回失敗,但是之前sku扣減成功的無法回滾)所以需要有對賬定時任務,定期保證最終一致性。
  • 可以采用redis集群模式分攤數據
static {
        /**
         *
         * @desc 扣減庫存Lua腳本
         * 庫存(stock)-1:表示不限庫存
         * 庫存(stock)0:表示沒有庫存
         * 庫存(stock)大於0:表示剩余庫存
         *
         * @params 庫存key
         * @return
         *   -3:庫存未初始化
         *   -2:庫存不足
         *   -1:不限庫存
         *   大於等於0:剩余庫存(扣減之后剩余的庫存)
         *   redis緩存的庫存(value)是-1表示不限庫存,直接返回1
         */
        StringBuilder sb = new StringBuilder();
        sb.append("if (redis.call('exists', KEYS[1]) == 1) then");
        sb.append("    local stock = tonumber(redis.call('get', KEYS[1]));");
        sb.append("    local num = tonumber(ARGV[1]);");
        sb.append("    if (stock == -1) then");
        sb.append("        return -1;");
        sb.append("    end;");
        sb.append("    if (stock >= num) then");
        sb.append("        return redis.call('incrby', KEYS[1], 0 - num);");
        sb.append("    end;");
        sb.append("    return -2;");
        sb.append("end;");
        sb.append("return -3;");
        STOCK_LUA = sb.toString();
    }

2.基於數據庫實現

 1)方案1 

  • 基於樂觀鎖保證並發扣減下數據正確性
  • 基於事務特性,保證批量扣減下,部分扣減失敗,全部回滾 
  • 庫存扣減和庫存流水在同一個事務內
  • 庫存流水需要記錄業務流水號,當庫存歸還場景下需要攜帶扣減業務流水號。
  • 需要冪等控制

          

    

//避免扣成負數
update
inventory set leaved_amount = leaved_amount - #{count} where sku_id='123' and leaved_amount >= #{count}

2)方案2

        

 

     使用這種方案可以很大程度緩解庫存校驗和查詢庫存時的性能問題,但會帶來庫存實時性的問題,即redis和mysql主節點一致性問題。

   

    3)總結:

  使用數據庫方案簡單高效,基於數據庫ACID特性很容易保證不出現超賣和並發下庫存准確性。

  缺點:性能瓶頸在mysql主節點的寫入,

     


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM