如之前系列( Spring Cloud升级之路 - Hoxton - 4. 使用Resilience4j实现实例级别的隔离与熔断 )所述,我们实现了实例级别的熔断。但是在生产中发现,并不是所有情况下都表现良好。首先如果发布了新接口,但是不小心回滚了,调用新接口就会报错,从而导致整个实例都不能访问。还有就是某些实例某个接口出现了问题,但是其他接口是好的,熔断掉整个实例有点浪费。于是乎,我们将实例级别的熔断改成 实例 + 方法级别。
首先,我们只针对断路器进行修改,线程隔离还是实例级别的,如果也抽象为实例+方法级别的,线程数与线程池的数量就太多了。
每个 Feign 调用都是有 url 的,我们是不是可以通过 url 获取不同的断路器呢?这样做是可以的,的确实现了实例 + 方法级别的熔断。但是有一个问题,对于有 PathVaraiable
的 FeignClient,相当于每个参数都会新建一个断路器,这并不是我们想要的,例如:
package com.github.hashjang.hoxton.service.consumer.feign; //省略import @FeignClient(value = "service-provider") public interface ServiceProviderTestReadTimeoutFeignCleint { @RequestMapping(value = "/test-read-time-out/{userId}", method = RequestMethod.GET) String testTimeoutGet(@PathVariable String userId); } 复制代码
这样的话,对于每一个 userId 都会生成一个不同的断路器,这样断路器实际上可能没起到该有的作用。
所以,还是用实际 Feign 接口的类全限定方法名称作为唯一标识。
LoadBalancerConfig.java:
@Override public Response execute(Request request, Request.Options options) throws IOException { String serviceName = request.requestTemplate().feignTarget().name(); String serviceInstanceId = getServiceInstanceId(request); String serviceInstanceMethodId = getServiceInstanceMethodId(request); ThreadPoolBulkhead threadPoolBulkhead; CircuitBreaker circuitBreaker; try { //每个实例一个线程池 threadPoolBulkhead = threadPoolBulkheadRegistry.bulkhead(serviceInstanceId, serviceName); } catch (ConfigurationNotFoundException e) { threadPoolBulkhead = threadPoolBulkheadRegistry.bulkhead(serviceInstanceId); } try { //每个服务实例具体方法一个resilience4j熔断记录器,在服务实例具体方法维度做熔断,所有这个服务的实例具体方法共享这个服务的resilience4j熔断配置 circuitBreaker = circuitBreakerRegistry.circuitBreaker(serviceInstanceMethodId, serviceName); } catch (ConfigurationNotFoundException e) { circuitBreaker = circuitBreakerRegistry.circuitBreaker(serviceInstanceMethodId); } //省略之后代码 } private String getServiceInstanceId(Request request) throws MalformedURLException { String serviceName = request.requestTemplate().feignTarget().name(); URL url = new URL(request.url()); return serviceName + ":" + url.getHost() + ":" + url.getPort(); } private String getServiceInstanceMethodId(Request request) throws MalformedURLException { String serviceName = request.requestTemplate().feignTarget().name(); URL url = new URL(request.url()); //通过微服务名称 + 实例 + 方法的方式,获取唯一id String methodName = request.requestTemplate().methodMetadata().method().toGenericString(); return serviceName + ":" + url.getHost() + ":" + url.getPort() + ":" + methodName; } 复制代码
对于 Spring Cloud Gateway,仅仅是在断路器上面加上 url, 同样的,会有上面说的,如果 url 中带有某个参数的 PathVariable,会生成很多独立的断路器的问题,这个目前还没有什么办法好解决。 并且,网关对于 404 并不认为是错误,只对IO异常以及超时会有错误。这种情况下,对于实例熔断,也并不是不能接受,虽然还会有某个接口超时导致实例熔断的风险,但是将超时时间设置足够长,以后后续的微服务进行异常处理熔断,也是可以接受的。
综合评估之后, 网关还是继续保持实例级别的熔断 。