基於Spring Cloud Netflix的TCC柔性事務和EDA事件驅動示例
Solar
Spring Cloud為開發者提供了快速構建分散式系統中的一些常見工具,如分散式配置中心,服務發現與註冊中心,智慧路由,服務熔斷及降級,訊息匯流排,分散式追蹤的解決方案等。
本次實戰以模擬下單流程為背景,結合Spring Cloud Netflix和分散式事務解決方案中Try Confirm Cancel模式與基於事件驅動的服務架構作為實戰演示。
開發環境
- 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解決方案。
RESTful TCC模式分3個階段執行
- Trying階段主要針對業務系統檢測及作出預留資源請求,若預留資源成功,則返回確認資源的連結與過期時間。
- Confirm階段主要是對業務系統的預留資源作出確認,要求TCC服務的提供方要對確認預留資源的介面實現冪等性,若Confirm成功則返回204,資源超時則證明已經被回收且返回404。
- Cancel階段主要是在業務執行錯誤或者預留資源超時後執行的資源釋放操作,Cancel介面是一個可選操作,因為要求TCC服務的提供方實現自動回收的功能,所以即便是不認為進行Cancel,系統也會自動回收資源。
Event Driven Architecture 基於事件驅動架構
本例項中的order-ms與membership-ms之間的通訊是基於事件驅動的。當訂單被成功建立並且付款成功之後,該訂單的部分資訊將會發往membership-ms以進行積分的增加。
從系統層面看,order-ms在EDA中是屬於Publisher角色,自然而然地membership-ms就是Subscriber。
Publisher中的事件狀態轉換如下:
- NEW —> PENDING —> DONE
- NEW —> PENDING —> FAILED / NO_ROUTE / NOT_FOUND / ERROR
Subscriber中的事件狀態轉換如下:
部分功能介紹:
- Publisher傳送訊息之前先將訊息落地,目的是防止訊息的錯誤釋出(業務資料被回滾而訊息卻釋出至Broker)。
- Publisher會週期性地掃描NEW狀態的訊息,併發布至Broker。
- 啟用mandatory與publisher confirms機制,在訊息被持久化至磁碟後將會收到basic.ack,此時可選擇將訊息轉換為DONE或者是直接將其刪除。
- Publisher將訊息釋出至Broker後會將其狀態由NEW更新為PENDING,PENDING狀態的事件將會由另一定時器掃描在當前時鐘的3秒之前釋出,但是卻並未得到basic.ack的事件,並重新發布至Broker。意在消除在單例項的情況下因crash而導致訊息狀態丟失的邊緣情況。
- 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
這些自定義配置正是控制方法返回的時延,隨機異常的因子等。
我在服務
order
,product
,account
和tcc
中的所有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向服務
account
與product
發起預留資源請求,並且記錄入庫。 - 確認訂單. 確認訂單時根據訂單ID從庫中獲取訂單,並獲取預留資源確認的URI,交由服務
tcc
統一進行確認,如果發生衝突即記錄入庫,等待人工處理。
membership
用於訂單付款成功後,對下單使用者的積分進行增加操作。該服務與訂單服務是基於訊息驅動以進行通訊,達到事務的最終一致性。
Swagger UI
下圖為product
服務的Swagger介面文件,根據下文的服務字典可知,本介面文件可通過http://localhost:8040/swagger-ui.html
進行訪問. order
,account
和tcc
的文件訪問方式亦是如出一撤。
執行
Docker Compose執行
在專案根路徑下執行指令碼build.sh
,該指令碼會執行Maven的打包操作,並會迭代目錄下的*-compose.yml
進行容器構建。
構建完成後需要按照指定的順序啟動,需要注意的一點是容器內服務的啟動是需要備留預熱時間,並非Docker容器啟動後容器內的所有服務就能馬上啟動起來,要注意區分容器的啟動和容器內的服務的啟動,建議配合docker-compse logs來觀察啟動情況。而且容器之間的服務是有依賴的,如account-ms
和product-ms
此類業務服務的啟動是會快速失敗於config-ms
的失聯。所以建議按照以下順序啟動Docker容器,並且在一組Docker容器服務完全啟動後,再啟動下一組的Docker容器。
- 啟動MySQL,RabbitMQ等基礎元件
docker-compose -f infrastructure-compose.yml up -d
- 啟動Eureka Server與Config Server
docker-compose -f basic-ms-compose.yml up -d
- 啟動監控服務
docker-compose -f monitor-ms-compose.yml up -d
- 啟動業務服務
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.port
和management.context-path
來提高攻擊成本. 是的,我對每一個服務都修改了以上兩個屬性,並且相容了Eureka Server,Hystrix Dashboard,Spring Boot Admin,使這些監控服務仍能正確工作. 因為對以上兩個引數修改,我們的監控路徑有所變化,如下表:
原文:Java架構筆記
免費Java高階資料需要自己領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分散式等教程,一共30G。
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT