前言

微服务与分步式系统架构里的事务问题一直就是痛点,在高并发与高吞吐下,伴随网络的不确定性,数据很难达到强一致性,业界一直没有完美的解决方案。基于分布式CAP理论,只能选择放弃强一致性,追求可用性,提出柔性事务、最终一致性。微服务等分布式架构中,要实现最终一致性,只能采用事务补偿方式(可能还需要手动补偿)。分布式事务在使用时应该尽量少用或不用,在不得以的情况下。最好定下分布式事务的开发规范来,对开发人员使用进行约束、降低使用分布式事务开发难度。

微服务技术平台使用Spring Cloud.

前期对分布式事务研究:TCC、Saga

一开始参考很多的开源方案:只是非常简单了解。从项目活跃度、开发者和项目源码、项目文档都不太满意。

代码有点多,有点乱!可以到浏览器里看,这是下面所有的源码地址:https://github.com/zuolang/servicecomb-pack.git

基础知识

这个例子需要SpringCloud的一些基础知识。

  • TC:Transaction Coordinator事务协调器
  • TM: Transaction Manager事务管理
  • RM:Resource Manager资源管理

RM是数据库这一端;TC指的统一管理协议各个分支事务状态;TM是在应用端:tx.begin() commit()

ServiceComb概述

Apache ServiceComb Pack 是一个微服务应用的数据最终一致性解决方案。架构ServiceComb Pack 架构是由 Alpha 和 Omega组成,其中:

  • alpha充当协调者的角色要单独部署,主要负责对事务进行管理和协调。
  • omega是微服务中内嵌的一个agent,负责对调用请求进行拦截并向alpha上报事务事件。

下图展示了alpha, omega以及微服务三者的关系:

1312aa5365bca192c2a566f4b51bc624.png

官方图片

例子:运行源码里Demo:Saga-Spring-Demo

这是所有的实验代码:https://github.com/zuolang/servicecomb-pack.git

这里是我在原来项目的基础上改的代码,后期还有继续使用。为什么要改呢?原来的例子运行都直接用命令行启动,动态传参数。这种方式不适合初学者,每次改完还编译,非常不方便。出错了也好不调试。在原来的基础上增加了服务注册中心

  • 使用eureka使用服务注册中心
  • Alpha启动并且注册到eureka
  • 启动Demo的三服务booking car hotel,并且注册到eureka
2c8c7200af40b8c44fb2e241e18af131.png

Saga流程

图中省略了注册中心

eureka注册中心:

通过:https://start.spring.io/ 建一个maven SpringBoot项目

pom.xmlorg.springframework.boot spring-boot-starter-actuatororg.springframework.boot spring-boot-starter-weborg.springframework.cloud spring-cloud-starter-netflix-eureka-clientorg.springframework.cloud spring-cloud-starter-netflix-eureka-serverorg.springframework.boot spring-boot-devtools runtimeorg.springframework.boot spring-boot-starter-test test

Spring项目配置文件 application.yaml

spring: application: name: eureka_service1 #服务名称,用于在服务注册中心查找 profiles: eureka1server: port: 8761eureka: instance: hostname: eureka1 instance-id: 1 #在服务中心需要唯一性 client: register-with-eureka: true #是否把自己注册 fetch-registry: false #是否会读取注册中心:中作为服务提供者,可以配置false service-url: defaultZone: http://127.0.0.1:8761/eureka #注册的地址

使用@EnableEurekaServer开启EurekaServer

@SpringBootApplication@EnableEurekaServerpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}

启动 http://127.0.0.1:8761 检查eureka服务是否启动成功

运行Alpha-0.4.x分支

  • 准备数据库
docker run --name=mysql -d -e "MYSQL_ROOT_PASSWORD=1234" -e "MYSQL_DATABASE=saga" -e "MYSQL_USER=root" -e "MYSQL_PASSWORD=1234" -p 3306:3306 mysql/mysql-server:5.7
  • 代码编译:这里可能会出现各种不一样问题

mvn clean package -Dmaven.test.skip=true

如果docker失败,可以跳过docker -Dgpg.skip跳过包的签名校验

mvn clean install -DskipTests -Prelease -Pspring-boot-2 -Pmysql -Pspring-cloud-eureka -P -docker -Dgpg.skip

  • 启动Alpha协调者

在Idea上直接启动

1.alpha-server是一个SpringBoot程序,AlphaApplication SpringBoot启动的main方法

  • 默认AlphaServer pom.xml没mysql jdbc驱动
 mysql mysql-connector-java 5.1.30org.springframework.cloud spring-cloud-starter-netflix-eureka-client ${spring.cloud.version}

加上最好检查在项目的dependency里上是否加上

  • 配置数据库url username password
  • eureka客户端配置
 eureka: client: enabled: true service-url: defaultZone: http://127.0.0.1:8761/eureka instance: metadataMap: servicecomb-alpha-server-port: ${alpha.server.port} servicecomb-alpha-server: ${alpha.server.host}:${alpha.server.port} prefer-ip-address: true hostname: 127.0.0.1

main JVM参数:-Dspring.profiles.active=mysql

application.yaml中使用了profile通过配置参数指定数据源

  • 先启动eureka服务作为服务注册中心,否则会启动会因连接不eureka出错。
  • 在AlphaApplication类开启@EnableEurekaClient
  • 右键run Java Application启动main方法,如果启动失败请从1.1再检查
  • http://localhost:8761 eureka确认Alpha注册成功

2、命令行下启动

  • Spring cloud saga booking car hotel DEMO代码:
  • 增加eureka client到saga-spring-demo 的pom中
org.springframework.cloud spring-cloud-starter-netflix-eureka-client ${spring.cloud.version}

给booking car hotel配置eureka注册中心

eureka: client: enabled: true service-url: defaultZone: http://192.168.0.5:8761/eureka //eureka服务注册中心alpha: cluster: address: 127.0.0.1:8081 //alpha服务的地址:omega: enable: true //ormega开关server:  port: 8092 //Spring boot服务端中@SpringBootApplication@EnableEurekaClientpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }} @Bean(name = "restTemplate") @LoadBalanced RestTemplate initRestTemplate(@Qualifier("omegaRestTemplate") RestTemplate omegaRestTemplate) { return omegaRestTemplate; }//booking 调用hotel car的微服务 @Autowired @Qualifier("restTemplate") private RestTemplate template; @SagaStart @GetMapping("/booking/{name}/{rooms}/{cars}") public String order(@PathVariable String name, @PathVariable Integer rooms, @PathVariable Integer cars) { template.getForEntity("http://CAR/order/"+name+"/"+cars, null, String.class, name, cars); template.getForEntity( "http://HOTEL/order/"+name+"/"+rooms, null, String.class, name, rooms); return name + " booking " + rooms + " rooms and " + cars + " cars OK"; }//service时@Compensable(compensationMethod = "cancel") cancel是同一个类的方法与本法参数是一样的@Serviceclass CarBookingService { private Map bookings = new ConcurrentHashMap<>(); @Compensable(compensationMethod = "cancel") void order(CarBooking booking) { booking.confirm(); bookings.put(booking.getId(), booking); } void cancel(CarBooking booking) { Integer id = booking.getId(); if (bookings.containsKey(id)) { bookings.get(id).cancel(); } } }

http://127.0.0.1:8761 检查eureka上的服务注册是完全

f49a83061e306e9678357b87edbd634e.png

http://localhost:8091/booking/room8848/2/3

room booking 2 rooms and 3 cars OK

tcc-spring-demo

由tcc-ordering tcc-inventory tcc-payment

给三个服务都注册到服务中心上

给三个服务配置不同的端口,配置alpha.cluster.address配置Alpha的服务地址,开启Omega

eureka: client: enabled: true service-url: defaultZone: http://127.0.0.1:8761/eurekaalpha: cluster: address: 127.0.0.1:8081omega: enable: trueserver:  port: 8092 @TccStart @GetMapping("/order/{userName}/{productName}/{productUnit}/{unitPrice}") @ResponseBody public String order( @PathVariable String userName, @PathVariable String productName, @PathVariable Integer productUnit, @PathVariable Integer unitPrice) { restTemplate.getForEntity("http://INVENTORY/order/"+userName+"/"+productName+"/"+productUnit,null, String.class, userName, productName, productUnit); restTemplate.getForEntity("http://PAYMENT/pay/"+userName+"/"+productUnit*unitPrice, null, String.class, userName, amount); return userName + " ordering " + productName + " with " + productUnit + " OK"; }  @Participate(confirmMethod = "confirm
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐