前言
在文章《小談Springcloud中的幾個主流熔斷器》,我們介紹了SpingCloud架構(gòu)中的幾個主流熔斷器,其中SpringCloud官方推薦的Resilience4j作為2020.x以后的新秀,遠遠沒有hystrix有名,相關(guān)的文檔也還不夠多;今天這個文章就來講講SpringCloud如何使用Resilience4j實現(xiàn)熔斷器;
Resilience4j
resilience4j是一個輕量級、易于使用的容錯庫,其靈感來?.NETflixesilience4j是一個輕量級、易于使用的容錯庫,其靈感來自Netflix Hystrix,但專為JAVA 8和函數(shù)式編程設(shè)計。
Resilience4j提供高階函數(shù)(decorators)來增強任何功能接口、lambda表達式或方法引用,包括斷路器、速率限制器、重試或艙壁。可以在任何函數(shù)接口、lambda表達式或方法引用上使用多個裝飾器。
Resilience4j非常輕量級,不僅可以在SpringCloud進行使用,還可以直接在自己的Java程序里通過Resilience4j的API實現(xiàn)Rate Limite的功能; 這點也是筆者非常認同的一點; 支持和擴展上更為方便; 今天的這個文章,重點還是介紹如何在SpringCloud里使用Resilicen4j
導入依賴包
通過pom.xml引入對Resilience4j的依賴
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot</artifactId>
</dependency>
配置resilience4j
在項目的Applicaiton.yml里添加SpringCloud里resilience4j的相關(guān)配置
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這些相關(guān)的熔斷限流的相關(guān)項目。引入resilience4j-spring-boot集成了circuitbeaker、retry、bulkhead、ratelimiter幾個模塊的功能, 提供相關(guān)的實現(xiàn);我們可以根據(jù)業(yè)務(wù)的實際需要對這些相關(guān)項目進行具體配置修改;
開發(fā)上和使用Hystrix大致上一樣; 不過注意調(diào)整一下自己的地方
feign契約只能用feign的, 不能用SpringMVC的契約
feign契約只能用feign的, 不能用SpringMVC的契約;所以不能使用默認的方式,必須通過autoconfiguraiton進行修改
@Bean
public Contract feignContract(){
return new Contract.Default();
}
/**
@Bean
public Contract feignContract(){
return new SpringMvcContract();
}
**/
使用標準Feign注解
由于修改了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}") //對應(yīng)請求方式和路徑
public String requestLine(@Param("userid") String userid);
}
通過上面的代碼可以看到在Feign的定義接口里, 不再使用SpringMVC里的標準Post/Get/Delete/Request等; 而是使用Feign標準的注解RequestLine;這個估計大多數(shù)做過Feign的朋友,還不知道這個才是Feign的標準Annotation; 由于上一步,我們修改了Consract不再使用SpringMVCConstract;所以Post/Get/Delete/Request等這些在SpringMVC里的Annotation不能使用;必須替換;
業(yè)務(wù)調(diào)用
上一段代碼,我們定義出了RPC的remote調(diào)用接口;在service層,我們使用已定義的Feign接口,完成業(yè)務(wù)上的調(diào)用;
@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);
}
}
上面的業(yè)務(wù)調(diào)用的代碼;和我們經(jīng)常使用OpenFeign進行調(diào)用上有點差別;在與對CodeManFeign的IOC的獲取上;可以看到代碼里
注釋掉了通過IOC的方式
//@Autowired
//CodeManFeign codeManFeign;
而是通過
@PostConstruct
public void init(){
}
的方式,在init里通過FeignDecorators.builder()去實例化了Resilience4jFeign.builder(decorators).target()的方式去實例化了這個; 這里也是不解的地方;為什么不去類似Hystrix的哦方式。在Feign接口定義的時候,也去做一些綁定,而是要手工在這里通過API進行綁定; 對這個不解:個人的感覺;可能還是在產(chǎn)品設(shè)計的時候,讓這個設(shè)置更加的細化吧。
在我們自己的項目里,在這里是自己做了處理的,其實這個處理的目的就省略掉Init里的代碼
定義Fallback的實現(xiàn)
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";
}
}
通過以上的步驟,就可以實現(xiàn)Resilience4j的熔斷器的實現(xiàn)了。 題外話,由于我們自己的項目都是使用的springcloud來實現(xiàn)微服務(wù);并且都是在springcloud的基礎(chǔ)上結(jié)合公司項目的特點,封裝了自己的springcloud的開發(fā)框架,在springcloud的ioc是個好東西,但是在spring里的ioc到處都是,不僅增加了調(diào)試問題和追蹤問題的復雜程度;而且更讓人不放心的就是ioc都是使用反射或者動態(tài)代理的方式進行處理的, 這樣的實現(xiàn)方式真的存在著很大的隱患;不過由于我們使用SpringCloud實現(xiàn)的微服務(wù)大多數(shù)都是來處理業(yè)務(wù)應(yīng)用,反射的性能低下相對于業(yè)務(wù)應(yīng)用往往性能的瓶頸而言,基本上都可以忽略不計了,這樣的隱患也就藏起來了。 當然在學習了Go語言以后,深深地體會到了,做服務(wù)和做業(yè)務(wù)對設(shè)計模式的取舍點的不同;
結(jié)束語
本文主要介紹的是在SpringCloud微服務(wù)開發(fā)中,如何使用Resilience4j實現(xiàn)熔斷器功能的實現(xiàn)方式; 也可以直接通過resilience4j的API,在自己的Java程序里實現(xiàn)熔斷器功能, 說實在的,SpringCloud的微服務(wù)體系真的和Service Mesh的體系一比較,真的就是一個小孩和大人的比較,熔斷是業(yè)務(wù)需求嗎,既然不是,為什么springCloude的開發(fā)中,微服務(wù)的代碼卻要揉入熔斷器的開發(fā)代碼; 別扭吧; 這就是Service Mesh為什么會是未來的原因。