Fallback是Spring Cloud Netflix框架套件中的Hystrix使用的,用于在出错时候进行的应急措施,我们可以用它来实现在出错的时候来进行回退操作。如果大家对Spring Cloud Netflix不太了解,可以查阅另一个篇文章Spring Cloud netflix概览和架构设计,来对Spring Cloud Netflix框架的各个组件做一个了解。在这篇文章中,我们将介绍如何使用Hyxtrix的Fallback来实现分布式事务,并提供一个完整的实例来展示这种方法。
首先来说一下Hystrix,Hystrix是Spring Cloud Netflix套件中的一个功能组件,我们可以在现有的基于Spring Cloud的微服务应用中使用Hystrix来提供额外的功能。它提供的功能有:
@HystrixCommand
的标签,这个方法的执行就能够被统计,包括运行测试、时间等。@HystrixCommand
监控一个方法的时候,除了能够监控这个方法的执行,还能够设置一个fallback
方法,用于在这个方法出错的时候来调用。这一般用于执行出错时的回退操作,特别是在服务间调用的时候。下面就是Hystrix提供的Dashboard页面:
Hystrix Dashboard
Hystrix dashboard为我们提供了每个Command的调用次数及其历史,还有调用时间等的统计值。
我们在基于Spring Cloud的微服务中实现分布式事务的时候,就可以使用Hystrix的fallback方法来实现出错时的回退功能。
我们在用@HystrixCommand
的时候,可以加fallback方法,这样,Hystrix断路器会监控这个方法的执行,在超时、发生异常等情况下就会调用相应的fallback
设置的方法。例如:
@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方法的大致流程:
在基于Spring Cloud的微服务系统中,服务之间需要调用的时候,一种常用的方式是使用Feign客户端。
首先,定义一个接口,并使用@FeignClient
标签。例如下面这个用于访问用户服务的接口:
@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
类的内容:
@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
使用即可:
@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
来实现服务间调用,就跟调用一般的方法一样简单。而且,在服务调用者和服务提供者之间使用同一个接口,也能很容易控制服务间接口。
我们使用Feign Client作为服务间调用的接口,那么,这个接口下面又是如何找到相应的服务和该服务的实例进行调用的呢?在Spring Cloud Netflix中,由Ribbon提供负载均衡功能,而负载均衡的服务器列表,是从Eureka服务器获得。当我们使用Feign Client的时候,也是使用Ribbon提供的负载均衡服务。
虽然Feign和Hystrix是两个独立的功能模块,但是只要在项目依赖里面包含Hystrix的库,那么Feign就会自动使用Hystrix来封装相应的调用方法。
@FeignClient
标签中也包含fallback
的配置。它跟Hystrix的fallback
配置不同,Hystrix的fallback
配置是配置一个方法为出错时的调用方式,而@FeignClient
里面的fallback
配置的是一个类,这个类必须继承这个FeignClient的接口。
????? ???? ??? ?? ?
该实例包括完整的微服务组件,包括代理、服务注册中心、和3个微服务,服务之间使用Feign Client
通讯,其组件图如下。
然后,我们的业务场景,是一个用户购票的流程,流程图如下:
Spring提供了一个在线工具,可以用来创建spring boot项目。我们用这个工具来创建实例中的几个微服务的项目:
使用spring boot,可以让我们免去很多配置的烦恼,很多情况下,只要将需要的库加到pom里(只要是spring提供了集成),剩下的基本上就是自动配置。 例如我们要使用数据库,在开发环境,如果不想在本地使用数据库,就使用H2的内存数据库,将H2的库加到依赖里,然后再使用JPA框架,如Spring-Data,就能够自动配置DataSource,自动创建数据库。再比如MQ,我们如果要使用JMS,只需要添加ActiveMQ的库,就会有一个基于内存的MQ共我们使用。然后我们就可以在需要的时候配置外部的数据库或MQ,在正式环境使用。
一般情况下,在Spring中,一个方法使用@Transactional
标签后,方法内出现任何错误,都会数据库的操作都会回退,但是,如果把它和@HystrixCommand
公用就会不一样了。
我们知道,Spring使用代理模式实现添加了事务标签的方法,也就是在这个方法调用的前后添加事务控制代码,通过try/catch实现出错的时候回退操作。但是,如果你同时使用了@HystrixCommand
标签,它也会通过代理把方法内容封装一下,来实现监控运行情况,和实现断路器功能等。而且默认会在独立的线程里面执行方法,这样,就跟外面的启用的事务不在一个线程里,所以事务就不会起作用。
对于这个,一个简单的办法就是把这两个标签拆开,写到2个service类里,由于@HystrixCommand
是在事务里面执行,那就先调用hystrix的:
@Service
public class ServiceA {
@Inject ServiceAWithTransaction serviceTrans;
@HystrixCommand
public void callMyMethod() {
serviceTrans.callMyMethod();
}
}
然后在另一个service里面用事务
@Service
public class ServiceAWithTransaction {
@Transactional
public void callMyMethod() {
// ....
}
}