1. 程式人生 > >電商系統之訂單系統

電商系統之訂單系統

01

概述

訂單系統作為電商系統的“紐帶”貫穿了整個電商系統的關鍵流程。其他模組都是圍繞訂單系統進行構建的。訂單系統的演變也是隨著電商平臺的業務變化而逐漸演變進化著,接下來就和大家一起來解析電商平臺的“生命紐帶”。

上帝視角訂單系統

訂單系統的作用是:管理訂單型別、訂單狀態,收集關於商品、優惠、使用者、收貨資訊、支付資訊等一系列的訂單實時資料,進行庫存更新、訂單下發等一系列動作。訂單系統業務的基本模型涉及使用者、商品(庫存)、訂單、付款,訂單基本流程是下訂單——>減庫存,這兩步必須同時完成,不能下了訂單不減庫存(超賣),或者減了庫存沒有生成訂單(少賣)。超賣商家庫存不足,消費者下了單買不到東西,體驗不好;少賣商家庫存積壓或者需要反覆修改商品資訊,反覆麻煩,體驗也不好。

02

訂單基本概念

設計訂單系統時包含幾個大的方向需要考慮,這些內容決定了訂單系統的穩定性和可持續性。

訂單的多樣性特點

主要由來源和操作的多樣導致了訂單多樣性點。

訂單欄位

訂單欄位包含了訂單中需要記錄的資訊,他的作用主要用於溝通其他系統,為下游系統提供資訊依據。

訂單資訊

訂單號作為訂單識別的標識,一般按照某種特定規則生成,根據訂單的增加進行自增,同時在設計訂單號的時候考慮訂單無序設定(防止競爭者或者第三方來估算訂單量)。訂單號後續用作訂單唯一標示用於對接WMS(倉存管理系統)和TMS(運輸管理系統)時的訂單識別。

訂單狀態

訂單狀態在下面章節會詳細描述

使用者資訊

指買家的相關資訊,包括名稱、地址、手機號。O2O還會多一種情況就是自提點,這樣地址則會變為自提點的地址。地址資訊在後續會作用在WMS和TMS上用於區分割槽域和配送安排。

商品資訊

商品的基本資訊和庫存,金額由於比較特殊所以我把金額獨立在商品資訊以外說,不過邏輯上其實都屬於商品資訊範疇。商品資訊主要影響庫存更新和WMS產生。

金額資訊

訂單產生的商品資訊,這裡面除了要記錄最終的金額,過程金額也需要記錄。比如商品分攤的優惠金額、支付金額,應付金額等。在後續的訂單結算、退換貨、財務等環節都需要使用。

時間資訊

記錄訂單每個狀態節點的觸發時間。

03

訂單流程

訂單流程是指整個訂單從產生到完成整個流轉過程,包括了正向和逆向流程的過程。

正向流程

這裡面主要是涉及主流電商系統中的通用訂單流程,部分細節可以根據自己平臺的特殊性進行調整。

需要注意的地方

  1. 訂單生成環節存在超時未支付自動取消的過程,庫存的佔用會在訂單取消後釋放。

  2. 如果選擇COD(貨到付款)則支付環節相應轉移到訂單配送之後,而過程中所有與款項相關的邏輯變為只操作金額數字,不對結算和賬戶進行打退款操作。

  3. 金額分攤需要到商品

  4. 訂單系統稽核主要對惡意使用者或者刷單情況進行處理。系統可根據白名單、黑名單、消費頻次、促銷品購買量方面做風控規則。如果後續會進入到人工稽核,則規則上可以適當從寬。當觸發規則需要進行訂單退訂的行為。此處設計時要小心對使用者體驗的損害,往往前臺文案上說明當前節點是稽核狀態或者是等待接單。

  5. 傳統電商則是通過關聯第三方物流的物流資訊進行跟蹤。

  6. 預售等貨和移倉需要做成SOA服務,以便在交易頁面計算預計時間和預計到貨時間。移倉處理依賴倉庫的情況,也會涉及到後續拆分和合幷包裹的邏輯。

  7. 訂單產生時先要判斷報缺情況,如果出現報缺問題則要考慮整單報缺、部分報缺、換貨或者換轉退的情況(庫存,倉促調撥和退款)。報缺情況分為系統報缺和實物報缺,這是承接但相對獨立的兩個環節。

  8. 電商系統要考慮7天無理由退貨的情景,即訂單狀態完成後申請退貨。此時主要涉及的是金額上的計算以及一些財務程式(如發票等)問題的處理。

逆向流程

逆向流程指訂單發生取消、退貨等情況時引發的訂單流程過程。

觸發逆向流程的觸發主要有幾種情況:

  • 使用者自主取消訂單(整單)

  • 風控系統觸發取消訂單(整單)

  • 客服接到客訴仲裁後觸發取消訂單(整單)

  • 超時未支付取消訂單(整單)

  • 換貨報缺轉為退單(整單、部分報缺)

關注點

  1. 訂單狀態(某一節點後如訂單產生後不允許取消訂單)

  2. 當退單被商家拒絕後需要轉入客服仲裁的環節

  3. 部分退的訂單促銷一般保持享用狀態,但金額按照分攤的金額進行退款

訂單狀態

從訂單狀態設計目的和存在價值去分析和理解它背後設計機制:維度及維度顆粒度大小。

1. 正向和逆向流程維度

  • 正向訂單:已鎖定、已確認、已付款、已發貨、已結算、已完成、已取消等

  • 正向預售訂單:預付款已付未確認、已確認未付尾款(變更)

  • 正向問題單:未確認、未鎖定、未發貨、部分付款、未付款等

  • 逆向退單:待結算、未收到貨、未入庫、質檢不通過、部分收貨、已取消、客戶已收貨等

  • 逆向換單:完成、已結算、客服已收貨等

2.服務物件維度

  • 顧客/使用者:待付款、待發貨、待收貨、待評價、買家已付款、交易成功/失敗、賣家已發貨、退款成功、交易關閉、

  • ERP等其他互動系統:已鎖定、已確認、已分倉、已分配、已出庫、已收貨、已完成等

  • 等待買家付款、待付款和待發貨訂單、退款中的訂單、定金已付、買家已付款、

  • 賣家已發貨、交易成功、交易失敗、異常訂單

訂單推送

當狀態發生變化時,需要將對應的變化情況告知給相關人員以便了解當前訂單的情況,這就是訂單推送的作用。

訂單推送的觸發依賴於狀態機的變化,涉及到的資訊包括

  • 推送物件(使用者、物流人員、商家)

  • 推送方式(push、簡訊、微信)

  • 推送節點(狀態改變)

04

訂單系統設計的挑戰和實踐

訂單系統需求演變

第一步:實現購買流程

1.實現訂單的建立、發貨、確認等資訊閉環

2.支援訂單稽核(初期可支援人工稽核即可)

3.支援使用者端顯示訂單相關資訊

4.支援促銷金額的計算

第二步:提供服務

1.提供訂單分散式服務

2.支援跨平臺交易單生成(即同一個大交易單內既有商家商品又有自營商品或者是多個商家的商品)

3.支援拆單、合併邏輯(配送單、支付單等)

4.提供更豐富的訂單推送服務,完善訂單狀態

第三步:支援不同營銷手段下的訂單型別

平臺發展到足夠大的規模,提效、穩定變成一個重要的話題。可以提供不同營銷場景下的訂單,如:團購、預購等。

訂單系統架構的演變

第一代:簡單粗暴

第一代的問題

第一代系統由於,訂單狀態是在特定的伺服器進行處理,如果服務一旦出現問題就會造成訂單的丟失,導致訂單流程無法進行下去。

總結:

1、服務單點

2、資料庫單點

第二代:無狀態非同步驅動

第二代系統對於第一代有了很好的提升,應用伺服器不再保留訂單狀態,但是這樣的系統設計同時也給資料庫伺服器造成了高頻查詢帶來的壓力,導致資料庫相對比較脆弱。

總結:

狀態掃描帶來的負載

第三代:佇列模式

第三代是對於第二代的升級,訂單的狀態流轉不再依靠高頻查詢資料庫來獲得,通過佇列模式,很好減輕了資料庫的壓力,但是第三代依然有問題,就是該系統中server2成了核心,該模組的維護就會變得很複雜,這也是架構設計的關鍵,沒有完全的完美架構,只能得到一個平衡架構。

三代系統演變中的最佳實踐

實踐1: 重試和補償

  • 多個機器重試不能同步, 需要隨機跳躍(Jitter)和指數回退 (exponential back-off)

  • 正在重試的服務也可能宕機,需要儲存狀態 (State)

實踐2: 冪等性

  • 你沒收到響應不見得失敗了

  • 你響應了不見得別人以為你成功了 

  • 重試必需帶上唯一的有意義的ID 

  • 每一個服務的呼叫都必須是冪等的 

  • 非只讀的服務必須儲存狀態

實踐3: 一致性實踐

  • 訂單系統有強一致性需求

  • 無單點故障的分散式系統的一致性是非常困難的問題

  • 已有演算法:Paxos,現有開源系統(e.g. Zookeeper)

  • 有時候單點故障並不可怕,常用的,成熟的關係資料庫方案也是一個不錯的選擇

  • 雲端分散式無單點故障的系統

實踐4: 工作流 (Workflow)

  • 可擴充套件性:

無狀態的Worker,分散式部署,分散式儲存 工作流狀態

  • 可靠性:

定時器、重試、冪等性、強一致性的狀態

  • 可維護性:

工作流的描述和執行Activity描述相分離, 支援非同步觸發

  • 支援版本和升級

系統優化

資料庫讀寫分離

基本的原理是讓主資料庫處理事務性查詢,而從資料庫處理SELECT查詢。資料庫複製被用來把事務性查詢導致的變更同步到叢集中的從資料庫。 當然,主伺服器也可以提供查詢服務。使用讀寫分離最大的作用無非是環境伺服器壓力。

好處

  1. 增加冗餘

  2. 增加了機器的處理能力

  3. 對於讀操作為主的應用,使用讀寫分離是最好的場景,因為可以確保寫的伺服器壓力更小,而讀又可以接受點時間上的延遲。

讀寫分離提高效能之原因

  1. 物理伺服器增加,負荷增加

  2. 主從只負責各自的寫和讀,極大程度的緩解X鎖和S鎖爭用

  3. 從庫可配置myisam引擎,提升查詢效能以及節約系統開銷

  4. 從庫同步主庫的資料和主庫直接寫還是有區別的,通過主庫傳送來的binlog恢復資料,但是,最重要區別在於主庫向從庫傳送binlog是非同步的,從庫恢復資料也是非同步的

  5. 讀寫分離適用與讀遠大於寫的場景,如果只有一臺伺服器,當select很多時,update和delete會被這些select訪問中的資料堵塞,等待select結束,併發效能不高。 對於寫和讀比例相近的應用,應該部署雙主相互複製

  6. 可以在從庫啟動是增加一些引數來提高其讀的效能,例如--skip-innodb、--skip-bdb、--low-priority-updates以及--delay-key-write=ALL。當然這些設定也是需要根據具體業務需求來定得,不一定能用上

  7. 分攤讀取。假如我們有1主3從,不考慮上述1中提到的從庫單方面設定,假設現在1分鐘內有10條寫入,150條讀取。那麼,1主3從相當於共計40條寫入,而讀取總數沒變,因此平均下來每臺伺服器承擔了10條寫入和50條讀取(主庫不承擔讀取操作)。因此,雖然寫入沒變,但是讀取大大分攤了,提高了系統性能。另外,當讀取被分攤後,又間接提高了寫入的效能。所以,總體效能提高了,說白了就是拿機器和頻寬換效能。

  8. MySQL複製另外一大功能是增加冗餘,提高可用性,當一臺資料庫伺服器宕機後能通過調整另外一臺從庫來以最快的速度恢復服務,因此不能光看效能,也就是說1主1從也是可以的。

實現方案

資料庫分庫分表

不管是採用何種分庫分表框架或者平臺,其核心的思路都是將原本儲存在單表中太大的資料進行拆分,將這些資料分散儲存到多個數據庫的多個表中,避免因為單表資料量太大給資料的訪問帶來讀寫效能的問題。所以在分庫分表場景下,最重要的一個原則就是被拆分的資料儘可能的平均拆分到後端的資料庫中,如果拆分的不均勻,還會產生資料訪問熱點,同樣存在熱點資料因為增長過快而又面臨資料單表資料量過大的問題。

而對於資料以什麼樣的緯度進行拆分,大家看到很多場景中都是對業務資料的ID(大部分場景此ID是以自增長的方式)進行HASH取模的方式將資料進行平均拆分,這個簡單的方式確實在很多場景下都是非常合適的拆分方法,但並不是在所有的場景中這樣拆分的方式都是最優的選擇。也就是說資料如何拆分並沒有所謂的金科玉律,更多的是需要結合業務資料的結構和業務場景來決定。

下面以大家最熟悉的電商訂單資料拆分為例,訂單是任何一個電商平臺都有的業務資料,每個平臺使用者提交訂單都會在平臺後端生成訂單相關的資料,一般記錄一條訂單資料的資料庫表結構如下:

訂單資料主要由三張資料庫表組成,主訂單表對應的就是使用者的一個訂單,每提交一次都會生成一條主訂單表的資料。在有些情況下,使用者可能在一個訂單中選擇不同賣家的商品,而每個賣家又會按照該訂單中自己提供的商品計算相關的商品優惠(如滿100元減10元)以及按照不同的收貨地址設定不同的物流配送,所以會出現子訂單的相關概念,即一個主訂單會由多個子訂單組成,而真正對應到具體每個商品訂單資訊,則儲存在訂單詳情表中。

如果一個電商平臺的業務發展健康的話,訂單資料是比較容易出現因為單個數據庫表中的資料量過大而造成效能的瓶頸,所以需要對他進行資料庫的拆分。此時從理論上對訂單拆分是可以由兩個緯度進行的,一個緯度是通過訂單ID(一般為自增長ID)取模的方式,即以訂單ID為分庫分表鍵;一個是通過買家使用者ID的緯度進行雜湊取模,即以買家使用者ID為分庫分表鍵。

兩種方案做一下對比:

1、如果是按照訂單ID取模的方式,比如按1024取模,則可以保證主訂單以及相關子訂單,訂單詳情資料平均落入到後端1024個數據庫表中,原則上很好地滿足了資料儘可能平均拆分的原則。

2、通過採用買家ID取模的方式,比如也是按照1024取模,技術上則也能保證訂單資料拆分到後端的1024個數據庫表中,但這裡就會出現一個業務場景中帶來的問題,就是如果有些賣家是交易量非常大的,那這些賣家的訂單資料量(特別是訂單詳情表的資料量)會比其他賣家要多處不少,也就是會出現資料不平均的現象,最終導致這些賣家的訂單資料所在的資料庫會相對其他資料庫提前進入資料歸檔(為避免線上交易資料庫的資料的增大帶來資料庫效能的問題,一般將3個月內的訂單資料儲存線上交易資料庫中,超過3個月的訂單會歸檔後端專門的歸檔資料庫)。

所以從對『資料儘可能平均拆分』這條原則來看,按照訂單ID取模的方式看起來更能保證訂單資料的平均拆分,但我們暫時不要這麼快下結論,也要根據不同的業務場景和最佳實踐角度多思考不同緯度帶來的優缺點。

總結

電商平臺的需求一直在變化,訂單系統的架構也會隨之變化,架構設計就是一個持續改進的過程,這篇文章還有好多細節未提及,如果你想把訂單系統做的更好,需要更加深入系統的每一個環節,比如:容災、災備、分流、流控都需要慢慢雕琢,在架構中沒有完美的架構只有平衡的架構,無需追求單點的完美,而是要追求多點的平衡。