本文轉自 微服務 開源項目 Apache ServiceComb (incubating) 的官方博客:java
http://servicecomb.incubator.apache.org/cn/docs/saga_pack_design/git
傳統的單體應用的微服務化改造過程當中大多會面臨數據庫拆分,故而原來由數據庫保證的數據一致性也必定面臨從新設計和實現,此時須要引入分佈式數據一致性方案來解決。常見的解決方案主要有2PC,TCC,事件驅動等,而在微服務開源項目 ServiceComb中提出並實現了使用Saga[1]來解決微服務的數據一致性難題,不一樣方案的對比可參考《ServiceComb中的數據最終一致性方案》[2]一文。Saga是一個數據最終一致性的解決方案,它容許咱們成功地執行全部事務,或在任何事務失敗的狀況下,補償已成功的事務,並提供了ACID[3]中ACD的保證(因爲事務是交錯執行的,可能會看到其餘事務的部分結果,所以不能知足隔離性要求)。所以,Saga適用於如下跨服務的事務場景:github
新年新氣象,Apache ServiceComb(incubating) Saga[4](如下簡稱Saga)進行了演進。相對於上一版[2],新演進的設計主要有如下優點:spring
Saga演進後的架構,以下圖所示,主要包含兩個組件,即alpha和omega,其中:sql
omega是微服務中內嵌的一個agent,負責向alpha上報事務狀態並與其它omega直接傳遞事務上下文信息。其中,每一個服務的事務上下文包括:docker
以下圖所示,分佈式事務與用於分佈式調用鏈追蹤的zipkin[5]的處理流程很相似,在服務提供方,omega會將請求攔截並從中提取請求信息中的全局事務id做爲其自身的全局事務id(即Saga事件id),並將請求中的本地事務id做爲其父事務id,且使用新生成的id做爲本地事務id;在服務消費方,omega會將請求攔截並往其中添加當前的全局事務id和本地事務id。經過服務提供方和服務消費方的這種協做處理,子事務能鏈接起來造成一個完整的全局事務。數據庫
omega在預處理階段會先向alpha發送事務開始的事件,在後處理階段會再向alpha發送事務結束的事件。alpha在收到事件後會進行持久化的存儲。所以,每一個成功的子事務都有一一對應的開始及結束事件。apache
在omega啓動時會向alpha註冊,使得異常或者超時場景下,alpha能經過回調向omega發送重試或補償的命令和相應的調用參數,從而確保全局事務的一致性。json
全局事務開始前omega會先向alpha發送全局事務開始的事件,並在全部子事務完成時向alpha發送全局事務結束的事件。而每一個子事務在執行前也會向alpha發送事務開始的事件,在成功執行後,會向alpha發送事務結束的事件。子事務間經過全局事務id鏈接在一塊兒,但也因本地事務id而有所區分。所以,在成功場景下,每一個開始的事件都會有對應的結束事件。bash
在子事務執行期間拋出異常時,omega會向alpha上報aborted事件,而後alpha會向該全局事務的其它已完成的子事務發送補償指令,確保最終同一全局事務下的全部子事務要麼都成功,要麼都回滾。因爲事務中沒有明確指定全局事務中的參與者,所以,alpha的掃描器會按期查詢事件表並找出已完成全部補償子事務的全局事務,而後對這些全局事務添加全局事務結束事件以保證事務的完整性。
alpha的掃描器會按期掃描正在處理的事件狀態,若發現事件超時,則會記錄相應的aborted事件,而後alpha會向該全局事務的其它已完成的子事務發送補償指令來恢復至事務開始前的狀態。
Saga的使用主要涵蓋兩方面,alpha的啓動及omega的使用。
alpha啓動前須要先運行數據庫PostgreSQL:
docker run -d -e "POSTGRES_DB=saga" -e "POSTGRES_USER=saga" -e "POSTGRES_PASSWORD=password" -p 5432:5432 postgres
在確保數據庫正常啓動後,便可運行alpha:
docker run -d -p 8090:8090 \ -e "JAVA_OPTS=-Dspring.profiles.active=prd" \ -e "spring.datasource.url=jdbc:postgresql://{docker.host.address}:5432/saga?useSSL=false" \ alpha-server:0.1.0
omega的使用很簡單,以一個簡化的轉帳業務爲例,同一筆轉帳要麼轉入和轉出都成功,要麼都失敗。在這樣一個業務中引入Saga,只需簡單幾步便可:
<dependency> <groupId>org.apache.servicecomb.saga</groupId> <artifactId>omega-spring-starter</artifactId> <version>0.1.0</version> </dependency> <dependency> <groupId>org.apache.servicecomb.saga</groupId> <artifactId>omega-transport-resttemplate</artifactId> <version>0.1.0</version> </dependency>
@EnableOmega
的註解來初始化omega的配置並與alpha創建鏈接。 @SpringBootApplication @EnableOmega public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Copy
2.2 在全局事務的起點添加 @SagaStart
的註解。
@SagaStart(timeout=10) public boolean transferMoney(String from, String to, int amount) { transferOut(from, amount); transferIn(to, amount); }
2.3 在子事務處添加 @Compensable
的註解並指明其對應的補償方法。其中,補償方法的形參列表需與子事務方法的形參列表保持一致。
@Compensable(timeout=5, compensationMethod="cancel") public boolean transferOut(String from, int amount) { repo.reduceBalanceByUsername(from, amount); } public boolean cancel(String from, int amount) { repo.addBalanceByUsername(from, amount); }
2.4 對轉入服務重複第2.3步便可。
2.5 在每一個服務的application.yaml中添加配置項,指明服務信息和alpha的地址信息:
spring: application: name: {application.name} alpha: cluster: address: {alpha.cluster.addresses}
目前Saga的實現還存在着不少有意思且有挑戰性的課題,如alpha的協調調度實現、冪等的實現及自動補償的實現等,歡迎有志之士與咱們攜手一塊兒解決數據一致性的難題,共同爲完善微服務生態貢獻本身的力量。
http://servicecomb.incubator.apache.org/cn/docs/join_the_community/
[1] Apache ServiceComb 官網,
http://servicecomb.incubator.apache.org/cn/
[2] 代碼參考項目地址 Apache ServiceComb(incubating) Saga,
https://github.com/apache/incubator-servicecomb-saga
[3] ServiceComb中的數據最終一致性方案,
http://servicecomb.incubator.apache.org/cn/docs/distributed_saga_1/
http://servicecomb.incubator.apache.org/cn/docs/distributed_saga_2/
http://servicecomb.incubator.apache.org/cn/docs/distributed_saga_3/
[4] Sagas, Hector Garcia-Molina & Kenneth Salem,
https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf
[5] ACID, Wikipedia,
https://en.wikipedia.org/wiki/ACID
[6] zipkin, zipkin,
https://github.com/openzipkin/zipkin
[7] 碼雲地址,