vivo 全球商城:電商交易平臺設計

作者:vivo 官網商城開發團隊 - Cheng Kun、Liu Wei

本文介紹了交易平臺的設計理念和關鍵技術方案,以及實踐過程中的思考與挑戰。

一、背景

vivo 官方商城經過了七年的迭代,從單體架構逐步演進到微服務架構,我們的開發團隊沉澱了許多寶貴的技術與經驗,對電商領域業務也有相當深刻的理解。

去年初,團隊承接了 O2O 商城的建設任務,還有即將成立的禮品中臺,以及官方商城的線上購買線下門店送貨需求,都需要搭建底層的商品、交易和庫存能力。

爲節約研發與運維成本,避免重複造輪子,我們決定採用平臺化的思想來搭建底層系統,以通用能力靈活支撐上層業務的個性化需求。

包括交易平臺、商品平臺、庫存平臺、營銷平臺在內的一整套電商平臺化系統應運而生。

本文將介紹交易平臺的架構設計理念與實踐,以及上線後持續迭代過程中的挑戰與思考。

二、整體架構

2.1 架構目標

除了高併發、高性能、高可用這三高外,還希望做到:

  1. 低成本

    注重模型與服務的可重用性,靈活支撐各業務的個性化需求,提高開發效率,降低人力成本。

  2. 高擴展

    系統架構簡單清晰,應用系統間耦合低,容易水平擴展,業務功能增改方便快捷。

2.2 系統架構

(1)電商平臺整體架構中的交易平臺

(2)交易平臺系統架構

2.3 數據模型

三、關鍵方案設計

3.1 多租戶設計

(1)背景和目標

(2)設計方案

通過上面的映射關係,可以爲每個租戶靈活分配存儲資源,數據量很小的租戶還能複用已有的庫表。

示例一:

新租戶接入前已有 4 庫 * 16 表,新租戶的訂單量少且併發低,直接複用已有的 0 號庫 0 號表,映射關係是:租戶編碼 -> 1,1,0,0

示例二:

新租戶接入前已有 4 庫 * 16 表,新租戶的訂單量多但併發低,用原有的 0 號庫中新建 8 張表來存儲,映射關係是:租戶編碼 -> 1,8,0,16

示例三:

新租戶接入前已有 4 庫 * 16 表,新租戶的訂單量多且併發高,用新的 4 庫 * 8 表來存儲,映射關係是:租戶編碼 -> 4,8,4,0

用戶訂單所屬庫表計算公式

庫序號 = Hash(userId) / 表數量 % 庫數量 + 起始庫編號
表序號 = Hash(userId) % 表數量 + 起始表編號

可能有小夥伴會問:爲什麼計算庫序號時要先除以表數量?下面的公式會有什麼問題?

庫序號 = Hash(userId) % 庫數量 + 起始庫編號
表序號 = Hash(userId) % 表數量 + 起始表編號

答案是,當庫數量和表數量存在公因數時,會存在傾斜問題,先除以表數量就能剔除公因數。

以 2 庫 4 表爲例,對 4 取模等於 1 的數,對 2 取模也一定等於 1,因此 0 號庫的 1 號表中不會有任何數據,同理,0 號庫的 3 號表、1 號庫的 0 號表、1 號庫的 2 號表中都不會有數據。

路由過程如下圖所示:

(3)侷限性和應對辦法

問題: 分庫分表後,數據庫自增主鍵不再全局唯一,不能作爲訂單號來使用。且很多內部系統間的交互接口只有訂單號,沒有用戶標識這個分片鍵。

方案: 如下圖所示,參考雪花算法來生成全局唯一訂單號,同時將庫表編號隱含在其中(兩個 5bit 分別存儲庫表編號),這樣就能在沒有用戶標識的場景下,從訂單號中獲取庫表編號。

問題: 管理後臺需要根據各種篩選條件,分頁查詢所有滿足條件的訂單。

方案: 將訂單數據冗餘存儲一份到搜索引擎 Elasticsearch 中,滿足各種場景下的快速靈活查詢需求。

3.2 狀態機設計

(1)背景

(2)目標

(3)方案

/**
 * 訂單流程配置
 **/
@Data
public class OrderFlowConfig implements Serializable {
    /**
     * 初始訂單狀態編碼
     **/
    private String initStatus;
    /**
     * 每個訂單狀態下,可執行的操作及執行操作後的目標狀態
     * Map<原狀態編碼, Map<訂單操作類型編碼, 目標狀態編碼>>
     */
    private Map<String, Map<String, String>> operations;
}

3.3 通用操作觸發器

(1)背景

業務中通常都會有這樣的延時需求,我們之前往往通過定時任務來掃描處理。

(2)目標

(3)方案

設計通用操作觸發器,具體步驟爲:

  1. 配置觸發器,粒度是狀態機的流程類型。

  2. 創建訂單 / 售後單時或訂單狀態變化時,如果有滿足條件的觸發器,發送延遲消息。

  3. 收到延遲消息後,再次判斷執行條件,執行配置的操作。

觸發器的配置包括:

  1. 註冊時間: 可選訂單創建時,或訂單狀態變化時

  2. 執行時間: 可使用 JsonPath 表達式選取訂單模型中的時間,並可疊加延遲時間

  3. 註冊條件: 使用 QLExpress 配置,滿足條件才註冊

  4. 執行條件: 使用 QLExpress 配置,滿足條件才執行操作

  5. 執行的操作和參數

3.4 分佈式事務

對交易平臺而言,分佈式事務是一個經典問題,比如:

我們是如何保證微服務架構下數據一致性的呢?首先要區分業務場景對一致性的要求。

(1)強一致性場景

比如訂單創建和取消時對庫存和優惠券系統的調用,如果不能保證強一致,可能導致庫存超賣或優惠券重複使用。

對於強一致性場景,我們採用 Seata 的 AT 模式來處理,下面的示意圖取自 seata 官方文檔。

圖片

(2)最終一致性場景

比如支付成功後通知發貨系統發貨,確認收貨後通知積分系統發放積分,只要保證能夠通知成功即可,不需要同時成功同時失敗。

對於最終一致性場景,我們採用的是本地消息表方案:在本地事務中將要執行的異步操作記錄在消息表中,如果執行失敗,可以通過定時任務來補償。

圖片

3.5 高可用與安全設計

使用 Hystrix 組件,對依賴的外部系統添加熔斷保護,防止某個系統故障的影響擴大到整個分佈式系統中。

通過性能測試找出並解決性能瓶頸,掌握系統的吞吐量數據,爲限流和熔斷的配置提供參考。

任何訂單更新操作之前,會通過數據庫行級鎖加以限制,防止出現併發更新。

所有接口均具備冪等性,上游調用我們接口如果出現超時之類的異常,可以放心重試。

只有極少數第三方接口可通過外網訪問,且都有白名單、數據加密、簽名驗證等保護,內部系統交互使用內網域名和 RPC 接口。

通過配置日誌平臺的錯誤日誌報警、調用鏈的服務分析告警,再加上公司各中間件和基礎組件的監控告警功能,讓我們能夠能夠第一時間發現系統異常。

3.6 其他考慮

考慮到團隊非敏捷型組織架構,又缺少領域專家,因此沒有采用

大促和推廣期間,特別是爆款搶購時的流量可能會觸發限流,導致部分用戶被拒之門外。因爲無法準確預估流量,難以提前擴容。

可以通過主動降級方案增加併發量,比如同步入庫切爲異步入庫、db 查詢轉爲 cache 查詢、只能查到最近半年的訂單等。

考慮到業務複雜度和數據量級還處在初期,團隊規模也難以支撐,這些設計有遠期計劃,但暫時還沒做。(架構的合適性原則,殺雞用牛刀,你願意也行)。

四、總結與展望

我們在設計系統時並沒有一味追求前沿技術和思想,面對問題時也不是直接採用業界主流的解決方案,而是根據團隊和系統的實際狀況來選取最合適的辦法。好的系統不是在一開始就被大牛設計出來的,而是隨着業務的發展和演進逐漸被迭代出來的。

目前交易平臺已上線一年多,接入了三個業務方,系統運行平穩,公司內有交易 / 商品 / 庫存等需求的新業務,以及存量業務在遇到系統瓶頸需要升級時,都可以複用這塊能力。

上游業務方數量的增加和版本的迭代,對平臺系統的需求源源不斷,平臺的功能得到逐漸完善,架構也在不斷演進,我們正在將履約模塊從交易平臺中剝離出來,進一步解耦,爲業務持續發展做好儲備。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/YDPZi1D8EDtv-C72_LRsog