基於Spring Cloud Netflix的TCC柔性事務和EDA事件驅動示例

Solar

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

本次實戰以模擬下單流程爲背景,結合Spring Cloud Netflix和分佈式事務解決方案中Try Confirm Cancel模式與基於事件驅動的服務架構做爲實戰演示。java

開發環境

  • Docker 1.13.1
  • Docker Compose 1.11.1
  • Docker MySQL 5.7.17
  • Docker RabbitMQ 3.6.6
  • Java8 with JCE
  • Spring Cloud Camden.SR6

系統結構


Try Confirm Cancel 補償模式

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

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


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

Event Driven Architecture 基於事件驅動架構

本實例中的order-ms與membership-ms之間的通訊是基於事件驅動的。當訂單被成功建立而且付款成功以後,該訂單的部分信息將會發往membership-ms以進行積分的增長。github

從系統層面看,order-ms在EDA中是屬於Publisher角色,天然而然地membership-ms就是Subscriber。web

Publisher中的事件狀態轉換以下:spring

  • NEW —> PENDING —> DONE
  • NEW —> PENDING —> FAILED / NO_ROUTE / NOT_FOUND / ERROR


Subscriber中的事件狀態轉換以下:sql

  • NEW —> DONE
  • NEW —> FAILED / NOT_FOUND / ERROR

部分功能介紹:docker

  1. Publisher發送消息以前先將消息落地,目的是防止消息的錯誤發佈(業務數據被回滾而消息卻發佈至Broker)。
  2. Publisher會週期性地掃描NEW狀態的消息,併發布至Broker。
  3. 啓用mandatory與publisher confirms機制,在消息被持久化至磁盤後將會收到basic.ack,此時可選擇將消息轉換爲DONE或者是直接將其刪除。
  4. Publisher將消息發佈至Broker後會將其狀態由NEW更新爲PENDING,PENDING狀態的事件將會由另外一定時器掃描在當前時鐘的3秒以前發佈,可是卻並未獲得basic.ack的事件,並從新發布至Broker。意在消除在單實例的狀況下因crash而致使消息狀態丟失的邊緣狀況。
  5. Subscriber的消息冪等性。

基礎組件

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的安裝)
  • 爲了達到開箱即用,選用公開倉庫Github或者GitOsc。
  • 本項目中有兩個自定義註解 @com.github.prontera.Delay 控制方法的延時返回時間;@com.github.prontera.RandomlyThrowsException 隨機拋出異常,人爲地製造異常。
    默認的遠程配置以下
    solar: delay: time-in-millseconds: 0 exception: enabled: false factor: 7
    這些自定義配置正是控制方法返回的時延,隨機異常的因子等。
    我在服務orderproductaccounttcc中的全部Controller上都添加了以上兩個註解,當遠程配置的更新時候,能夠手工刷新/refresh或經過webhook等方法自動刷新本地配置. 以達到模擬微服務繁忙或熔斷等狀況。

監控服務

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統一進行確認,若是發生衝突即記錄入庫,等待人工處理。

membership

用於訂單付款成功後,對下單用戶的積分進行增長操做。該服務與訂單服務是基於消息驅動以進行通訊,達到事務的最終一致性。

Swagger UI

下圖爲product服務的Swagger接口文檔,根據下文的服務字典可知,本接口文檔可經過http://localhost:8040/swagger-ui.html進行訪問. orderaccounttcc的文檔訪問方式亦是如出一撤。

運行

Docker Compose運行

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

構建完成後須要按照指定的順序啓動,須要注意的一點是容器內服務的啓動是須要備留預熱時間,並不是Docker容器啓動後容器內的全部服務就能立刻啓動起來,要注意區分容器的啓動容器內的服務的啓動,建議配合docker-compse logs來觀察啓動狀況。並且容器之間的服務是有依賴的,如account-msproduct-ms此類業務服務的啓動是會快速失敗於config-ms的失聯。因此建議按照如下順序啓動Docker容器,而且在一組Docker容器服務徹底啓動後,再啓動下一組的Docker容器。

  1. 啓動MySQL,RabbitMQ等基礎組件docker-compose -f infrastructure-compose.yml up -d
  2. 啓動Eureka Server與Config Serverdocker-compose -f basic-ms-compose.yml up -d
  3. 啓動監控服務docker-compose -f monitor-ms-compose.yml up -d
  4. 啓動業務服務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 > 其餘業務微服務(account-ms, product-ms, order-ms, tcc-coordinator-ms等)

示例

根據附表中的服務字典,咱們經過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倉庫的相關驗證等參數便可。

服務字典

鑑於Spring Boot Actuator的端點所帶來的兩面性,除了能夠增長spring-boot-starter-security來得到強度較弱的HTTP Basic認證外,咱們還能夠修改management.portmanagement.context-path來提升攻擊成本. 是的,我對每個服務都修改了以上兩個屬性,而且兼容了Eureka Server,Hystrix Dashboard,Spring Boot Admin,使這些監控服務仍能正確工做. 由於對以上兩個參數修改,咱們的監控路徑有所變化,以下表:


原文:Java架構筆記

相關文章
相關標籤/搜索