Spring Cloud綜合實戰 - 基於TCC補償模式的分佈式事務

本文通過使用Spring Cloud和Docker構建了一個常見的Microservice體系.

Spring Cloud爲開發者提供了快速構建分佈式系統中的一些常見工具, 如分佈式配置中心, 服務發現與註冊中心, 智能路由, 服務熔斷及降級, 消息總線等.

而Spring Cloud Sleuth爲Spring Cloud提供了分佈式追蹤方案, 可視化地分析服務調用鏈路和服務間的依賴關係

本次實戰以模擬下單流程作爲實戰演示, 使用Try-Confirm-Cancel即TCC模式爲分佈式事務提供最終一致性.

完整的代碼示例已經上傳至Github

Try Confirm Cancel補償模式

本實例遵循的是Atomikos公司對微服務的分佈式事務所提出的RESTful TCC解決方案

RESTful TCC模式分3個階段執行

  1. Trying階段主要針對業務系統檢測及作出預留資源請求, 若預留資源成功, 則返回確認資源的鏈接與過期時間
  2. Confirm階段主要是對業務系統的預留資源作出確認, 要求TCC服務的提供方要對確認預留資源的接口實現冪等性, 若Confirm成功則返回204, 資源超時則證明已經被回收且返回404
  3. Cancel階段主要是在業務執行錯誤或者預留資源超時後執行的資源釋放操作, Cancel接口是一個可選操作, 因爲要求TCC服務的提供方實現自動回收的功能, 所以即便是不認爲進行Cancel, 系統也會自動回收資源

對RESTful TCC事務更爲詳細的解釋可以點擊這裏進行閱讀

系統結構

基礎組件

Zuul Gateway

Zuul在本實例中僅作爲路由所使用, 配置降低Ribbon的讀取與連接超時上限

Eureka H.A.

多個對等Eureka節點組成高可用集羣, 並將註冊列表的自我保護的閾值適當降低

Config Server

  • 如果遠程配置中有密文{cipher}*, 那麼該密文的解密將會延遲至客戶端啓動的時候. 因此客戶端需要配置AES的對稱密鑰encrypt.key, 並且客戶端所使用的JRE需要安裝Java 8 JCE, 否則將會拋出Illegal key size相關的異常.
    (本例中Docker Compose構建的容器已經安裝了JCE, 如果遠程配置文件沒有使用{cipher}*也不必進行JCE的安裝)

    spring:
    cloud:
      config:
        server:
          git:
            uri: 'https://git.oschina.net/witless/conf-repo.git'
            clone-on-start: true
          encrypt:
            enabled: false
    application:
      name: 'config-server'
  • 爲了達到開箱即用, 選用公開倉庫Github或者GitOsc

  • 本項目中有兩個自定義註解
    @com.github.prontera.Delay 控制方法的延時返回時間

    @com.github.prontera.RandomlyThrowsException 隨機拋出異常, 人爲地製造異常

    默認的遠程配置如下

    solar:
    delay:
      time-in-millseconds: 0
    exception:
      enabled: false
      factor: 7

    這些自定義配置正是控制方法返回的時延, 隨機異常的因子等

    我在服務order, product, accounttcc中的所有Controller上都添加了以上兩個註解, 當遠程配置的更新時候, 可以手工刷新/refresh或通過webhook等方法自動刷新本地配置. 以達到模擬微服務繁忙或熔斷等情況.

RabbitMQ

原本作爲可靠性事件投遞的Broker, 如今被TCC模式所替代. 可爲日後的Spring Cloud Steam或Spring Cloud Bus的集成作爲基礎組件而保留

監控服務

Spring Boot Admin

此應用提供了管理Spring Boot服務的簡單UI, 下圖是在容器中運行時的服務健康檢測頁

Hystrix Dashboard

提供近實時依賴的統計和監控面板, 以監測服務的超時, 熔斷, 拒絕, 降級等行爲

Zipkin Server

Zipkin是一款開源的分佈式實時數據追蹤系統, 其主要功能是聚集來自各個異構系統的實時監控數據, 用來追蹤微服務架構下的系統時延問題. 下圖是對order服務的請求進行追蹤的情況

業務服務

首次啓動時通過Flyway自動初始化數據庫

對spring cloud config server採用fail fast策略, 一旦遠程配置服務無法連接則無法啓動業務服務

account

用於獲取用戶信息, 用戶註冊, 修改用戶餘額, 預留餘額資源, 確認預留餘額, 撤銷預留餘額

product

用於獲取產品信息, 變更商品庫存, 預留庫存資源, 確認預留庫存, 撤銷預留庫存

tcc coordinator

TCC資源協調器, 其職責如下

  • 對所有參與者發起Confirm請求
  • 無論是協調器發生的錯誤還是調用參與者所產生的錯誤, 協調器都必須有自動恢復重試功能, 尤其是在確認的階段, 以防止網絡抖動的情況

order

order服務是本項目的入口, 儘管所提供的功能很簡單

  • 下單. 即生成預訂單, 爲了更好地測試TCC功能, 在下單時就通過Feign向服務accountproduct發起預留資源請求, 並且記錄入庫
  • 確認訂單. 確認訂單時根據訂單ID從庫中獲取訂單, 並獲取預留資源確認的URI, 交由服務tcc統一進行確認, 如果發生衝突即記錄入庫, 等待人工處理

與其他服務進行通訊, 我們選擇使用Feign

/**
 * @author Zhao Junjian
 */
@FeignClient(name = TccClient.SERVICE_ID, fallback = TccClientFallback.class)
public interface TccClient {
    /**
     * eureka service name
     */
    String SERVICE_ID = "tcc";
    /**
     * api prefix
     */
    String API_PATH = "/api/v1/coordinator";

    @RequestMapping(value = API_PATH + "/confirmation", method = RequestMethod.PUT, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}, consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    void confirm(@RequestBody TccRequest request);

    @RequestMapping(value = API_PATH + "/cancellation", method = RequestMethod.PUT, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}, consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    void cancel(@RequestBody TccRequest request);

}

Swagger UI

Swagger的目標是爲REST APIs 定義一個標準的, 與語言無關的接口, 使人和計算機在看不到源碼或者看不到文檔或者不能通過網絡流量檢測的情況下能發現和理解各種服務的功能. 當服務通過Swagger定義, 消費者就能與遠程的服務互動通過少量的實現邏輯. 類似於低級編程接口, Swagger去掉了調用服務時的很多猜測.

運行

Docker Compose運行

在項目根路徑下執行腳本build.sh, 該腳本會執行Maven的打包操作, 並會迭代目錄下的*-compose.yml進行容器構建

構建完成後需要按照指定的順序啓動

  1. 啓動MySQL, RabbitMQ等基礎組件

    ➜  solar git:(feature/cleanup) ✗ docker-compose -f infrastructure-compose.yml up -d
  2. 啓動Eureka Server與Config Server

    ➜  solar git:(feature/cleanup) ✗ docker-compose -f basic-ms-compose.yml up -d
  3. 啓動監控服務

    ➜  solar git:(feature/cleanup) ✗ docker-compose -f monitor-ms-compose.yml up -d
  4. 啓動業務服務

    ➜  solar git:(feature/cleanup) ✗ docker-compose -f business-ms-compose.yml up -d

IDE運行

因爲程序本身按照Docker啓動, 所以對於hostname需要在hosts文件中設置正確才能正常運行

## solar
127.0.0.1 eureka1
127.0.0.1 eureka2
127.0.0.1 rabbitmq
127.0.0.1 zipkin_server
127.0.0.1 solar_mysql
127.0.0.1 gitlab

根據依賴關係, 程序最好按照以下的順序執行

docker mysql > docker rabbitmq > eureka server > config server > zipkin server > 其他微服務

示例

根據附表中的服務字典, 我們通過Zuul或Swagge對order服務進行預訂單生成操作

POST http://localhost:7291/order/api/v1/orders
Content-Type: application/json;charset=UTF-8

{
  "product_id": 7,
  "user_id": 1
}

成功後我們將得到預訂單的結果

{
  "data": {
    "id": 15,
    "create_time": "2017-03-28T18:18:02.206+08:00",
    "update_time": "1970-01-01T00:00:00+08:00",
    "delete_time": "1970-01-01T00:00:00+08:00",
    "user_id": 1,
    "product_id": 7,
    "price": 14,
    "status": "PROCESSING"
  },
  "code": 20000
}

此時我們再確認訂單

(如果想測試預留資源的補償情況, 那麼就等15s後過期再發請求, 注意容器與宿主機的時間)

POST http://localhost:7291/order/api/v1/orders/confirmation
Content-Type: application/json;charset=UTF-8

{
  "order_id": 15
}

如果成功確認則返回如下結果

{
  "data": {
    "id": 15,
    "create_time": "2017-03-28T18:18:02.206+08:00",
    "update_time": "2017-03-28T18:21:32.78+08:00",
    "delete_time": "1970-01-01T00:00:00+08:00",
    "user_id": 1,
    "product_id": 7,
    "price": 14,
    "status": "DONE"
  },
  "code": 20000
}

至此就完成了一次TCC事務, 當然你也可以測試超時和衝突的情況, 這裏就不再贅述

拓展

使用Gitlab作爲遠程配置倉庫

本例中默認使用Github或GitOsc中的公開倉庫, 出於自定義的需要, 我們可以在本地構建Git倉庫, 這裏選用Gitlab爲例.

將以下配置添加至docker compose中的文件中並啓動Docker Gitlab容器

gitlab:
    image: daocloud.io/daocloud/gitlab:8.16.7-ce.0
    ports:
        - "10222:22"
        - "80:80"
        - "10443:443"
    volumes:
        - "./docker-gitlab/config/:/etc/gitlab/"
        - "./docker-gitlab/logs/:/var/log/gitlab/"
        - "./docker-gitlab/data/:/var/opt/gitlab/"
    environment:
        - TZ=Asia/Shanghai

將項目的config-repo添加至Gitlab中, 並修改config-ms中git倉庫的相關驗證等參數即可

結語

更爲詳細的說明及代碼示例已經上傳至Github

https://github.com/prontera/spring-cloud-rest-tcc

如有對本項目中的Spring Cloud的使用或者對本人的編碼風格有更好的想法或者建議, 希望能在下方給我留言, 再次感謝你的耐心閱讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章