Spring Cloud綜合實戰(zhàn) - 基于TCC補償模式的分布式事務(wù)

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

Spring Cloud為開發(fā)者提供了快速構(gòu)建分布式系統(tǒng)中的一些常見工具, 如分布式配置中心, 服務(wù)發(fā)現(xiàn)與注冊中心, 智能路由, 服務(wù)熔斷及降級, 消息總線等.

而Spring Cloud Sleuth為Spring Cloud提供了分布式追蹤方案, 可視化地分析服務(wù)調(diào)用鏈路和服務(wù)間的依賴關(guān)系

本次實戰(zhàn)以模擬下單流程作為實戰(zhàn)演示, 使用Try-Confirm-Cancel即TCC模式為分布式事務(wù)提供最終一致性.

Try Confirm Cancel補償模式

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

RESTful TCC模式分3個階段執(zhí)行

Trying階段主要針對業(yè)務(wù)系統(tǒng)檢測及作出預(yù)留資源請求, 若預(yù)留資源成功, 則返回確認資源的鏈接與過期時間

Confirm階段主要是對業(yè)務(wù)系統(tǒng)的預(yù)留資源作出確認, 要求TCC服務(wù)的提供方要對確認預(yù)留資源的接口實現(xiàn)冪等性, 若Confirm成功則返回204, 資源超時則證明已經(jīng)被回收且返回404

Cancel階段主要是在業(yè)務(wù)執(zhí)行錯誤或者預(yù)留資源超時后執(zhí)行的資源釋放操作, Cancel接口是一個可選操作, 因為要求TCC服務(wù)的提供方實現(xiàn)自動回收的功能, 所以即便是不認為進行Cancel, 系統(tǒng)也會自動回收資源

對RESTful TCC事務(wù)更為詳細的解釋可以點擊這里進行閱讀

系統(tǒng)結(jié)構(gòu)

基礎(chǔ)組件

Zuul Gateway

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

Eureka H.A.

多個對等Eureka節(jié)點組成高可用集群, 并將注冊列表的自我保護的閾值適當降低

Config Server

如果遠程配置中有密文{cipher}*, 那么該密文的解密將會延遲至客戶端啟動的時候. 因此客戶端需要配置AES的對稱密鑰encrypt.key, 并且客戶端所使用的JRE需要安裝Java 8 JCE, 否則將會拋出Illegal key size相關(guān)的異常.

(本例中Docker Compose構(gòu)建的容器已經(jīng)安裝了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

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

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

RabbitMQ

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

監(jiān)控服務(wù)

Spring Boot Admin

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

Hystrix Dashboard

提供近實時依賴的統(tǒng)計和監(jiān)控面板, 以監(jiān)測服務(wù)的超時, 熔斷, 拒絕, 降級等行為

Zipkin Server

Zipkin是一款開源的分布式實時數(shù)據(jù)追蹤系統(tǒng), 其主要功能是聚集來自各個異構(gòu)系統(tǒng)的實時監(jiān)控數(shù)據(jù), 用來追蹤微服務(wù)架構(gòu)下的系統(tǒng)時延問題. 下圖是對order服務(wù)的請求進行追蹤的情況

業(yè)務(wù)服務(wù)

首次啟動時通過Flyway自動初始化數(shù)據(jù)庫

對spring cloud config server采用fail fast策略, 一旦遠程配置服務(wù)無法連接則無法啟動業(yè)務(wù)服務(wù)

account

用于獲取用戶信息, 用戶注冊, 修改用戶余額, 預(yù)留余額資源, 確認預(yù)留余額, 撤銷預(yù)留余額

product

用于獲取產(chǎn)品信息, 變更商品庫存, 預(yù)留庫存資源, 確認預(yù)留庫存, 撤銷預(yù)留庫存

tcc coordinator

TCC資源協(xié)調(diào)器, 其職責如下

對所有參與者發(fā)起Confirm請求

無論是協(xié)調(diào)器發(fā)生的錯誤還是調(diào)用參與者所產(chǎn)生的錯誤, 協(xié)調(diào)器都必須有自動恢復(fù)重試功能, 尤其是在確認的階段, 以防止網(wǎng)絡(luò)抖動的情況

order

order服務(wù)是本項目的入口, 盡管所提供的功能很簡單

下單. 即生成預(yù)訂單, 為了更好地測試TCC功能, 在下單時就通過Feign向服務(wù)account與product發(fā)起預(yù)留資源請求, 并且記錄入庫

確認訂單. 確認訂單時根據(jù)訂單ID從庫中獲取訂單, 并獲取預(yù)留資源確認的URI, 交由服務(wù)tcc統(tǒng)一進行確認, 如果發(fā)生沖突即記錄入庫, 等待人工處理

與其他服務(wù)進行通訊, 我們選擇使用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 定義一個標準的, 與語言無關(guān)的接口, 使人和計算機在看不到源碼或者看不到文檔或者不能通過網(wǎng)絡(luò)流量檢測的情況下能發(fā)現(xiàn)和理解各種服務(wù)的功能. 當服務(wù)通過Swagger定義, 消費者就能與遠程的服務(wù)互動通過少量的實現(xiàn)邏輯. 類似于低級編程接口, Swagger去掉了調(diào)用服務(wù)時的很多猜測.

運行

Docker Compose運行

在項目根路徑下執(zhí)行腳本build.sh, 該腳本會執(zhí)行Maven的打包操作, 并會迭代目錄下的*-compose.yml進行容器構(gòu)建

構(gòu)建完成后需要按照指定的順序啟動

啟動MySQL, RabbitMQ等基礎(chǔ)組件

?? solar git:(feature/cleanup) ? docker-compose -f infrastructure-compose.yml up -d

1

啟動Eureka Server與Config Server

?? solar git:(feature/cleanup) ? docker-compose -f basic-ms-compose.yml up -d

1

啟動監(jiān)控服務(wù)

?? solar git:(feature/cleanup) ? docker-compose -f monitor-ms-compose.yml up -d

1

啟動業(yè)務(wù)服務(wù)

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

1

IDE運行

因為程序本身按照Docker啟動, 所以對于hostname需要在hosts文件中設(shè)置正確才能正常運行

## 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


根據(jù)依賴關(guān)系, 程序最好按照以下的順序執(zhí)行

docker mysql > docker rabbitmq > eureka server > config server > zipkin server > 其他微服務(wù)

示例

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

POST http://localhost:7291/order/api/v1/orders

Content-Type: application/json;charset=UTF-8

{

? "product_id": 7,

? "user_id": 1

}


成功后我們將得到預(yù)訂單的結(jié)果

{

? "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

}


此時我們再確認訂單

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

POST http://localhost:7291/order/api/v1/orders/confirmation

Content-Type: application/json;charset=UTF-8

{

? "order_id": 15

}


如果成功確認則返回如下結(jié)果

{

? "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事務(wù), 當然你也可以測試超時和沖突的情況, 這里就不再贅述

拓展

使用Gitlab作為遠程配置倉庫

本例中默認使用Github或GitOsc中的公開倉庫, 出于自定義的需要, 我們可以在本地構(gòu)建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中g(shù)it倉庫的相關(guān)驗證等參數(shù)即可

結(jié)語

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

在此我向大家推薦一個架構(gòu)學習交流群。交流學習群號:938837867 暗號:555 里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容