在文章《小谈Springcloud中的几个主流熔断器》,我们介绍了SpingCloud架构中的几个主流熔断器,其中SpringCloud官方推荐的Resilience4j作为2020.x以后的新秀,远远没有hystrix有名,相关的文档也还不够多;今天这个文章就来讲讲SpringCloud如何使用Resilience4j实现熔断器;
resilience4j是一个轻量级、易于使用的容错库,其灵感来.NETflixesilience4j是一个轻量级、易于使用的容错库,其灵感来自Netflix Hystrix,但专为JAVA 8和函数式编程设计。
Resilience4j提供高阶函数(decorators)来增强任何功能接口、lambda表达式或方法引用,包括断路器、速率限制器、重试或舱壁。可以在任何函数接口、lambda表达式或方法引用上使用多个装饰器。
Resilience4j非常轻量级,不仅可以在SpringCloud进行使用,还可以直接在自己的Java程序里通过Resilience4j的API实现Rate Limite的功能; 这点也是笔者非常认同的一点; 支持和扩展上更为方便; 今天的这个文章,重点还是介绍如何在SpringCloud里使用Resilicen4j
通过pom.xml引入对Resilience4j的依赖
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot</artifactId>
</dependency>
配置resilience4j
在项目的Applicaiton.yml里添加SpringCloud里resilience4j的相关配置
resilience4j.circuitbreaker:
configs:
default:
registerHealthIndicator: true
slidingwindowsize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
recordExceptions:
- org.springframework.web.client.HttpServerErrorException
- java.util.concurrent.TimeoutException
- java.io.IOException
ignoreExceptions:
- com.kxblive.common.error.CustomException
shared:
slidingWindowSize: 100
permittedNumberOfCallsInHalfOpenState: 30
waitDurationInOpenState: 1s
failureRateThreshold: 50
eventConsumerBufferSize: 10
ignoreExceptions:
- com.kxblive.common.error.CustomException
instances:
backendA:
baseConfig: default
backendB:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 10
permittedNumberOfCallsInHalfOpenState: 3
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
recordFailurePredicate: com.kxblive.common.error.FailureExceptionPredicate
resilience4j.retry:
configs:
default:
maxAttempts: 3
waitDuration: 100
retryExceptions:
- org.springframework.web.client.HttpServerErrorException
- java.util.concurrent.TimeoutException
- java.io.IOException
ignoreExceptions:
- com.kxblive.common.error.CustomException
instances:
backendA:
baseConfig: default
backendB:
baseConfig: default
resilience4j.bulkhead:
configs:
default:
maxConcurrentCalls: 100
instances:
backendA:
maxConcurrentCalls: 10
backendB:
maxWaitDuration: 10ms
maxConcurrentCalls: 20
resilience4j.thread-pool-bulkhead:
configs:
default:
maxThreadPoolSize: 4
coreThreadPoolSize: 2
queueCapacity: 2
instances:
backendA:
baseConfig: default
backendB:
maxThreadPoolSize: 1
coreThreadPoolSize: 1
queueCapacity: 1
resilience4j.ratelimiter:
configs:
default:
registerHealthIndicator: false
limitForPeriod: 10
limitRefreshPeriod: 1s
timeoutDuration: 0
eventConsumerBufferSize: 100
instances:
backendA:
baseConfig: default
backendB:
limitForPeriod: 6
limitRefreshPeriod: 500ms
timeoutDuration: 3s
resilience4j.timelimiter:
configs:
default:
cancelRunningFuture: false
timeoutDuration: 2s
instances:
backendA:
baseConfig: default
backendB:
baseConfig: default
在这个配置中分别配置了circuitbeaker、retry、bulkhead、ratelimiter, timelimiter,thread-pool-bulkhead这些相关的熔断限流的相关项目。引入resilience4j-spring-boot集成了circuitbeaker、retry、bulkhead、ratelimiter几个模块的功能, 提供相关的实现;我们可以根据业务的实际需要对这些相关项目进行具体配置修改;
开发上和使用Hystrix大致上一样; 不过注意调整一下自己的地方
feign契约只能用feign的, 不能用SpringMVC的契约;所以不能使用默认的方式,必须通过autoconfiguraiton进行修改
@Bean
public Contract feignContract(){
return new Contract.Default();
}
/**
@Bean
public Contract feignContract(){
return new SpringMvcContract();
}
**/
由于修改了feign的constract方式;所以默认的feign注解方式,不能使用SpringMVC的方式,必须使用Feign的标准方式
// after Hystrix is removed from SpringCloud2021.0.1, the fallback is ineffective
//@FeignClient(name = "${codeman.service.name:codeman}", url = "${codeman.service.address:}", fallback = CodeManFallbackImpl.class)
public interface CodeManFeign extends CodeManService {
@RequestLine("GET /codeman/info/version")
public String getVersion();
@RequestLine("GET /codeman/info/author")
public String getAuthor();
@RequestLine("GET /codeman/info/author/{userid}") //对应请求方式和路径
public String requestLine(@Param("userid") String userid);
}
通过上面的代码可以看到在Feign的定义接口里, 不再使用SpringMVC里的标准Post/Get/Delete/Request等; 而是使用Feign标准的注解RequestLine;这个估计大多数做过Feign的朋友,还不知道这个才是Feign的标准Annotation; 由于上一步,我们修改了Consract不再使用SpringMVCConstract;所以Post/Get/Delete/Request等这些在SpringMVC里的Annotation不能使用;必须替换;
上一段代码,我们定义出了RPC的remote调用接口;在service层,我们使用已定义的Feign接口,完成业务上的调用;
@Component
public class CodeManServiceImpl implements CodeManService{
//@Autowired
//CodeManFeign codeManFeign;
@Value("${codeman.service.address:http://${codeman.service.name:codeman}}")
private String url;
@Value("${codeman.service.name:codeman}")
private String name;
CircuitBreaker circuitBreaker;
CodeManFeign codeManFeign;
@PostConstruct
public void init(){
circuitBreaker = CircuitBreaker.ofDefaults("backendA");
FeignDecorators decorators = FeignDecorators.builder()
.withFallback(new CodeManFallbackImpl(), FeignException.class)
.build();
codeManFeign = Resilience4jFeign.builder(decorators).target(new Target.HardCodedTarget<>(CodeManFeign.class,
name, url));
}
@Override
public String getVersion() {
return codeManFeign.getVersion();
}
@Override
public String getAuthor() {
return codeManFeign.getAuthor();
}
@Override
public String requestLine(String userid) {
return codeManFeign.requestLine(userid);
}
}
上面的业务调用的代码;和我们经常使用OpenFeign进行调用上有点差别;在与对CodeManFeign的IOC的获取上;可以看到代码里
注释掉了通过IOC的方式
//@Autowired
//CodeManFeign codeManFeign;
而是通过
@PostConstruct
public void init(){
}
的方式,在init里通过FeignDecorators.builder()去实例化了Resilience4jFeign.builder(decorators).target()的方式去实例化了这个; 这里也是不解的地方;为什么不去类似Hystrix的哦方式。在Feign接口定义的时候,也去做一些绑定,而是要手工在这里通过API进行绑定; 对这个不解:个人的感觉;可能还是在产品设计的时候,让这个设置更加的细化吧。
在我们自己的项目里,在这里是自己做了处理的,其实这个处理的目的就省略掉Init里的代码
public class CodeManFallbackImpl implements CodeManService {
@Override
public String getVersion() {
return "N/A";
}
@Override
public String getAuthor() {
return "SpringCloud";
}
@Override
public String requestLine(String userid){
return "Kill team‘s poison";
}
}
通过以上的步骤,就可以实现Resilience4j的熔断器的实现了。 题外话,由于我们自己的项目都是使用的springcloud来实现微服务;并且都是在springcloud的基础上结合公司项目的特点,封装了自己的springcloud的开发框架,在springcloud的ioc是个好东西,但是在spring里的ioc到处都是,不仅增加了调试问题和追踪问题的复杂程度;而且更让人不放心的就是ioc都是使用反射或者动态代理的方式进行处理的, 这样的实现方式真的存在着很大的隐患;不过由于我们使用SpringCloud实现的微服务大多数都是来处理业务应用,反射的性能低下相对于业务应用往往性能的瓶颈而言,基本上都可以忽略不计了,这样的隐患也就藏起来了。 当然在学习了Go语言以后,深深地体会到了,做服务和做业务对设计模式的取舍点的不同;
本文主要介绍的是在SpringCloud微服务开发中,如何使用Resilience4j实现熔断器功能的实现方式; 也可以直接通过resilience4j的API,在自己的Java程序里实现熔断器功能, 说实在的,SpringCloud的微服务体系真的和Service Mesh的体系一比较,真的就是一个小孩和大人的比较,熔断是业务需求吗,既然不是,为什么springCloude的开发中,微服务的代码却要揉入熔断器的开发代码; 别扭吧; 这就是Service Mesh为什么会是未来的原因。