從一個電商平臺的庫存同步談效能優化和方案落地

西召發表於2019-04-16

目錄

下面的案例來自筆者的實際工作經歷,涉及到的系統是筆者負責開發和維護的,一個國外的電商平臺。

如果你對電商系統有所瞭解,將有助於你理解下面提到的業務。

如果你沒有相關的知識背景,也沒有關係,我會盡可能簡化地將業務講給你,並且只要求你理解關鍵概念即可。

背景

事情的起因是平臺的某位高階主管的一封郵件,其中提到商品全量庫存的實時性太低,需要各個部門的人協力解決。

庫存同步相關概念

先介紹一下電商平臺的一些基本概念。

從一個電商平臺的庫存同步談效能優化和方案落地

庫存就是倉庫中某個SKU(最小庫存單元)在倉庫中實際有量。

比如某型號灰色8核16G記憶體的膝上型電腦就是一個SKU,在倉庫中這個SKU有100臺,那麼它的庫存量就是100。

  • 全量和增量庫存

倉庫每天都會把自己實際的庫存量統計出來,這就是全量庫存,倉庫把庫存量傳送給各個銷售終端,這就是全量庫存同步

同時,為了保證庫存的實時性,防止超賣(賣出比實際庫存量更多的商品,倉庫無法發出貨品,有可能導致客訴)和倉庫有貨但客戶買不到的情況,倉庫會把庫存的變化量也實時分發到各個終端,這個庫存的變化量就是增量庫存

舉例來說,上面的那個SKU膝上型電腦有一臺送到攝影棚去拍照了,那麼這臺就無法銷售了,倉庫就會推送一個-1的增量庫存到銷售終端;而如果它收到了消費者的退貨,退貨入庫以後,將會推送一個+1的增量庫存。

  • 多店鋪與分盤

電商平臺一般都會有多個店鋪入駐,例如3C這個分類下面,可能有蘋果、華為、三星、小米等店鋪。

不同店鋪的庫存是獨立的。

有時候一個SKU在多家店鋪都有售,iPhone X/太空灰色/256GBXXX蘋果平臺旗艦店XXX手機大世界店XX蘋果折扣店 就是三個不同的庫存記錄。

這就是多店鋪庫存

作為分銷商,它的倉庫中放著不同平臺、不同品牌的商品。例如上面的手機,在深圳、廣州、上海三個地區倉庫都有貨,並且是分別賣給天貓和京東的,那麼它的庫存記錄就有6條,分別是:

No. 倉庫 渠道
1 深圳 天貓
2 深圳 京東
3 廣州 天貓
4 廣州 京東
5 上海 天貓
6 上海 京東

這就是分盤庫存

  • 庫存清點時間和最後更新時間

在實際操作中,為了保證資料的準確性,平臺會對庫存的時間進行校驗。

例如,倉庫在凌晨 01:00 清點出某SKU庫存量為100,則這條庫存記錄的庫存清點時間就是01:00。

倉庫在01:00清點完以後,在02:00收到了一個退貨,那麼就會推送一個+1的增量到平臺。

一般情況下,全量先發出,平臺應該先收到全量100,再收到增量+1,最後為101。

但如果由於某個中間環節出了問題,先收到增量+1,在收到全量100,那麼最終的庫存量將是100。全量庫存會直接覆蓋平臺現在的庫存量。

因此,如果有一個最後更新時間,記錄是02:00收到的增量,那麼當01:00的全量過來的時候,由於比增量時間要早,將被平臺視為作廢。

庫存流轉過程

實際的庫存資料流轉過程往往不是 「倉庫——>平臺」 這麼短的鏈路,實際鏈路總是很長的:

InventoryDataSystemFlow

不同系統的效能不同,實現方式不同,越長的鏈路時延問題就越嚴重。

方案

問題分析

想要解決問題,首先要分析問題。作為平臺技術負責人,我先統計了平臺最近一個月的庫存同步時間,大約是150分鐘,並且每隔幾天會延長几分鐘。

然後我統計了最近一段時間全量庫存的資料變化量,僅僅10天就增加了5w。

InventoryDataAmount

  • 問題定義: 目前看來,從平臺角度來講,隨著庫存資料量的增加,處理時間不斷延長,再加上整個鏈路很長,造成全量庫存資料的實時性很差。

頭腦風暴

分析完問題,我立即召開了團隊的人員討論解決方案,經過大家討論,可以優化的環節是下面幾個:

  1. 提升硬體配置

當你拼命練跑步避免遲到的時候,也許給你一輛車就解決問題了。

部門服務的資源緊張,配置極低。

  1. 修改訊息處理邏輯

目前庫存資料拆分粒度很細(分店鋪分倉庫分門店),加上網路時延,會造成處理時間延長。

  1. 優化訊息處理的邏輯

庫存資料由訊息中心統一處理,訊息中心會處理訂單、商品、價格、會員、庫存等等多種型別的資料,效率不高。

  1. 優化全量庫存同步

從平臺處理資料的程式碼流程著手優化。

確定方案

對於方案1需要金主批錢,方案2需要多個系統修改,這些不好談;方案3需要改動整體架構,工作量巨大。

對於解決燃眉之急,方案4的可行性最高,改動量和影響範圍最小。

細化方案

方案4優化全量庫存同步,具體細化為下面三個方面

  1. 業務精簡和標準化
  2. 資料處理高效能
  3. 佇列操作高效能

下面在實施的時候一一詳細說明。

實施

業務精簡和標準化

業務精簡和標準化分為下面幾個方面:

  1. 全增隔離

目前全量和增量庫存同步使用同一個佇列名,通過欄位判斷是全量還是增量。 這樣增加了程式碼的複雜度,而且原子性不好。 全量庫存單獨佇列,與增量同步隔離。

  1. 剝離日誌

修改庫存以後需要記錄詳細變更日誌,日誌的實時性要求不高,將改操作剝離為單獨的佇列進行處理。

  1. 剝離新建

目前同步庫存之前先判斷該商品是否存在,如果存在再判斷該商品在庫存表是否有記錄,如果沒有則新建記錄,有則更新庫存。

由於隨著資料量的增加,新建的記錄(每天1k到3k之間)所佔的比重越來越小,因此將新建的操作也單獨剝離為一個佇列進行處理。

優化訊息處理的邏輯

平臺為分散式系統,訊息處理需要從註冊中心呼叫遠端Dubbo服務,首先將資料處理移動到Dubbo服務中完成,避免了頻繁呼叫Dubbo服務,另外使用多執行緒處理訊息,最大限度利用多核心的優勢。

關於執行緒池的使用,可以參考這篇文章:使用ThreadPoolExecutor構造執行緒池

//構造執行緒池
private static ExecutorService executorService = new ThreadPoolExecutor(
16,
32,
10L,
TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(
                2048),new ThreadFactoryBuilder()
                     .setNameFormat("BatchSyncFullInventory-Pool-%d").build(),
                     new ThreadPoolExecutor.CallerRunsPolicy());
複製程式碼

經過上面的優化,目前處理的時間有了大幅度降低:

InventoryDataAmount

佇列操作高效能

經過上面的優化,發現每處理2k條訊息,處理時間在1s以內,但出隊時間接近15s。

因此,下面的優化重點是提高佇列的操作效能。

由於Redis頻繁的操作,會造成RTT(網路時延)不斷延長,可以使用管道來降低RTT。

下面是Spring Data Redis使用管道的方式:

//從佇列中迴圈取出訊息, 使用管道, 減少網路傳輸時間
List<Object> msgList = redisTemplate.executePipelined(new RedisCallback<Object>() {
    @Override
    public Object doInRedis(RedisConnection connection) throws DataAccessException {
        for (int i = 0; i < batchSize; i++) {
            connection.rPop(getQuenueName().getBytes());
        }
        return null;
    }
});
複製程式碼

理論上是這樣的,需要有實際的資料支撐,因此需要通過做實驗來驗證方案的效果。

首先,在測試環境對比了三種不同的出隊方式的效能,分別是單執行緒迴圈出隊、多執行緒迴圈出隊和單執行緒管道出隊。

測試發現使用管道出隊兩千次,只需要70毫秒左右。

從一個電商平臺的庫存同步談效能優化和方案落地

最終,使用了 管道+多執行緒,庫存訊息的處理時間降到了30分鐘左右:

InventoryDataAmount

關於管道的使用,可以參考這篇文章:Redis管道技術

CPU使用過高

雖然釋出到生產以後,處理時間有了大幅度降低,但是經過監控發現,Redis的CPU使用率一直居高不下。

從一個電商平臺的庫存同步談效能優化和方案落地

對於監聽佇列的場景,一個簡單的做法是當發現佇列返回的內容為空的時候,就讓執行緒休眠幾秒鐘,等佇列中累積了一定量資料以後再通過管道去取,這樣就既能享受管道帶來的高效能,又避免了CPU使用率過高的風險。

//如果訊息的內容為空, 則休眠[10]秒鐘以後再繼續取資料,防止大批量地讀取redis造成CPU消耗過高
if (CollectionUtils.isEmpty(messageList)) {
    Thread.currentThread().sleep(10 * 1000);
    continue;
}
複製程式碼

總結

  • 方案設計:頭腦風暴與可行性評估
  • 邏輯精簡化 : 剝離不必要的操作
  • 流程標準化 : 梳理統一業務的流程
  • 執行緒池實現高效能併發 : Executor Service
  • 管道實現高效能佇列 : Redis Pipelining

作為一個工程師,要知道自己能力的邊界在哪裡,利用有限的資源讓方案落地。

這裡優化的經歷,是想讓大家對電商相關的業務有所瞭解,另外對處理問題的解決思路有所借鑑。

相關文章