在之前的项目中我们已经实现了使用 Feign 调用远程接口,本章内容主要是借助 sentinel 实现 Feign 接口熔断器功能。
概述
首先我们看看不使用熔断器的情况下调用一个没有启动的服务会出现什么效果,然后再来看看使用 sentinel 熔断器后的效果。
如上,我们使用 order-service 中 FeignController
调用 account-service 中的接口,在没启用熔断器的情况下,接口会抛出 500 异常。
实现
使用 sentinel 实现熔断器很简单,简单几步即可。
-
定义 fallback 类,当熔断时返回默认数据
package com.javadaily.feign.fallback; @Slf4j public class AccountFeignFallback implements AccountFeign { @Setter private Throwable cause; @Override public ResultData<String> insert(AccountDTO accountDTO) { return ResultData.fail("接口熔断"); } @Override public ResultData<String> delete(String accountCode) { return ResultData.fail("接口熔断"); } @Override public ResultData<String> update(AccountDTO accountDTO) { return ResultData.fail("接口熔断"); } @Override public ResultData<AccountDTO> getByCode(String accountCode) { log.error("查询失败,接口异常" ,cause); AccountDTO account = new AccountDTO(); account.setAccountCode("000"); account.setAccountName("测试Feign"); return ResultData.success(account); } @Override public ResultData<String> reduce(String accountCode, BigDecimal amount) { return ResultData.fail("接口熔断"); } }
-
编写 FallbackFactory
@Component public class AccountFeignFallbackFactory implements FallbackFactory<AccountFeign> { @Override public AccountFeign create(Throwable throwable) { AccountFeignFallback accountFeignFallback = new AccountFeignFallback(); accountFeignFallback.setCause(throwable); return accountFeignFallback; } }
-
给 feign 接口指定熔断工厂
@FeignClient(name = "account-service",fallbackFactory = AccountFeignFallbackFactory.class) public interface AccountFeign { ... }
-
在消费中配置文件中开启熔断
feign: sentinel: enabled: true
经过以上四步我们就可以实现了接口的熔断,接下来重新启动 order-service 验证结果。系统居然无法正常启动!!!
堆栈信息如下:
2020-10-23 15:22:14,286 ERROR SpringApplication:826 - Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'feignController': Unsatisfied dependency expressed through field 'accountFeign'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.javadaily.feign.account.AccountFeign': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: No fallbackFactory instance of type class com.javadaily.feign.factory.AccountFeignFallbackFactory found for feign client account-service
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:643) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
问题解决
俗话说出现问题不可怕,可怕的是我们害怕出现问题,看上面的启动日志应该是无法找到 AccountFeignFallbackFactory
,接下来我们看一下 FeignClient 的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
...省略部分代码,保留关键代码...
/**
* Fallback class for the specified Feign client interface. The fallback class must
* implement the interface annotated by this annotation and be a valid spring bean.
* @return fallback class for the specified Feign client interface
*/
Class<?> fallback() default void.class;
...省略部分代码,保留关键代码...
}
注意看注释部分,说的是 Feign 的降级类必须实现该 Feign 接口并且必须是一个 Spring Bean。
出现错误的原因应该是降级类没有被注册成 Spring Bean。
大家都知道,在 SpringBoot 启动的时候会默认扫描主启动类所在的包以及子包进行 Bean 实例化。
项目中的 order-service 的主启动类位于 com.javadaily.order
,那么 order-service 项目启动时只会扫描到 com.javadaily.order
以及 com.javadaily.order
的子包,而 AccountFeignClient 的降级类 AccountFeignFallback
的包路径为 com.javadaily.feign.fallback
,这样就无法被 Spring 实例化,最终导致项目启动失败。
既然定位到了问题原因那就很好解决了,只要在启动类上配置 Feign 降级类的包路径即可。
@SpringBootApplication(scanBasePackages = {"com.javadaily.feign"})
加上这个配置后系统能正常启动,但是调用接口又返回如下的错误:
{
"timestamp": "2020-10-27T03:30:44.664+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/order/getAccount/jianzh5"
}
出现这个问题的原因是我们通过 scanBasePackages 配置 Feign 降级类的路径后自身的 Bean 无法实例化,所以我们还需要配置上我们自己项目的扫描路径,即:
@SpringBootApplication(scanBasePackages = {"com.javadaily.feign","com.javadaily.order"})
至此所有问题都解决,我们再次访问接口,当系统故障时返回我们默认结果。
小结
本章内容我们通过给 Feign 的接口加上熔断器,当实例故障时系统会返回默认数据。这样就不会出现当某一个服务不可用时导致他的消费者长时间等待,线程池耗尽,进而影响到其他服务的线程调用,这也是常说的 "雪崩效应"。
当然了实现过程中出现了一点小挫折,总结下来就是如果各位的 Feign 客户端是由消费者自己编写,位于消费者自己模块不会出现这个问题。如果是由生产者编写并提供则需要注意 Spring Bean 实例化的扫描路径,如果无法扫描实例化熔断类,只需要在启动类上通过 scanBasePackages
扫描到对应的路径即可。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于