REST微服务的分布式事务实现-使用Spring Cloud的fallback模式

还未完成,后续更新会在这里注释。

Fallback是Spring Cloud Netflix框架套件中的Hystrix使用的,用于在出错时候进行的应急措施,我们可以用它来实现在出错的时候来进行回退操作。如果大家对Spring Cloud Netflix不太了解,可以查阅另一个篇文章Spring Cloud netflix概览和架构设计,来对Spring Cloud Netflix框架的各个组件做一个了解。在这篇文章中,我们将介绍如何使用Hyxtrix的Fallback来实现分布式事务,并提供一个完整的实例来展示这种方法。

Hystrix

首先来说一下Hystrix,Hystrix是Spring Cloud Netflix套件中的一个功能组件,我们可以在现有的基于Spring Cloud的微服务应用中使用Hystrix来提供额外的功能。它提供的功能有:

  • 运行时间统计。我们只需要在我们的某一个方法上加上@HystrixCommand的标签,这个方法的执行就能够被统计,包括运行测试、时间等。
  • 可视化显示运行统计信息的web应用。Hystrix提供了一个Hystrix Dashboard,它嵌入了一个web应用,可以直接查看被监控的方法和服务的执行情况。
  • 断路器功能。在Spring Cloud的微服务框架中,会有很多的服务间调用,包括代理转发请求到服务,服务间的调用等,由于网络等的原因,这些调用有很多不可控因素。Hystrix的断路器功能就是在某个服务发生错误的时候,避免由于一直等待等问题,而造成整个系统的瘫痪。
  • 出错时的Fallback退回操作。我们在用@HystrixCommand监控一个方法的时候,除了能够监控这个方法的执行,还能够设置一个fallback方法,用于在这个方法出错的时候来调用。这一般用于执行出错时的回退操作,特别是在服务间调用的时候。

下面就是Hystrix提供的Dashboard页面:
Hystrix Dashboard

Hystrix dashboard为我们提供了每个Command的调用次数及其历史,还有调用时间等的统计值。

我们在基于Spring Cloud的微服务中实现分布式事务的时候,就可以使用Hystrix的fallback方法来实现出错时的回退功能。

Hystrix fallback

我们在用@HystrixCommand的时候,可以加fallback方法,这样,Hystrix断路器会监控这个方法的执行,在超时、发生异常等情况下就会调用相应的fallback设置的方法。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@RequestMapping("/api/order")
public class OrderResource {
@HystrixCommand(fallbackMethod = "createFallback")
@PostMapping(value = "/")
public Order buyTicket(@RequestBody Order order) {
return orderService.create(order);
}
private Order createFallback(Order order) {
return orderService.createFallback(order);
}
}

在这个类中,create方法上加了@HystrixCommand标签,当这个方法在执行的时候发生任何异常,createFallback方法就会被调用,而且,调用的时候,会使用相同的对象作为参数。

下面的流程图就描述了使用@Hystrix执行一个Service方法的大致流程:
hystrix-flow.png

Feign Client

在基于Spring Cloud的微服务系统中,服务之间需要调用的时候,一种常用的方式是使用Feign客户端。
首先,定义一个接口,并使用@FeignClient标签。例如下面这个用于访问用户服务的接口:

1
2
3
4
5
6
@FeignClient(value = "user", path = "/api/user/comp")
public interface UserCompositeService {
@PostMapping(value = "/pay")
void payForOrder(@RequestBody UserPayDTO pay);
}

在这个接口中用@FeignClient标签标记它是一个Feign客户端,这个客户端对应的是user模块,URL路径的前缀是/api/user/comp。它里面定义了一个方法payForOrder,也就是支付订单的方法,这个方法是接受POST请求,相对路径是’/pay’。

然后,我们需要在User模块提供这个路径的响应。一般,我会把服务间调用的url定义成/api/user/comp/**这样,对应的web接口所在的类名叫UserCompositeResource这样的名称,这样就便于对服务间调用进行统一的权限、日志等控制。下面就是UserCompositeResource类的内容:

1
2
3
4
5
6
7
8
9
10
11
@RestController
@RequestMapping("/api/user/comp")
public class UserCompositeResource implements UserCompositeService {
@Override
@PostMapping(value = "/pay")
@Transactional
public void payForOrder(@RequestBody UserPayDTO payDTO) {
// do your business here
}
}

我们一般是把所有的服务间调用的Feign接口文件放在一个单独的项目里,打包方式是jar,然后其它需要使用这个接口的项目就依赖这个接口项目。然后就像上面这样实现这个接口。这样能较好的控制接口和实现的统一。但是,这样就需要设计好各个项目之间的依赖问题。

然后,在调用的时候,直接在需要的地方注入UserCompositeService使用即可:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class TicketOrderService {
@Inject
UserCompositeService userCompositeService;
public Order buy(OrderDTO dto) {
// create Order
userCompositeService.payForOrder(payDTO);
// do other...
}
}

当Spring在容器中初始化这个TicketOrderService类的实例的时候,对成员变量userCompositeService,它知道它是一个Feign Client的接口,Spring就会自动创建一个类,实现这个接口,并且根据接口的定义,和接口中方法的定义,实现接口的方法。实现出来的方法,实际上就是通过RestTemplate调用相应的Rest接口,将返回的结果转换成相应的类型。

所以,我们使用Feign Client来实现服务间调用,就跟调用一般的方法一样简单。而且,在服务调用者和服务提供者之间使用同一个接口,也能很容易控制服务间接口。

Ribbon与Load balance

我们使用Feign Client作为服务间调用的接口,那么,这个接口下面又是如何找到相应的服务和该服务的实例进行调用的呢?在Spring Cloud Netflix中,由Ribbon提供负载均衡功能,而负载均衡的服务器列表,是从Eureka服务器获得。当我们使用Feign Client的时候,也是使用Ribbon提供的负载均衡服务。

Feign对Hystrix的支持

虽然Feign和Hystrix是两个独立的功能模块,但是只要在项目依赖里面包含Hystrix的库,那么Feign就会自动使用Hystrix来封装相应的调用方法。

@FeignClient标签中也包含fallback的配置。它跟Hystrix的fallback配置不同,Hystrix的fallback配置是配置一个方法为出错时的调用方式,而@FeignClient里面的fallback配置的是一个类,这个类必须继承这个FeignClient的接口。

?????
????
???
??

实例

该实例包括完整的微服务组件,包括代理、服务注册中心、和3个微服务,服务之间使用Feign Client通讯,其组件图如下。
spring-cloud-components.png

然后,我们的业务场景,是一个用户购票的流程,流程图如下:
ticket-process.png

  1. 用户发起购票请求,网关将该请求转发给order服务
  2. order服务在一个事务里面创建订单等信息、然后调用user服务进行扣费、然后调用ticket服务转移票。
  3. 方法完成,给用户返回。

创建项目

Spring提供了一个在线工具,可以用来创建spring boot项目。我们用这个工具来创建实例中的几个微服务的项目:

  • proxy
    Proxy代理服务器需要使用ZUUL, Eureka Discovery。
  • registry
    然后是服务注册服务,使用Eureka Server。
  • order/user/ticket service
    然后创建3个服务项目,都需要使用Web, JPA, H2, Eureka Discovery, Hystrix。

使用spring boot,可以让我们免去很多配置的烦恼,很多情况下,只要将需要的库加到pom里(只要是spring提供了集成),剩下的基本上就是自动配置。 例如我们要使用数据库,在开发环境,如果不想在本地使用数据库,就使用H2的内存数据库,将H2的库加到依赖里,然后再使用JPA框架,如Spring-Data,就能够自动配置DataSource,自动创建数据库。再比如MQ,我们如果要使用JMS,只需要添加ActiveMQ的库,就会有一个基于内存的MQ共我们使用。然后我们就可以在需要的时候配置外部的数据库或MQ,在正式环境使用。

注意

HystrixCommand和Transactional公用

一般情况下,在Spring中,一个方法使用@Transactional标签后,方法内出现任何错误,都会数据库的操作都会回退,但是,如果把它和@HystrixCommand公用就会不一样了。
我们知道,Spring使用代理模式实现添加了事务标签的方法,也就是在这个方法调用的前后添加事务控制代码,通过try/catch实现出错的时候回退操作。但是,如果你同时使用了@HystrixCommand标签,它也会通过代理把方法内容封装一下,来实现监控运行情况,和实现断路器功能等。而且默认会在独立的线程里面执行方法,这样,就跟外面的启用的事务不在一个线程里,所以事务就不会起作用。

对于这个,一个简单的办法就是把这两个标签拆开,写到2个service类里,由于@HystrixCommand是在事务里面执行,那就先调用hystrix的:

1
2
3
4
5
6
7
8
9
@Service
public class ServiceA {
@Inject ServiceAWithTransaction serviceTrans;
@HystrixCommand
public void callMyMethod() {
serviceTrans.callMyMethod();
}
}

然后在另一个service里面用事务

1
2
3
4
5
6
7
8
@Service
public class ServiceAWithTransaction {
@Transactional
public void callMyMethod() {
// ....
}
}

坚持原创技术分享,您的支持将鼓励我继续创作!