本文转自 微服务 开源项目 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] 码云地址,