在 SpringCloud 微服务项目中,我们有了 Eureka 做服务的注册中心,进行服务的注册于发现和服务治理。使得我们可以摒弃硬编码式的 ip:端口 + 映射路径 来发送请求。我们有了 Feign 作为声明式服务调用组件,可以像调用本地服务一样来调用远程服务。基于 Ribbon 我们又实现了客户端负载均衡,轻松的在集群环境下选取合适的服务提供者。这样看来我们的微服务貌似很完善了。是这样的吗?
并非如此,想想我们在编码过程中进行的健壮性检查。类比一下服务与服务调用是否也应该更加健壮一些呢?我们目前的微服务在正常运行的时候是没有问题的,但若是某个偏下游的服务提供者不可用,造成服务积压,接连引起上游的服务消费者宕机,引法雪崩效应。是不是就显得我们的微服务不堪一击呢?因此我们需要一个组件来解决这样的问题,前辈们参考生活中保险丝的原理做出了微服务中的保险丝-Hystrix熔断器。下面让我们来一起使用一下
声明:本文首发于博客园,作者:后青春期的Keats;地址:https://www.cnblogs.com/keatsCoder/ 转载请注明,谢谢!
Hystrix主要实现了下面的功能:
<!-- 熔断器 hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
/**
* getUserByAge 方法 Hystrix 回退方法
* @param age
* @return
*/
public User getUserByAgeFallBack(Integer age){
User user = new User();
user.setName("默认用户");
user.setAge(age);
return user;
}
@HystrixCommand(fallbackMethod = "getUserByAgeFallBack")
测试:将服务提供方的代码打断点。调用服务消费方,会发现返回了默认用户
需要注意:
com.netflix.hystrix.contrib.javanica.utils.MethodProvider
public FallbackMethod find(Class<?> enclosingType, Method commandMethod, boolean extended) {
// 首先判断该方法的 HystrixCommand 注解上有没有 defaultFallback / fallbackMethod 配置回退方法名称
if (this.canHandle(enclosingType, commandMethod)) {
// 调用 doFind 方法
return this.doFind(enclosingType, commandMethod, extended);
} else {
// 没有配置的化就接着下一个判断
return this.next != null ? this.next.find(enclosingType, commandMethod, extended) : FallbackMethod.ABSENT;
}
}
find 方法在用户所请求的方法的 HystrixCommand 注解上有用 defaultFallback / fallbackMethod 配置回退方法名称的时候,会调用 doFind 方法来寻找回退方法。该方法的参数有两个,enclosingType 是用户所请求的方法的类字节码文件,commandMethod 是用户所请求的方法
首先通过 this.getFallbackName 获取回退方法名称,接着通过获取 commandMethod 的参数类型们
接着分两种情况:
private FallbackMethod doFind(Class<?> enclosingType, Method commandMethod, boolean extended) {
String name = this.getFallbackName(enclosingType, commandMethod);
Class<?>[] fallbackParameterTypes = null;
if (this.isDefault()) {
fallbackParameterTypes = new Class[0];
} else {
fallbackParameterTypes = commandMethod.getParameterTypes();
}
if (extended && fallbackParameterTypes[fallbackParameterTypes.length - 1] == Throwable.class) {
fallbackParameterTypes = (Class[])ArrayUtils.remove(fallbackParameterTypes, fallbackParameterTypes.length - 1);
}
Class<?>[] extendedFallbackParameterTypes = (Class[])Arrays.copyOf(fallbackParameterTypes, fallbackParameterTypes.length + 1);
extendedFallbackParameterTypes[fallbackParameterTypes.length] = Throwable.class;
Optional<Method> exFallbackMethod = MethodProvider.getMethod(enclosingType, name, extendedFallbackParameterTypes);
Optional<Method> fMethod = MethodProvider.getMethod(enclosingType, name, fallbackParameterTypes);
Method method = (Method)exFallbackMethod.or(fMethod).orNull();
if (method == null) {
throw new FallbackDefinitionException("fallback method wasn‘t found: " + name + "(" + Arrays.toString(fallbackParameterTypes) + ")");
} else {
return new FallbackMethod(method, exFallbackMethod.isPresent(), this.isDefault());
}
}
由源码可以得到结论:回退方法要么参数列表和原始方法相同,要么加且仅加一个类型为 Throwable 的参数。其他的都不行
设置:feign.hystrix.enabled: true
Feign 客户端接口上的 @FeignClient 添加 fallback 属性,指向回退类
回退类实现客户端接口
# feign的配置
feign:
hystrix:
enabled: true # 打开 feign 的 hystrix 支持
注意回退类加上 @Component 接口,避免因为 Spring 容器找不到该类而启动报错
// Feign 客户端接口上的 @FeignClient 添加 fallback 属性,指向回退类
@FeignClient(name = "SERVICE-PROVIDER", fallback = UserServiceFeignClientFallBack.class)
public interface UserServiceFeignClient {
@GetMapping("/api/v1/user/{age}")
User getUser(@PathVariable("age") Integer age);
/**
* 用户列表
* @return
*/
@GetMapping("/api/v1/users")
List<User> getUsers();
}
// 回退类实现客户端接口
@Component
public class UserServiceFeignClientFallBack implements UserServiceFeignClient {
@Override
public User getUser(Integer age) {
return null;
}
@Override
public List<User> getUsers() {
return null;
}
}
当采用 Feign 客户端来实现回退的时候,前面的捕捉异常方法就不起作用了,那我们应该如何来处理异常呢?可以使用 @FeignClient 的 fallbackFactory 属性
@FeignClient(name = "SERVICE-PROVIDER", fallbackFactory = UserServiceFallbackFactory.class)
@Component
@Slf4j
public class UserServiceFallbackFactory implements FallbackFactory<UserServiceFeignClient> {
@Override
public UserServiceFeignClient create(Throwable t) {
// 日志最好写在各个 fallback 方法中,而不要直接卸载 create方法中
// 否则引用启动时就会打印该日志
return new UserServiceFeignClient() {
@Override
public User getUser(Integer age) {
log.info("调用User服务提供者失败", t);
User user = new User();
user.setName("默认用户");
user.setAge(age);
return user;
}
@Override
public List<User> getUsers() {
return null;
}
};
}
}
注意: **fallback 和 fallbackFactory 属性同时存在时,fallback 的优先级更高。因此开发中如果需要处理异常,只需配置 fallbackFactory 属性即可 **
在某些场景下,当发生业务异常时,我们并不想触发 fallback。例如业务中判断年龄 age 不能小于 1,否则抛出异常
if(age < 1){
throw new KeatsException(ExceptionEnum.NUM_LESS_THAN_MIN);
}
这时 Hystrix 会捕捉到异常然后执行 fallback 方法,我们可以通过下面两个方法来避免:
只要打开 feign 的 hystrix 支持开关,feign 就会使用断路器包裹 feign 客户端的所有方法,但很多场景并不需要这样。该如何禁用呢?
@Configuration
public class FeignDisableHystrixConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}
@FeignClient(name = "SERVICE-PROVIDER", configuration = {FeignDisableHystrixConfiguration.class})
本博客中所有示例代码都已上传至 github仓库: https://github.com/keatsCoder/cloud-cli
参考文献:《Spring Cloud与Docker 微服务架构实战》 --- 周立
码字不易,如果你觉得读完以后有收获,不妨点个推荐让更多的人看到吧!
原文:https://www.cnblogs.com/keatsCoder/p/13028727.html