SpringCloud CircuitBreak, 熔断限流

前言

CircuitBreaker熔断器是类似于家用保险盒的功能,当在微服务体系里,由于某个作为服务提供者的微服务发生性能故障或者网络中断的情况下,作为消费者的微服务不会因为上端的大规模的服务中断而发生服务一直等待响应;而产生雪崩状况的一种解决方案;通过熔断器的设计,当上端服务路由出现故障或者不响应的时候,会对上端的服务路由进行短路处理,而避免出现长时间等待响应的情况;

限流器和熔断器一样,都是为提供微服务的高可用的一种解决方案;和熔断器有所不同,限流器的主要目的是用于限制微服务云中服务路由中的流量控制; 限流和熔断器都是提供咱们的微服务体系的高可用性的重要解决手段;今天咱们这个文章就是来看看SpringCloud 2021.0.x的版本中如何来做到熔断和限流。

方案选择

在SpringCloud体系中,作为熔断器和限流器解决方案的产品,主流的有Hystrix,Resilience4j,Sentinel;

其产品特点和对比,如下图所示

其中Hystrix是Netflix的熔断器组件;和其他的Netflix组件一样, 目前Netflix已经不再继续进行维护;断更了; 所以SpringCloud2020.x就已经移除了对其的相关引用和支持; 取而代之的就是SpringCloud官方推荐的Resilience4j,这个项目已经开源; Sentinel是有SpringCloud Alibaba套件里提供的熔断组件; 在功能上较resilience4j更为丰富;不过笔者觉得集成度过高,反而不如resilience4j扩展灵活;sentinel集成了sentinel dashboard提供规则和统计功能。 三个产品的更详细的对比可以查看文章《小谈Springcloud中的几个主流熔断器》; 今天的文章主要介绍Resilience4j的集成;

Resilience4j

resilience4j是一个轻量级、易于使用的容错库,其灵感来自Netflixesilience4j是一个轻量级、易于使用的容错库,其灵感来自Netflix Hystrix,但专为Java 8和函数式编程设计。Resilience4j非常轻量级,不仅可以在SpringCloud进行使用,还可以直接在自己的Java程序里通过Resilience4j的API实现Rate Limite的功能; 这点也是笔者非常认同的一点; 支持和扩展上更为方便;

导入依赖包

通过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的契约

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}")    //对应请求方式和路径
    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接口,完成业务上的调用;

@Slf4j
@Component("CodeManService")
public class CodeManServiceImpl implements CodeManService {

    @Autowired
    CodeManFeign codeManFeign;

    @Override
    @CircuitBreaker(name = "default", fallbackMethod = "getVersionFallback")
    public String getVersion() {

        return codeManFeign.getVersion();
    }

    @Override
    @CircuitBreaker(name = "default", fallbackMethod = "getAuthorFallback")
    public String getAuthor() {
        return codeManFeign.getAuthor();
    }

    @Override
    @CircuitBreaker(name = "default", fallbackMethod = "requestLineFallback")
    public String requestLine(String userid) {
        return codeManFeign.requestLine(userid);
    }


    public String getVersionFallback(Throwable t) {
        log.info("=================================  Exception(getVersion): {}", t.getMessage());
        return "N/A";
    }

    public String getAuthorFallback(Throwable t) {
        log.info("=================================  Exception(getAuthor): {}", t.getMessage());
        return "SpringCloud";
    }

    public String requestLineFallback(String userid, Throwable t){
        log.info("=================================  Exception(requestLine): {}", t.getMessage());
        return "Kill team‘s poison " + userid;
    }
}

定义Fallback的实现

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";
    }
}

熔断测试

测试环境;通过FeignClient定义的ServiceName; 对应了访问http://localhost:7500; 启动了端口7400的微服务,关闭了端口为7500的微服务;然后访问当前的微服务,由于7500端口不能访问,服务熔断降级;执行Fallback对应的处理;返回结果“SpringCloud”; 如下图

此时,Feign调用失败; 调用的http://localhost:7500; Fallback的效果就是显示SpringCloud

启动7500端口的微服务; 此时可以正常访问,如下图

成功调用,没有Fallback的调用;

结束语

本文主要介绍的是在SpringCloud微服务开发中,如何使用Resilience4j实现熔断器功能的实现方式; 也可以直接通过resilience4j的API,在自己的Java程序里实现熔断器功能, 说实在的,SpringCloud的微服务体系真的和Service Mesh的体系一比较,真的就是一个小孩和大人的比较,熔断是业务需求吗,既然不是,为什么springCloude的开发中,微服务的代码却要揉入熔断器的开发代码; 别扭吧; 这就是Service Mesh为什么会是未来的原因。

想知悉哪些相关的技术点;欢迎大家们在回复区进行回复,咱们将汇总大家感兴趣的知识点和热点给大家进行介绍; 希望大家多多的交流和探讨!!!!

谢谢大家的关注!!!

猜你喜欢

转载自blog.csdn.net/inthirties/article/details/126827286