作者:vivo官網商城開發團隊 - Xu Yi、Yan Chao
本文是vivo商城系列文章,主要介紹vivo商城庫存系統發展歷程、架構設計思路以及應對業務場景的實踐。
庫存系統是電商商品管理的核心繫統,本文主要介紹vivo商城庫存中心發展歷程、架構設計思路及應對各種業務場景的實踐。
vivo商城原庫存系統耦合在商品系統,考慮到相關業務邏輯複雜度越來越高,庫存做了服務拆分,在可售庫存管理的基礎上新增了實物庫存管理、秒殺庫存、物流時效 、發貨限制、分倉管理等功能,滿足了商城庫存相關業務需求。
本文將介紹vivo商城庫存系統架構設計經驗以及一些問題的解決方案。
根據vivo大電商的銷售渠道與業務場景可以將庫存業務架構分為3個層級:倉庫層、排程層以及銷售層。
倉庫層對應實體倉庫,包括自營倉庫、順豐倉等第三方倉庫以及WMS系統、ERP系統等;排程層負責庫存排程與訂單發貨管理;銷售層包含多個服務終端,vivo官方商城、vivo門店、第三方電商分銷渠道等。其分層結構如圖所示:
本文探討的vivo官方商城庫存架構設計,從整個vivo大電商庫存架構來看,vivo官方商城庫存系統涉及銷售層內部架構以及銷售層與排程層的互動。
早期商城的庫存冗餘在各業務系統中,如可售庫存在商品系統、活動庫存在行銷系統等,庫存流轉也只有扣減與釋放,無法針對庫存進行整合與業務創新,存在諸多限制:
不能進行精細化管理,庫存未分層,無法針對實物庫存、分倉策略、活動庫存進行精細化管理。
沒有分倉策略,無法提前獲取商品收發地址,物流時效無法估算。
無法針對地區、商品等進行發貨管控。
實時性差,無法及時同步實物庫存以及分倉策略。
效能弱,與其他系統耦合大,不能靈活擴充套件。
基於上述限制與產品期望,21年庫存系統完成初版架構設計,此後系統不斷迭代完善,形成當前的系統架構:
庫存系統提供兩個核心能力:交易能力和庫存管理。上層業務方可以呼叫提供的API完成庫存查詢、庫存扣減等操作;管理臺可以按成分倉策略、庫存同步等操作。
庫存系統一共包含4類庫存:可售庫存、實物庫存、預佔庫存、活動庫存。
可售庫存:運營設定的普通商品庫存,商品維度到SKU。
實物庫存:由倉儲系統同步到庫存系統的實物庫存,細化到具體倉庫。
預佔庫存:使用者下單完成庫存預佔,倉儲系統發貨後釋放預佔庫存,預佔庫存可以監控已下單未發貨庫存量。
活動庫存:用於秒殺、搶購等各類行銷活動的商品庫存。
基於不同型別庫存,可以構建一個簡單的庫存分層體系:
庫存中心還維護了倉庫資訊、分倉策略、倉庫實物庫存資訊等等:
倉庫資訊:倉庫基礎資訊,包括倉庫地址、型別、編碼等。
分倉策略:倉庫功能資訊,倉庫可發貨區域、無實物庫存後的備選倉庫;訂單根據收貨地址對應優先發貨的倉庫,爭取儘快發貨儘早到貨。
倉庫庫存:倉庫實物庫存,由倉庫排程系統同步到商城庫存系統。
商品庫存流轉涉及兩個主要操作:正向庫存扣減、逆向庫存回退,整套庫存變更流程如下:
對於庫存扣減,目前常見有兩種庫存扣減方案:
(1)下單時扣庫存。
優點是:實時扣庫存,避免付款時因庫存不足而阻斷影響使用者體驗。
缺點是:庫存有限的情況下,惡意下單佔庫存影響其他正常使用者下單。比如說有100臺手機,如果沒有限制下單數量,這100個庫存可能被一個使用者惡意佔用,導致其他使用者無法購買。
(2)支付時扣庫存。
優點是:不受惡意下單影響。
缺點是:當支付訂單數大於實際庫存,會阻斷部分使用者支付,影響購物體驗。比如說只有100臺手機,但可能下了1000個訂單,但有900個訂單在支付時無法購買。
從使用者體驗考慮,我們採用的是下單時扣庫存 + 回退這種方案。
下單時扣減庫存,但只保留一段時間(比如15分鐘),保留時間段內未支付則釋放庫存,避免長時間佔用庫存。
庫存回退基於庫存變更紀錄檔逐個回退。
庫存回退基本流程:訂單出庫前使用者申請退款,回退可售庫存、回退預佔庫存、軟刪除扣減紀錄檔、增加回退紀錄檔;一旦商品出庫,使用者申請退貨走處理機流程,可售庫存和實物庫存均不回退。
庫存系統還提供了一系列客製化輔助功能:分倉策略、發貨限制、物流時效等等。
(1)分倉策略
為了給使用者更快的發貨,我們採用的是分倉策略,即由最近的倉庫(存在優先順序)給使用者發貨;同時存在備選倉庫,當所有倉庫無實物庫存時可走備選倉庫。
發貨限制分地區限制時間限制。
地區限制:根據收貨地址批次設定部分割區域無法發貨等規則,粒度到省市區維度。
時間限制:倉庫的發貨時效管理,包括每天的發貨時段、大促發貨時段、以及特殊情況下的停發時段。
根據使用者收貨地址,基於分倉策略確定發貨地址,再基於發貨時效確定發貨時間,提升使用者體驗。
訂單重複提交會導致庫存重複扣減,比如使用者誤提交、系統超時重試等,針對此類問題有如下常見解決方案:
本系統採用的是保證介面冪等性的方案。
在庫存扣減介面入參中增加訂單序列號作為唯一標識,庫存扣減時增加一條扣減紀錄檔。當介面重複請求時,會優先校驗是否已經存在扣減記錄,如果已存在則直接返回,避免重複扣減問題,具體流程如下:
常規下單渠道流量小且對超賣風險厭惡度極高,常用的防超賣方案有:
方案一:直接資料庫扣減。通過sql判斷剩餘庫存是否大於等於待扣庫存,滿足則扣減庫存。該方案利用樂觀鎖原理即update的排他性確保事務性,避免超賣。
虛擬碼sql:
sql:update store set store = store - #{deductStore } where (store-#{deductStore }) >= 0
該方案的優點是:
實庫實扣,不會出現超賣;
資料庫樂觀鎖保證並行扣減一致性;
資料庫事務保證批次扣減正常回滾。
該方案的缺點是:
行級鎖的原因存在效能瓶頸,高並行會出現請求堵塞超時問題;
直連資料庫,每次扣庫存都是寫操作,介面效能較低。
方案二:利用分散式鎖,強制序列化扣減同一商品庫存。
該方案的優點是:
減輕資料庫壓力,同時還能確保不會超賣。
該方案的缺點是:
每次只能有一個請求搶佔鎖,不能應對高並行場景。
對於常規渠道,庫存扣減是後置邏輯,流量不高,我們採用的是直接資料庫扣減,且針對弊端做了一些措施:
前置校驗嚴格,同時針對刷單場景會有嚴格限流,保證最終扣減庫存的流量可控;
庫存系統讀寫分離,減少資料庫的壓力。
針對高並行庫存扣減,比如秒殺,一般採用的是快取扣減庫存的方式(redis+lua指令碼實現單執行緒庫存更新)作為前置流程,代替資料庫直接更新。
在redis中扣減庫存雖然效能高,可以大大減輕資料庫壓力,但需要保證快取資料能完整、正確的入庫,以保證最終一致性。
針對快取資料更新至資料庫,目前主流方案有兩種:
方案一:Redis資料直接非同步更新至資料庫。
優點:簡單、沒有複雜的流程。
缺陷:redis宕機或者故障,可能會造成快取內庫存資料的丟失。
方案二:Redis扣減庫存時,同步在業務資料中insert庫存資訊。
這裡大家可能會有兩個疑問:
對於疑問1:由於資料庫insert比update效能優,insert是在表的末尾直接插入,沒有定址的過程,可以保證效能比較快。
對於疑問2:方案2不同於快取直接扣減,而是把快取扣減放在資料庫insert的事務內,通過資料庫的事務保證整體的事務。
insert的表被稱為庫存任務表,其中儲存了庫存扣減的資訊,庫存任務表結構可以設計的非常簡單,主鍵 + 庫存資訊(json字串)就可以了。
後續通過非同步任務,從庫存任務表表中查詢出庫存更新資訊,將其同步到具體的庫存表中,實現最終一致性,這種方案可以避免資料的丟失。
對於疑問3:庫存回退是根據業務庫中扣減記錄進行回退的,由於非同步更新業務庫必定存在延遲(延遲極低,數秒以內),所以極端場景會存在走退款逆向流程時業務庫的庫存扣減記錄還未更新。
針對這種情況庫存回退設定延遲重試機制,如果再極端點達到重試閾值依舊沒有扣減記錄,則返回回退成功,不做阻斷。
目前我們針對秒殺庫存扣減,採用的是方案2。但畢竟涉及資料庫的更新,為了避免風險,在前置流量校驗上做了限制,保證流量的可控:
什麼是熱點問題?熱點問題就是因熱點商品導致的redis、資料庫等效能瓶頸。在庫存系統中,熱點問題主要存在:
採用直接扣減庫存資料庫的方式,存在資料庫的行鎖問題。常規渠道的庫存扣減,我們採用的就是的就是這種方式。
採用快取扣減庫存的方式,大流量的情況下,熱點商品扣減庫存操作會打向redis單片,造成單片效能抖動,從而出現redis效能瓶頸。
對於第1種熱點問題,在vivo商城常見的場景是:新發的爆品手機,在準點售賣時會有搶購效應,容易造成庫存資料庫單行的瓶頸問題。針對這種熱點問題,我們的解決方案是「分而治之」:
對於潛在的熱點爆款手機,我們會將庫存平均分為多行(比如M行),扣減庫存時,隨機在M行中選取一行庫存資料進行扣減。該方案突破了資料庫單行鎖的瓶頸限制,解決了爆款商品的熱點問題。
對於第2種redis單片熱點問題,解決方案也是分而治之。將資料庫中的庫存資料同步到redis時,把key值打散,分散在多個redis單片中。注:我們目前線上的流量峰值還達不到會造成redis單片瓶頸的問題,為避免過度設計,只做了前置限流,沒有進行key值的打散。
庫存系統存在一些庫存同步場景:
對接倉儲系統,完成實物庫存同步。
相容歷史架構,商品系統庫存的可售庫存同步等。
(1)實物庫存同步:
實物庫存同步,對接的是倉儲系統,通過介面來獲取商品的實際庫存。實物庫存同步分成兩種:定時全量同步、指定單品更新。
定時全量同步:每天定時全量拉取庫存排程平臺的實物庫存進行全量同步。
制定單品:運營也可以手動觸發單個sku的商品即時同步實物庫存。
(2)商品系統庫存同步:
由於庫存系統多個場景涉及庫存變更,運營手動編輯、使用者下單退款導致庫存扣減回退,還有商品系統內編輯庫存資料也會導致庫存變更(以前庫存系統未獨立,庫存資料維護在商品系統)。同時很多業務在查詢庫存時,參考的依舊是商品系統的庫存資料。
這裡有一個問題:庫存系統已經獨立出來,為什麼還會依賴商品系統的庫存資料?
這有兩點原因:
商城多個業務的後臺有商品篩選的需要,商品篩選會有庫存數量的篩選項。商品數量很多,篩選是分頁的,如果將庫存資料全部替換成庫存系統的,那麼存在跨系統分頁問題,分頁篩選會存在問題;
歷史遺留問題,很多業務方依賴的是商品系統的庫存資料(包括依賴商品庫存離線表的業務方),全部切換到庫存系統,成本和影響範圍大。
因此,我們需要保證商品系統和庫存系統兩邊庫存資料的一致。
庫存變更場景多,為了降低業務複雜度、採用簡單的方式實現庫存同步,我們利用了團隊自研的CDC系統(魯班平臺),整體流程如下:
庫存資料庫發生變更後,魯班平臺通過binlog採集獲取庫存變更紀錄檔,再通過自定義規則篩選,然後傳送mq變更訊息,最後商品系統消費訊息完成庫存同步變更。
最後對庫存系統進行一個總結:
庫存系統完成服務拆分,在單一的可售庫存扣減功能基礎上拓展了很多功能,賦能業務的發展。
完成庫存架構分層,抽象多個庫存型別,更靈活地滿足當前業務需求。
針對庫存扣減防重、高並行場景下的庫存扣減、庫存熱點問題、庫存同步等技術問題,我們根據業務實際情況設計合理方案。
展望
目前vivo商城庫存系統平臺化能力不足,部分能力分散在其他系統中,未來我們希望能為vivo新零售提供一體化的庫存管理方案。