基於中臺思想的物流系統設計(二):構建物流訂單能力

1、引言

物流訂單能力做爲基礎能力,須要設計一套穩定的訂單模型,以及一套可以在高併發環境下持續可用的接口。這些接口做爲原子接口,供上層業務複用。上層業務不管多麼複雜,經過這些原子接口,最終都會收斂到穩定的訂單模型中來,這也是區分基礎能力和產品服務的一個重要的邊界。html


本文經過如下5點來介紹如何構建一套物流訂單能力:算法

一、模型設計spring

二、狀態機設計sql

三、高併發建立接口數據庫

四、高併發更新接口express

五、高併發查詢接口架構


2、物流訂單數據模型設計

首先來看ER模型併發


一共四張表,主模型是logistics_order、logistics_order_package和logistics_order_item表,logistics_order_unique是去重表。異步

一、logistics_order

描述:物流訂單主單表,整張表大概分爲如下幾部分信息高併發


表結構設計

字段名稱
字段類型
是否必填
描述
id
bigint
必填
主鍵
lg_order_code
varchar(128)
必填
物流單號
trade_order_code
varchar(128)
非必填
交易單號
receiver_id
bigint
非必填
收貨人ID
receiver_name
varchar(64)
非必填
收貨人姓名
receiver_telephone
varchar(32)
非必填
收貨人電話
receiver_province
varchar(32)
非必填
收貨人省份
receiver_city
varchar(64)
非必填
收貨人城市
receiver_area
varchar(64)
非必填
收貨人地區
receiver_street
varchar(64)
非必填
收貨人街道
receiver_address
varchar(1024)
非必填
收貨人詳細地址
receiver_address_code
varchar(32)
非必填
四級地址編碼
sender_id
bigint
非必填
發貨人ID
sender_name
varchar(64)
非必填
發貨人姓名
sender_telephone
varchar(32)
非必填
發貨人電話
sender_province
varchar(32)
非必填
發貨人省份
sender_city
varchar(64)
非必填
發貨人城市
sender_area
varchar(64)
非必填
發貨人地區
sender_street
varchar(64)
非必填
發貨人街道
sender_address
varchar(1024)
非必填
發貨人詳細地址
sender_address_code
varchar(32)
非必填
四級地址編碼
buyer_id
bigint
必填
買家ID
buyer_name
varchar(64)
非必填
買家暱稱
seller_id
bigint
非必填
賣家ID
seller_name
varchar(64)
非必填
賣家暱稱
parent_lg_order_code
varchar(128)
非必填
父物流單號
biz_type
varchar(32)
必填
業務類型
order_origin
int
非必填
訂單來源
order_type
int
必填
訂單類型
status
int
必填
狀態
mailno
varchar(256)
非必填
運單號
express_code
varchar(32)
非必填
快遞公司編碼
express_name
varchar(32)
非必填
快遞公司名稱
is_delete
int
必填
是否刪除
feature
varchar(1024)
非必填
擴展字段,JSON格式
version
int
非必填
版本號,用於樂觀鎖
gmt_created
datetime
必填
建立時間
gmt_modified
datetime
必填
編輯時間

索引設計:

a)、主鍵id

b)、普通索引字段:lg_order_code、buyer_id

二、logistics_order_item

描述:物流子單表,主要存儲要發貨的商品信息,整張表大概分爲如下幾部分信息


表設計

字段名稱
字段類型
是否必填
描述
id
bigint
必填
主鍵
lg_order_code
varchar(128)
必填
物流單號
trade_order_code
varchar(128)
非必填
交易單號
trade_sub_order_code
varchar(128)
非必填
交易子單號
package_id
bigint
非必填
包裹ID
sku_id
bigint
非必填
skuid
sku_name
varchar(256)
非必填
sku名稱
buyer_id
bigint
必填
買家ID
seller_id
bigint
非必填
賣家ID
shop_id
bigint
非必填
店鋪ID
item_id
bigint
必填
商品ID
item_type
int
非必填
商品類型
item_name
varchar(256)
非必填
商品名稱
item_num
int
必填
商品數量
item_weight
decimal
非必填
商品重量
item_volumn
decimal
非必填
商品體積
marking
varchar(128)
非必填
商品標籤信息
status
int
必填
狀態
feature
varchar(1024)
非必填
擴展字段
is_delete
int
必填
是否刪除
version
int
必填
版本號
gmt_created
datetime
必填
建立時間
gmt_modified
datetime
必填
修改時間

索引設計:

a)、主鍵id

b)、普通索引字段:lg_order_code、buyer_id

三、logistics_order_pacakge

描述:物流包裹,是對物流商品的包裝。這張表主要是爲了拆單場景使用。拆單場景有不少種,好比同一個訂單下的不一樣商品發往不一樣地址,你們電商品拆分發貨,商品分倉發貨等等。總之,每個包裹都對應一個運單號,都有對應的發貨地和收貨地以及物流詳情。


整張表包含以下幾部分信息:


表設計

字段名稱
字段類型
是否必填
描述
id
bigint
必填
主鍵
lg_order_code
varchar(128)
必填
物流單號
trade_order_code
varchar(128)
非必填
交易單號
receiver_id
bigint
非必填
收貨人ID
receiver_name
varchar(64)
非必填
收貨人姓名
receiver_telephone
varchar(32)
非必填
收貨人電話
receiver_province
varchar(32)
非必填
收貨人省份
receiver_city
varchar(64)
非必填
收貨人城市
receiver_area
varchar(64)
非必填
收貨人地區
receiver_street
varchar(64)
非必填
收貨人街道
receiver_address
varchar(1024)
非必填
收貨人詳細地址
receiver_address_code
varchar(32)
非必填
四級地址編碼
sender_id
bigint
非必填
發貨人ID
sender_name
varchar(64)
非必填
發貨人姓名
sender_telephone
varchar(32)
非必填
發貨人電話
sender_province
varchar(32)
非必填
發貨人省份
sender_city
varchar(64)
非必填
發貨人城市
sender_area
varchar(64)
非必填
發貨人地區
sender_street
varchar(64)
非必填
發貨人街道
sender_address
varchar(1024)
非必填
發貨人詳細地址
sender_address_code
varchar(32)
非必填
四級地址編碼
buyer_id
bigint
必填
買家ID
seller_id
bigint
非必填
賣家ID
shop_id
bigint
非必填
店鋪ID
mailno
varchar(256)
非必填
運單號
express_code
varchar(32)
非必填
快遞公司編碼
express_name
varchar(32)
非必填
快遞公司名稱
pacakge_type
int
必填
包裹類型
status
int
必填
狀態
feature
varchar(1024)
非必填
擴展字段
is_delete
int
必填
是否刪除
version
int
必填
版本號
gmt_created
datetime
必填
建立時間
gmt_modified
datetime
必填
修改時間

索引設計:

a)、主鍵id

b)、普通索引字段:lg_order_code、buyer_id


四、logistics_order_unique

描述:物流去重表,用於建立的時候去重,具體做用會在第四節介紹。

字段名稱
字段類型
是否必填
描述
id
bigint
必填
主鍵
unique_code
varchar(196)
必填
去重單號
trade_code
varchar(128)
必填
業務單號
biz_type
varchar(32)
必填
業務類型
lg_order_id
bigint
必填
物流單主鍵ID
buyer_id
bigint
必填
買家ID
gmt_created
datetime
必填
建立時間
gmt_modified
datetime
必填
修改時間

索引設計

主鍵:id

惟一索引:unique_code

3、狀態機的設計

一、正向物流狀態機設計


正向物流包含了三條主要流程:

a、建立->發貨->簽收/拒籤

這種是最簡單的流程,也是用戶最關心的流程,若是公司使用的是第三方物流系統,那麼只要這條狀態流就足夠了。

b、建立->發貨->配送接單->配送攬收->配送派送->簽收/拒籤

這條狀態流對接了配送的物流流轉狀態,通常對接第三方物流詳情後,會獲得物流配送的信息。

c、建立->發貨->倉庫接單->倉庫出庫->配送攬收->配送派送->簽收/拒籤

這條狀態流是最複雜的,包含了倉庫和配送,通常只有大公司纔會考慮這麼細緻的狀態流轉。


二、逆向物流狀態機設計


由上面的狀態機能夠看出來,取消物流的時機有4種:

一、建立後取消

二、發貨後取消

三、倉庫接單後出庫前取消

四、配送接單後簽收前取消

上面第三種和第四種情況也叫倉截單和配截單,須要配合WMS系統和TMS系統進行特別開發。

4、高併發下的訂單建立接口設計

在整個交易物流業務流程中,物流訂單的建立是銜接交易和物流的關鍵環節。從系統架構上來講,首先交易和物流必須經過消息解耦,這樣能夠對交易中心的高流量進行削峯,減小物流訂單中心的壓力,其次,物流訂單中心必須提供高併發下穩定的建立接口,並且須要支持冪等。


爲此,咱們設計了以下的高併發建立流程:


一、生成物流訂單ID

這個ID必須提早生成,不能使用數據庫自增ID,緣由一個是後面訂單中心數據庫不可避免的會進行分庫分表,提早經過全局生成能夠規避後面遷移數據的風險,第二是提早生成ID能夠將ID存入去重表,這樣高併發下,多餘的建立請求能夠直接從去重表拿到訂單ID,而不須要走後面的流程。

二、構建惟一去重碼

惟一去重碼必須惟一識別一次請求,咱們經過業務單號+業務類型做爲去重碼,並構建惟一索引,保證高併發下不會重複建立。

三、開啓消息事務

因爲建立訂單流量很是大,因此除了必要的插入數據操做,其餘業務操做必須經過消息異步化。爲了保證消息必定可以發出去,咱們會使用MQ的消息事務保證。消息事務的原理能夠參考這篇文章:www.codeceo.com/article/dis…

四、開啓數據庫事務

數據庫事務就不用說了,可使用spring的事務模板。

五、插入去重表

這裏經過惟一去重碼的惟一索引保證建立的惟一性,若是插入失敗而且是數據庫惟一索引異常,則經過惟一去重碼去查去重表的數據,把裏面的物流訂單ID拿出來直接返回,若是是其餘異常,則直接拋異常回滾事務,不然插入去重表。

六、插入訂單數據

基本的數據庫操做,這一步若是出錯,會回滾整個事務。

七、發送消息

經過mq發送訂單建立消息,這一步出錯,按照上面的文章中的介紹,MQ會主動回調系統,驗證是不是數據庫插入成功消息沒發,若是是則會把該條消息設置爲已提交,從而保證消息發送成功。

經過上面的流程,咱們能夠保證物流訂單的高併發冪等建立。


5、高併發下的訂單更新接口設計

物流訂單中心承載了整個物流域的狀態流轉,對於物流訂單中心的更新也會比較多。平均來講,一筆物流訂單在整個生命週期中,會有10到20次更新,當物流訂單很是多的時候,更新的量是很是可觀的,所以,咱們須要設計出一套高併發的更新接口。

一、使用版本鎖保證數據不被覆蓋

咱們在設計數據庫表的時候,每每會加上version字段,這個字段就是用來作版本鎖的,版本鎖的流程以下:


二、鎖分離

對於更新來講,有些字段會頻繁更新,好比狀態,有些字段則較少更新。對於頻繁更新的字段,若是使用版本鎖,就會致使大量版本衝突,從而會影響其餘字段的更新。所以,咱們能夠對狀態更新單獨設計一個status_version字段,更新狀態只會使用這個字段,即便狀態更新衝突,也不影響其餘字段的更新,從而提升更新效率。

爲了使鎖分離,咱們須要在接口層面設計兩套接口,一套是通用的更新接口,用於全量更新字段,一套是相似狀態這樣的特殊字段的更新接口。

三、數據比對

在實踐中,咱們發現更新接口被誤用的狀況,好比數據徹底一致,也進行更新接口的調用,這些調用到數據庫層面僅僅是改了下gmt_modified字段,沒有任何其餘做用。對於這些誤調用,咱們經過更新字段的比對,將它們擋掉,這樣就減小了一部分數據庫的壓力。


6、高併發下的訂單查詢接口設計

物流訂單中心做爲物流領域的核心,其餘業務系統幾乎所有會依賴到物流訂單,物流訂單的查詢接口調用量每每會很是大,物流訂單能夠說是整個業務的單點,一旦物流訂單中心掛了,影響會很是大。所以,咱們必須設計高併發下的訂單查詢接口。

一、數據庫層面優化

首先是數據庫層面的優化,具體能夠參考這篇文章:www.jianshu.com/p/cd033668f…

二、分庫分表

物流訂單庫不可避免的會涉及到分庫分表,在進行分庫分表的時候須要注意三點:

a、物流訂單ID全局生成

物流訂單ID全局生成能夠參考雪花算法或者阿里TDDL的方法

b、選擇合適的分表字段

分表字段是用來作路由的,所以必須選擇必定會有的字段,好比買家ID。

c、sql語句儘可能不要跨表

一旦分庫分表,對於一些複雜的sql查詢必須進行拆分,不然會影響性能。若是沒法拆分,則須要遷移到搜索引擎中。

三、數據分離

物流訂單數據通常會分紅熱點數據和冷數據,熱點數據是最近生成的訂單,這些訂單還處於業務流轉中,冷數據是那些歷史數據,通常查詢量很是小。咱們能夠按照必定規則,把歷史數據遷移到Hbase保存,數據庫只留下熱點數據,從而減小數據庫的數據量。對於歷史數據,咱們須要提供歷史數據的查詢接口。

四、查詢接口優化

咱們在設計查詢接口的時候,設計一個LogisticsOrderQuery對象,其中包含查詢條件,以及一些開關:


isIncludePackage:這個開關告訴接口是否把包裹信息查出來

isIncludeItems:這個開關告訴接口是否把物流商品查出來

經過這些開關,能夠減小數據的查詢量,減輕數據庫壓力。

五、集羣讀寫分離

當上面的策略都沒法增長併發量的時候,咱們還剩最後一招,那就是加機器。可是,加機器也不是隨便加的,爲了更科學的利用自有,咱們把集羣分爲讀集羣和寫集羣,經過dubbo的接口路由規則,把讀流量分配到讀集羣,寫流量分配到寫集羣,咱們根據讀寫請求的峯值進行集羣的容量規劃,動態擴容。

7、總結

經過上面的介紹,咱們基本介紹完了一個物流訂單系統涉及到的技術要點,咱們能夠看出來,對於基礎能力相關的系統,每每對技術要求比較高,它們聚焦的是高併發下穩定、可靠的系統表現,而不是業務需求,這也是爲何中臺思想中要把系統分爲基礎能力系統和業務產品系統。接下來的一系列文章,我會逐一介紹其餘基礎能力系統,以及產品服務系統的設計要點,最後會把這兩種系統串起來,再次講一下基於中臺思想的系統設計。

更多文章歡迎訪問 http://www.apexyun.com/

聯繫郵箱:public@space-explore.com

(未經贊成,請勿轉載)

相關文章
相關標籤/搜索