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

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

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

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

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

Try Confirm Cancel補償模式

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

RESTful TCC模式分3個階段執行web

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

系統結構

基礎組件

Zuul Gateway

Zuul在本實例中僅做爲路由所使用, 配置下降Ribbon的讀取與鏈接超時上限面試

Eureka H.A.

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

Config Server

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

    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或者GitOscdocker

  • 本項目中有兩個自定義註解
    @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倉庫的相關驗證等參數便可

歡迎工做一到五年的Java工程師朋友們加入Java架構開發:855801563

本羣提供免費的學習指導 架構資料 以及免費的解答

不懂得問題均可以在本羣提出來 以後還會有職業生涯規劃以及面試指導

同時你們能夠多多關注一下小編 純乾貨 你們一塊兒學習進步

相關文章
相關標籤/搜索