訂單系統簡介
訂單系統是交易平臺的核心繫統,涉及多個方面的複雜任務,需要仔細考慮業務需求、效能、可擴充套件性和安全性等因素。把訂單系統拆分為訂單服務、庫存服務、優惠券服務、支付服務等等,每個服務都有自己獨立資料庫。訂單處理過程中必然會涉及到分散式事務,例如建立訂單與扣減庫存需要保證原子性,在分散式系統中,保證這些操作的原子性,會遇到不少難題需要克服,例如程式crash問題
、冪等問題
、回滾問題
、精準補償問題
等。
在單體服務訂單系統中,使用資料庫的本身支援的事務很容易解決,服務化之後必須考慮分散式系統問題,目前常見的解決分散式事務有訊息佇列方案
和狀態機方案
,兩種解決方案都比較重,使得訂單系統變得更復雜。而dtm作為另一種解決分散式事務方案,極大的簡化了訂單系統架構,使用dtm優雅的解決了分散式事務中的資料一致性問題。
當前端請求grpc閘道器服務order_gw提交訂單api介面,服務端完成以下操作:
- 訂單服務order:在訂單表中建立訂單,訂單id作為唯一鍵。
- 庫存服務stock:在庫存表中扣減庫存,如果庫存不足,全域性事務自動回滾。
- 優惠券服務coupon:在優惠券表中標記優惠券已使用,如果優惠券無效,全域性事務自動回滾。
- 支付服務pay:在支付表中建立支付單,最後告訴使用者跳轉到支付頁付款。
下面從0開始搭建一個簡單的訂單系統,這是按照下面步驟搭建的訂單系統原始碼。
準備工作
(1) 準備一個mysql服務,使用指令碼docker-compose.yaml快速啟動一個mysql服務。
(2) 把準備好的sql匯入到mysql。
- dtm相關sql
- 訂單相關sql
(3) 準備proto檔案。
- order.proto 用來建立訂單服務order。
- stock.proto 用來建立庫存服務stock。
- coupon.proto 用來建立優惠券服務coupon。
- pay.proto 用來建立支付服務pay。
- order_gw.proto 用來建立grpc閘道器服務order_gw。
(4) 安裝工具 sponge。
安裝完工具sponge後,執行命令開啟生成程式碼的UI介面:
sponge run
(5) 啟動分散式事務管理器dtm服務。
使用docker-compose.yml指令碼執行一個dtm服務。
version: '3'
services:
dtm:
image: yedf/dtm
container_name: dtm
restart: always
environment:
STORE_DRIVER: mysql
STORE_HOST: '192.168.3.37'
STORE_USER: root
STORE_PASSWORD: '123456'
STORE_PORT: 3306
#volumes:
# - /etc/localtime:/etc/localtime:ro
# - /etc/timezone:/etc/timezone:ro
ports:
- '36789:36789'
- '36790:36790'
修改STORE_xxx相關環境變數值,然後啟動dtm服務:
docker-compose up -d
快速建立訂單系統相關的微服務
生成訂單、庫存、優惠券、支付、grpc閘道器5個服務程式碼
進入sponge的UI介面,點選左邊選單欄【Protobuf】–>【建立微服務專案】,填寫引數,分別生成訂單、庫存、優惠券、支付服務程式碼。
快速建立訂單服務order,如下圖所示:
快速建立庫存服務stock,如下圖所示:
快速建立優惠券服務coupon,如下圖所示:
快速建立支付服務pay,如下圖所示:
快速建立grpc閘道器服務order_gw,點選左邊選單欄【Protobuf】–>【建立grpc閘道器服務】,填寫引數,點選下載程式碼按鈕即可,如下圖所示:
把生成的5個服務名稱分別修改為order、stock、coupon、pay、order_gw,並開啟5個終端,每個服務對應一個終端。
配置和執行庫存服務stock
切換到庫存服務stock目錄,按下面步驟操作:
(1) 生成與自動合併api介面相關程式碼。
make proto
(2) 新增連線mysql程式碼。
make patch TYPE=mysql-init
(3) 開啟配置檔案configs/stock.yml
,修改mysql地址和賬號資訊,修改預設的grpc服務埠,主要是為了避免埠衝突。
mysql:
dsn: "root:123456@(192.168.3.37:3306)/eshop_stock?parseTime=true&loc=Local&charset=utf8mb4"
grpc:
port: 28282
httpPort: 28283
(4) 在生成的模板程式碼上新增扣減庫存和補償庫存的業務邏輯程式碼,點選檢視程式碼internal/service/stock.go。
(5) 編譯和啟動庫存服務stock:
make run
這是根據上面步驟完成的庫存服務stock原始碼。
配置和執行優惠券服務coupon
切換到優惠券服務coupon目錄,操作步驟與上面的配置和執行庫存服務stock一樣,除了業務邏輯程式碼。這是優惠券服務coupon原始碼。
配置和填寫完具體的業務邏輯程式碼後,編譯和啟動優惠券服務coupon:
make run
配置和執行支付服務pay
切換到支付服務pay目錄,操作步驟與上面的配置和執行庫存服務stock一樣,除了業務邏輯程式碼。這是支付服務pay原始碼。
配置和填寫完具體的業務邏輯程式碼碼後,編譯和啟動支付服務pay:
make run
配置和執行訂單服務order
切換到訂單服務order目錄,操作步驟與上面的配置和執行庫存服務stock一樣,除了業務邏輯程式碼。這是訂單服務order原始碼。
因為提交訂單時候需要把訂單服務order、庫存服務stock、優惠券服務coupon、支付服務pay的grpc服務地址告訴dtm服務,讓dtm服務協調管理分散式事務,所以需要配置這些地址,開啟配置檔案configs/order.yml
,新增訂單相關的服務地址和dmt服務地址配置,如下所示:
grpcClient:
- name: "order"
host: "127.0.0.1"
port: 8282
- name: "coupon"
host: "127.0.0.1"
port: 18282
registryDiscoveryType: ""
enableLoadBalance: false
- name: "stock"
host: "127.0.0.1"
port: 28282
registryDiscoveryType: ""
enableLoadBalance: false
- name: "pay"
host: "127.0.0.1"
port: 38282
registryDiscoveryType: ""
enableLoadBalance: false
dtm:
addr: "127.0.0.1:36790"
配置檔案新增了新欄位,需要更新到對應的go結構體程式碼:
make update-config
在生成的模板程式碼上新增的提交訂單、建立訂單、取消訂單業務邏輯程式碼,點選檢視程式碼internall/service/order.go。
配置和填寫完業務邏輯程式碼碼後,編譯和啟動訂單服務:
make run
配置和執行grpc閘道器服務order_gw
(1) 生成grpc服務連線程式碼。
grpc閘道器服務order_gw作為請求入口,因為前端是http請求,而後端是grpc服務,需要把http轉為grpc請求,因此需要生成連線order服務的程式碼,如果有必要也可以按照同樣步驟新增其他服務(stock、coupon、pay)的grpc連線程式碼。進入sponge的UI介面,點選左邊選單欄【Public】–>【生成grpc服務連線程式碼】,填寫引數生成grpc服務連線程式碼,如下圖所示:
解壓程式碼,把internal目錄移動到grpc閘道器服務order_gw服務目錄下。
(2) 複製proto檔案。
因為grpc閘道器服務order_gw需要知道訂單服務order有哪些api介面可以呼叫,因此需要把訂單服務order的proto檔案複製過來,開啟終端,切換到order_gw目錄,執行命令:
make copy-proto SERVER=../order
(3) 開啟配置檔案configs/order_gw.yml
,配置訂單服務order地址。
grpcClient:
- name: "order"
host: "127.0.0.1"
port: 8282
registryDiscoveryType: ""
enableLoadBalance: false
(4) 生成與自動合併api介面相關程式碼。
make proto
(5) 填寫業務邏輯程式碼,也就是http請求轉為grpc請求,這裡可以直接使用已經生成的模板程式碼示例即可。點選檢視程式碼internal/service/order_gw.go。
配置和填寫完業務邏輯程式碼碼後,編譯和啟動grpc閘道器服務order_gw:
make run
測試分散式事務
在瀏覽器開啟swagger介面 http://localhost:8080/apis/swagger/index.html
,測試提交訂單api介面。
在dtm的管理介面 http://localhost:36789
可以檢視分散式事務狀態和詳情。
在各個服務的終端可以檢視日誌資訊瞭解dtm協調呼叫的api介面情況。
測試成功提交訂單場景
在swagger介面上,填寫請求引數。
點選Execute按鈕進行測試,提交訂單成功,從dtm的管理介面和各個服務日誌可以看到。
測試失敗提交訂單場景
(1) 優惠券無效造成訂單失敗。
在請求引數不變情況下,
{
"userId": 1,
"productId": 1,
"amount": 1100,
"productCount": 1,
"couponId": 1
}
直接點選Execute按鈕測試,雖然返回了訂單id(這不表示訂單成功,實際需要獲取到訂單成功狀態再執行後面操作),從dtm的管理介面和優惠券服務coupon日誌可以看到,訂單狀態是失敗的,因為優惠券已經被使用,返回了Aborted
錯誤,dtm收到Aborted
錯誤資訊之後,會對已經建立訂單和扣減庫存分支事務進行補償,保證資料最終一致。
(2) 庫存不足造成訂單失敗。
填寫請求引數,欄位productCount值為1000確定大於了庫存數量,把引數couponId設定為0表示不使用優惠券。
{
"userId": 1,
"productId": 1,
"amount": 1100000,
"productCount": 1000,
"couponId": 0
}
點選Execute按鈕測試,雖然返回了訂單id(這不表示訂單成功),從dtm的管理介面和庫存服務stock日誌可以看到,訂單狀態是失敗的,因為庫存不足原因,返回了Aborted
錯誤,dtm收到Aborted
錯誤資訊之後,會對建立訂單分支事務進行補償,保證資料最終一致。
後續新增支付業務邏輯程式碼之後,可以測試賬號餘額不足導致訂單失敗,dtm會補償分支事務確保資料最終一致。
測試模擬程式crash,恢復後成功提交訂單場景
停止庫存服務stock,然後在swagger介面填寫請求引數:
{
"userId": 1,
"productId": 1,
"amount": 1100,
"productCount": 1,
"couponId": 0
}
點選Execute按鈕測試,雖然返回了訂單id(這不表示訂單成功),從dtm的管理介面看到訂單狀態是submitted
狀態,dtm會一直重試連線庫存服務stock,重試預設是指數退避演算法,可以修改為固定時間間隔重試。啟動庫存服務,dtm連線庫存服務成功之後,接著完成後續分支事務,成功完成訂單,保證資料最終一致。根據業務需求也可以做超時主動強制取消訂單處理,dtm收到強制取消訂單後,會對建立訂單分支事務進行補償,也保證資料最終一致。
總結
本文介紹了從0開始快速搭建一個簡單的訂單系統,使用sponge很容易構建微服務,使用dtm優雅的解決提交訂單的分散式事務,開發一個訂單系統變得很簡單,讓開發人員把精力用在業務開發上。
各個服務的常用服務治理功能也是具備的,例如服務註冊與發現、限流、熔斷、鏈路跟蹤、監控、效能分析、資源統計、CICD等,這些功能統一在yml配置檔案開啟或關閉。
這不是完整的訂單系統,只有一個提交訂單業務邏輯,如果需要構建一個自己的訂單系統,可以作為一個參考。根據上面操作步驟,很容易新增商品服務product、物流服務logistics、使用者服務user等服務模組組成一個電商平臺。
本作品採用《CC 協議》,轉載必須註明作者和本文連結