01
概述訂單系統作為電商系統的“紐帶”貫穿了整個電商系統的關鍵流程。其他模塊都是圍繞訂單系統進行構建的。訂單系統的演變也是随着電商平台的業務變化而逐漸演變進化着,接下來就和大家一起來解析電商平台的“生命紐帶”。
上帝視角訂單系統
訂單系統的作用是:管理訂單類型、訂單狀态,收集關于商品、優惠、用戶、收貨信息、支付信息等一系列的訂單實時數據,進行庫存更新、訂單下發等一系列動作。訂單系統業務的基本模型涉及用戶、商品(庫存)、訂單、付款,訂單基本流程是下訂單——>減庫存,這兩步必須同時完成,不能下了訂單不減庫存(超賣),或者減了庫存沒有生成訂單(少賣)。超賣商家庫存不足,消費者下了單買不到東西,體驗不好;少賣商家庫存積壓或者需要反複修改商品信息,反複麻煩,體驗也不好。
02
訂單基本概念設計訂單系統時包含幾個大的方向需要考慮,這些内容決定了訂單系統的穩定性和可持續性。
訂單的多樣性特點
主要由來源和操作的多樣導緻了訂單多樣性點。
訂單字段
訂單字段包含了訂單中需要記錄的信息,他的作用主要用于溝通其他系統,為下遊系統提供信息依據。
訂單信息
訂單号作為訂單識别的标識,一般按照某種特定規則生成,根據訂單的增加進行自增,同時在設計訂單号的時候考慮訂單無序設置(防止競争者或者第三方來估算訂單量)。訂單号後續用作訂單唯一标示用于對接WMS(倉存管理系統)和TMS(運輸管理系統)時的訂單識别。
訂單狀态
訂單狀态在下面章節會詳細描述
用戶信息
指買家的相關信息,包括名稱、地址、手機号。O2O還會多一種情況就是自提點,這樣地址則會變為自提點的地址。地址信息在後續會作用在WMS和TMS上用于區分區域和配送安排。
商品信息
商品的基本信息和庫存,金額由于比較特殊所以我把金額獨立在商品信息以外說,不過邏輯上其實都屬于商品信息範疇。商品信息主要影響庫存更新和WMS産生。
金額信息
訂單産生的商品信息,這裡面除了要記錄最終的金額,過程金額也需要記錄。比如商品分攤的優惠金額、支付金額,應付金額等。在後續的訂單結算、退換貨、财務等環節都需要使用。
時間信息
記錄訂單每個狀态節點的觸發時間。
03
訂單流程訂單流程是指整個訂單從産生到完成整個流轉過程,包括了正向和逆向流程的過程。
正向流程
這裡面主要是涉及主流電商系統中的通用訂單流程,部分細節可以根據自己平台的特殊性進行調整。
需要注意的地方
逆向流程
逆向流程指訂單發生取消、退貨等情況時引發的訂單流程過程。
觸發逆向流程的觸發主要有幾種情況:
關注點
訂單狀态
從訂單狀态設計目的和存在價值去分析和理解它背後設計機制:維度及維度顆粒度大小。
1. 正向和逆向流程維度
2.服務對象維度
訂單推送
當狀态發生變化時,需要将對應的變化情況告知給相關人員以便了解當前訂單的情況,這就是訂單推送的作用。
訂單推送的觸發依賴于狀态機的變化,涉及到的信息包括
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查詢。數據庫複制被用來把事務性查詢導緻的變更同步到集群中的從數據庫。 當然,主服務器也可以提供查詢服務。使用讀寫分離最大的作用無非是環境服務器壓力。
好處
- 增加冗餘
- 增加了機器的處理能力
- 對于讀操作為主的應用,使用讀寫分離是最好的場景,因為可以确保寫的服務器壓力更小,而讀又可以接受點時間上的延遲。
讀寫分離提高性能之原因
- 物理服務器增加,負荷增加
- 主從隻負責各自的寫和讀,極大程度的緩解X鎖和S鎖争用
- 從庫可配置myisam引擎,提升查詢性能以及節約系統開銷
- 從庫同步主庫的數據和主庫直接寫還是有區别的,通過主庫發送來的binlog恢複數據,但是,最重要區别在于主庫向從庫發送binlog是異步的,從庫恢複數據也是異步的
- 讀寫分離适用與讀遠大于寫的場景,如果隻有一台服務器,當select很多時,update和delete會被這些select訪問中的數據堵塞,等待select結束,并發性能不高。 對于寫和讀比例相近的應用,應該部署雙主相互複制
- 可以在從庫啟動是增加一些參數來提高其讀的性能,例如--skip-innodb、--skip-bdb、--low-priority-updates以及--delay-key-write=ALL。當然這些設置也是需要根據具體業務需求來定得,不一定能用上
- 分攤讀取。假如我們有1主3從,不考慮上述1中提到的從庫單方面設置,假設現在1分鐘内有10條寫入,150條讀取。那麼,1主3從相當于共計40條寫入,而讀取總數沒變,因此平均下來每台服務器承擔了10條寫入和50條讀取(主庫不承擔讀取操作)。因此,雖然寫入沒變,但是讀取大大分攤了,提高了系統性能。另外,當讀取被分攤後,又間接提高了寫入的性能。所以,總體性能提高了,說白了就是拿機器和帶寬換性能。
- MySQL複制另外一大功能是增加冗餘,提高可用性,當一台數據庫服務器宕機後能通過調整另外一台從庫來以最快的速度恢複服務,因此不能光看性能,也就是說1主1從也是可以的。
實現方案
數據庫分庫分表
不管是采用何種分庫分表框架或者平台,其核心的思路都是将原本保存在單表中太大的數據進行拆分,将這些數據分散保存到多個數據庫的多個表中,避免因為單表數據量太大給數據的訪問帶來讀寫性能的問題。所以在分庫分表場景下,最重要的一個原則就是被拆分的數據盡可能的平均拆分到後端的數據庫中,如果拆分的不均勻,還會産生數據訪問熱點,同樣存在熱點數據因為增長過快而又面臨數據單表數據量過大的問題。
而對于數據以什麼樣的緯度進行拆分,大家看到很多場景中都是對業務數據的ID(大部分場景此ID是以自增長的方式)進行HASH取模的方式将數據進行平均拆分,這個簡單的方式确實在很多場景下都是非常合适的拆分方法,但并不是在所有的場景中這樣拆分的方式都是最優的選擇。也就是說數據如何拆分并沒有所謂的金科玉律,更多的是需要結合業務數據的結構和業務場景來決定。
下面以大家最熟悉的電商訂單數據拆分為例,訂單是任何一個電商平台都有的業務數據,每個平台用戶提交訂單都會在平台後端生成訂單相關的數據,一般記錄一條訂單數據的數據庫表結構如下:
訂單數據主要由三張數據庫表組成,主訂單表對應的就是用戶的一個訂單,每提交一次都會生成一條主訂單表的數據。在有些情況下,用戶可能在一個訂單中選擇不同賣家的商品,而每個賣家又會按照該訂單中自己提供的商品計算相關的商品優惠(如滿100元減10元)以及按照不同的收貨地址設置不同的物流配送,所以會出現子訂單的相關概念,即一個主訂單會由多個子訂單組成,而真正對應到具體每個商品訂單信息,則保存在訂單詳情表中。
如果一個電商平台的業務發展健康的話,訂單數據是比較容易出現因為單個數據庫表中的數據量過大而造成性能的瓶頸,所以需要對他進行數據庫的拆分。此時從理論上對訂單拆分是可以由兩個緯度進行的,一個緯度是通過訂單ID(一般為自增長ID)取模的方式,即以訂單ID為分庫分表鍵;一個是通過買家用戶ID的緯度進行哈希取模,即以買家用戶ID為分庫分表鍵。
兩種方案做一下對比:
1、如果是按照訂單ID取模的方式,比如按1024取模,則可以保證主訂單以及相關子訂單,訂單詳情數據平均落入到後端1024個數據庫表中,原則上很好地滿足了數據盡可能平均拆分的原則。
2、通過采用買家ID取模的方式,比如也是按照1024取模,技術上則也能保證訂單數據拆分到後端的1024個數據庫表中,但這裡就會出現一個業務場景中帶來的問題,就是如果有些賣家是交易量非常大的,那這些賣家的訂單數據量(特别是訂單詳情表的數據量)會比其他賣家要多處不少,也就是會出現數據不平均的現象,最終導緻這些賣家的訂單數據所在的數據庫會相對其他數據庫提前進入數據歸檔(為避免在線交易數據庫的數據的增大帶來數據庫性能的問題,一般将3個月内的訂單數據保存在線交易數據庫中,超過3個月的訂單會歸檔後端專門的歸檔數據庫)。
所以從對『數據盡可能平均拆分』這條原則來看,按照訂單ID取模的方式看起來更能保證訂單數據的平均拆分,但我們暫時不要這麼快下結論,也要根據不同的業務場景和最佳實踐角度多思考不同緯度帶來的優缺點。
總結
電商平台的需求一直在變化,随之訂單系統的架構也會随之變化,架構設計就是一個持續改進的過程,這篇文章還有好多細節未提及,如果你想把訂單系統做的更好,需要更加深入系統的每一個環節,比如:容災、災備、分流、流控都需要慢慢雕琢,在架構中沒有完美的架構隻有平衡的架構,不要追求單點的完美,而是要追求多點的平衡。
李偉山 技術方舟
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!