使用go效率工具一小時輕鬆搭建一個簡單可靠的訂單系統,使用dtm解決分散式事務超級簡單

zhuyasen發表於2023-12-05

訂單系統簡介

訂單系統是交易平臺的核心繫統,涉及多個方面的複雜任務,需要仔細考慮業務需求、效能、可擴充套件性和安全性等因素。把訂單系統拆分為訂單服務、庫存服務、優惠券服務、支付服務等等,每個服務都有自己獨立資料庫。訂單處理過程中必然會涉及到分散式事務,例如建立訂單與扣減庫存需要保證原子性,在分散式系統中,保證這些操作的原子性,會遇到不少難題需要克服,例如程式crash問題冪等問題回滾問題精準補償問題等。

在單體服務訂單系統中,使用資料庫的本身支援的事務很容易解決,服務化之後必須考慮分散式系統問題,目前常見的解決分散式事務有訊息佇列方案狀態機方案,兩種解決方案都比較重,使得訂單系統變得更復雜。而dtm作為另一種解決分散式事務方案,極大的簡化了訂單系統架構,使用dtm優雅的解決了分散式事務中的資料一致性問題。

當前端請求grpc閘道器服務order_gw提交訂單api介面,服務端完成以下操作:

  • 訂單服務order:在訂單表中建立訂單,訂單id作為唯一鍵。
  • 庫存服務stock:在庫存表中扣減庫存,如果庫存不足,全域性事務自動回滾。
  • 優惠券服務coupon:在優惠券表中標記優惠券已使用,如果優惠券無效,全域性事務自動回滾。
  • 支付服務pay:在支付表中建立支付單,最後告訴使用者跳轉到支付頁付款。


下面從0開始搭建一個簡單的訂單系統,這是按照下面步驟搭建的訂單系統原始碼


準備工作

(1) 準備一個mysql服務,使用指令碼docker-compose.yaml快速啟動一個mysql服務。


(2) 把準備好的sql匯入到mysql。


(3) 準備proto檔案。


(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 協議》,轉載必須註明作者和本文連結

相關文章