关于云计算:Spring-Cloud-Gateway自定义过滤器实战观测断路器状态变化

36次阅读

共计 9258 个字符,预计需要花费 24 分钟才能阅读完成。

欢送拜访我的 GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,波及 Java、Docker、Kubernetes、DevOPS 等;

本篇概览

  • 本文是《Spring Cloud Gateway 实战》系列的第七篇,后面的文章咱们学习了各种内置过滤器,还在《Spring Cloud Gateway 的断路器 (CircuitBreaker) 性能》一文深入研究了断路器类型的过滤器(实践 & 实战 & 源码剖析皆有),置信聪慧的您肯定会有此疑难:内置的再多也无奈笼罩全副场景,定制才是终极武器
  • 所以明天咱们就来开发一个本人专属的过滤器,至于此过滤器的具体性能,其实前文已埋下伏笔,如下图:

  • 简略来说,就是在一个有断路器的 Spring Cloud Gateway 利用中做个自定义过滤器,在解决每个申请时把断路器的状态打印进去,这样咱们就能明明白白清清楚楚晓得断路器的状态啥时候扭转,变成了啥样,也算补全了《Spring Cloud Gateway 的断路器 (CircuitBreaker) 性能》的知识点
  • 过滤器分为全局和部分两种,这里咱们选用部分的,起因很简略:咱们的过滤器是为了察看断路器,所以不须要全局失效,只有在应用断路器的路由中失效就够了;

套路提前通晓

  • 咱们先看看自定义部分过滤器的的根本套路:
  1. 新建一个类(我这里名为 StatePrinterGatewayFilter.java),实现 GatewayFilter 和 Ordered 接口,重点是 filter 办法,该过滤器的次要性能就在这外面实现
  2. 新建一个类(我这里名为 StatePrinterGatewayFilterFactory.java),实现 AbstractGatewayFilterFactory 办法,其 apply 办法的返回值就是上一步新建的 StatePrinterGatewayFilter 的实例,该办法的入参是在路由配置中过滤器节点上面的配置,这样就能够依据配置做一些非凡的解决,而后再创立实例作为返回值
  3. StatePrinterGatewayFilterFactory 类实现 <font color=”blue”>String name()</font> 办法,该办法的返回值就是路由配置文件中过滤器的 <font color=”red”>name</font>
  4. <font color=”blue”>String name()</font> 也能够不实现,这是因为定义该办法的接口中有默认实现了,如下图,这样您在路由配置文件中过滤器的 <font color=”red”>name</font> 只能是 <font color=”blue”>StatePrinter</font>:

  1. 在配置文件中,增加您自定义的过滤器,该操作和之前的增加内置过滤器截然不同
  • 以上就是自定义过滤器的根本套路了,可见还是非常简单的,接下来的实战也是依照这个套路来的
  • 在编写自定义过滤器代码之前,还有个拦路虎等着咱们,也就是咱们过滤器的基本功能:如何获得断路器的状态

如何获得断路器的状态

  • 前文的代码剖析中,咱们理解到断路器的外围性能集中在 SpringCloudCircuitBreakerFilterFactory.apply 办法中(没错,就是方才提到的 apply 办法),关上这个类,如下图,从绿框可见断路器性能来自名为 <font color=”blue”>cb</font> 的对象,而这个对象是在红框处由 reactiveCircuitBreakerFactory 创立的:

  • 开展上图红框右侧的 reactiveCircuitBreakerFactory.create 办法持续看,最终跟踪到了 ReactiveResilience4JCircuitBreakerFactory 类,发现了一个极其重要的变量,就是下图红框中的 circuitBreakerRegistry,它的外部有个 ConcurrentHashMap(InMemoryRegistryStore 的 entryMap),这外面寄存了所有断路器实例:

  • 此时您应该想到了,拿到断路器的要害就是拿到上图红框中的 <font color =”blue”>circuitBreakerRegistry</font> 对象,不过怎么拿呢?首先它是公有类型的,其次尽管有个办法返回了该对象,然而此办法并非 public 的,如下图红框:

  • 这个问题当然难不倒聪慧的您了,没错,用反射批改此办法的拜访权限,稍后的代码中咱们就这么干
  • 还剩最初一个问题:circuitBreakerRegistry 是 ReactiveResilience4JCircuitBreakerFactory 的成员变量,这个 ReactiveResilience4JCircuitBreakerFactory 从哪获取?
  • 如果您配置过断路器,对这个 ReactiveResilience4JCircuitBreakerFactory 就很相熟了,设置该对像是配置断路器的基本操作,回顾一下前文的代码:
@Configuration
public class CustomizeCircuitBreakerConfig {

    @Bean
    public ReactiveResilience4JCircuitBreakerFactory defaultCustomizer() {CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() //
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED) // 滑动窗口的类型为工夫窗口
                .slidingWindowSize(10) // 工夫窗口的大小为 60 秒
                .minimumNumberOfCalls(5) // 在单位工夫窗口内起码须要 5 次调用能力开始进行统计计算
                .failureRateThreshold(50) // 在单位工夫窗口内调用失败率达到 50% 后会启动断路器
                .enableAutomaticTransitionFromOpenToHalfOpen() // 容许断路器主动由关上状态转换为半开状态
                .permittedNumberOfCallsInHalfOpenState(5) // 在半开状态下容许进行失常调用的次数
                .waitDurationInOpenState(Duration.ofSeconds(5)) // 断路器关上状态转换为半开状态须要期待 60 秒
                .recordExceptions(Throwable.class) // 所有异样都当作失败来解决
                .build();

        ReactiveResilience4JCircuitBreakerFactory factory = new ReactiveResilience4JCircuitBreakerFactory();
        factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
                .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(200)).build())
                .circuitBreakerConfig(circuitBreakerConfig).build());

        return factory;
    }
}
  • 既然 ReactiveResilience4JCircuitBreakerFactory 是 spring 的 bean,那咱们在 StatePrinterGatewayFilterFactory 类中用 Autowired 注解就能随便应用了
  • 至此,实践剖析已全副实现,问题都曾经解决,开始编码

源码下载

  • 本篇实战中的残缺源码可在 GitHub 下载到,地址和链接信息如下表所示(https://github.com/zq2599/blo…):
名称 链接 备注
我的项目主页 https://github.com/zq2599/blo… 该我的项目在 GitHub 上的主页
git 仓库地址(https) https://github.com/zq2599/blo… 该我的项目源码的仓库地址,https 协定
git 仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该我的项目源码的仓库地址,ssh 协定
  • 这个 git 我的项目中有多个文件夹,本篇的源码在 <font color=”blue”>spring-cloud-tutorials</font> 文件夹下,如下图红框所示:

  • <font color=”blue”>spring-cloud-tutorials</font> 文件夹下有多个子工程,本篇的代码是 <font color=”red”>circuitbreaker-gateway</font>,如下图红框所示:

编码

  • 前文创立了子工程 <font color=”blue”>circuitbreaker-gateway</font>,此工程已增加了断路器,当初咱们的过滤器代码就写在这个工程中是最合适的了
  • 接下来依照套路写代码,首先是 StatePrinterGatewayFilter.java,代码中有具体正文就不再啰嗦了,要留神的是 getOrder 办法返回值是 10,这示意过滤器的执行程序:
package com.bolingcavalry.circuitbreakergateway.filter;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.vavr.collection.Seq;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.lang.reflect.Method;

public class StatePrinterGatewayFilter implements GatewayFilter, Ordered {

    private ReactiveResilience4JCircuitBreakerFactory reactiveResilience4JCircuitBreakerFactory;

    // 通过构造方法获得 reactiveResilience4JCircuitBreakerFactory 实例
    public StatePrinterGatewayFilter(ReactiveResilience4JCircuitBreakerFactory reactiveResilience4JCircuitBreakerFactory) {this.reactiveResilience4JCircuitBreakerFactory = reactiveResilience4JCircuitBreakerFactory;}

    private CircuitBreaker circuitBreaker = null;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 这里没有思考并发的状况,如果是生产环境,请您自行添加上锁的逻辑
        if (null==circuitBreaker) {
            CircuitBreakerRegistry circuitBreakerRegistry = null;
            try {Method method = reactiveResilience4JCircuitBreakerFactory.getClass().getDeclaredMethod("getCircuitBreakerRegistry",(Class[]) null);
                // 用反射将 getCircuitBreakerRegistry 办法设置为可拜访
                method.setAccessible(true);
                // 用反射执行 getCircuitBreakerRegistry 办法,失去 circuitBreakerRegistry
                circuitBreakerRegistry = (CircuitBreakerRegistry)method.invoke(reactiveResilience4JCircuitBreakerFactory);
            } catch (Exception exception) {exception.printStackTrace();
            }

            // 失去所有断路器实例
            Seq<CircuitBreaker> seq = circuitBreakerRegistry.getAllCircuitBreakers();
            // 用名字过滤,myCircuitBreaker 来自路由配置中
            circuitBreaker = seq.filter(breaker -> breaker.getName().equals("myCircuitBreaker"))
                    .getOrNull();}

        // 取断路器状态,再判空一次,因为下面的操作未必能取到 circuitBreaker
        String state = (null==circuitBreaker) ? "unknown" : circuitBreaker.getState().name();

        System.out.println("state :" + state);

        // 继续执行前面的逻辑
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {return 10;}
}
  • 接下来是 StatePrinterGatewayFilterFactory.java,这里用不上什么配置,所以 apply 办法的入参也就没用上,须要留神的是通过 Autowired 注解拿到了 reactiveResilience4JCircuitBreakerFactory,而后通过构造方法传递给了 StatePrinterGatewayFilter 实例:
package com.bolingcavalry.circuitbreakergateway.filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;

@Component
public class StatePrinterGatewayFilterFactory extends AbstractGatewayFilterFactory<Object>
{
    @Autowired
    ReactiveResilience4JCircuitBreakerFactory reactiveResilience4JCircuitBreakerFactory;

    @Override
    public String name() {return "CircuitBreakerStatePrinter";}

    @Override
    public GatewayFilter apply(Object config)
    {return new StatePrinterGatewayFilter(reactiveResilience4JCircuitBreakerFactory);
    }
}
  • 最初是配置文件,残缺的配置文件如下,可见咱们将 CircuitBreakerStatePrinter 过滤器加了进来,放到最初:
server:
  #服务端口
  port: 8081
spring:
  application:
    name: circuitbreaker-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/**
          filters:
            - name: CircuitBreaker
              args:
                name: myCircuitBreaker
            - name: CircuitBreakerStatePrinter
  • 再次运行单元测试类 CircuitbreakerTest.java,如下图红框所示,断路器状态曾经打印进去,至此,咱们能够准确把握断路器的状态变动了:

剖析申请被 filter 漏掉的问题

  • 有个很显著的问题,聪明睿智的您当然不会疏忽:上图绿框中的间断四个响应,对应的断路器状态都没有打印进去,要晓得,咱们的过滤器可是要解决每一个申请的,怎么会间断漏掉四个呢?
  • 其实起因很容易推理进去:断路器 CircuitBreaker 的 filter 先执行,而后才是咱们的 CircuitBreakerStatePrinter,而处于开启状态的断路器会间接返回谬误给调用方,其前面的 filter 都不会执行了
  • 那么问题来了:如何管制 CircuitBreaker 和 CircuitBreakerStatePrinter 这两个 filter 的程序,让 CircuitBreakerStatePrinter 先执行?
  • CircuitBreakerStatePrinter 是咱们本人写的代码,批改 <font color=”blue”>StatePrinterGatewayFilter.getOrder</font> 的返回值能够调整程序,但 CircuitBreaker 不是咱本人的代码呀,这可如何是好?
  • 老规矩,看看断路器的源码,前文曾经剖析过了,断路器最重要的代码是 SpringCloudCircuitBreakerFilterFactory.apply 办法,如下图红框,生成的 filter 是 GatewayFilter 接口的实现类:

  • 再看加载过滤器到汇合的那段要害代码,在 RouteDefinitionRouteLocator.loadGatewayFilters 办法中,如下图所示,因为 CircuitBreaker 的 filter 并没有实现 Ordered 接口,因而执行的是红框中的代码,代表其程序的值等于 <font color=”red”>i+1</font>,这个 <font color=”red”>i</font> 就是遍历路由配置中所有过滤器时的一个从零开始的自增变量而已:

  • 回顾咱们的路由配置,CircuitBreaker 在前,CircuitBreakerStatePrinter 在后,所以,在增加 CircuitBreaker 的时候,i 等于 0,那么 CircuitBreaker 的 order 就等于 i +1= 1 了
  • 而 CircuitBreakerStatePrinter 实现了 Ordered 接口,因而不会走红框中的代码,其 order 等于咱们写在代码中的值,咱们写的是 10
  • 所以:CircuitBreaker 的 order 等于 1,CircuitBreakerStatePrinter 等于 10,当然是 CircuitBreaker 先执行了!

再次批改

  • 晓得了起因,改起来就容易了,我的做法很简略:StatePrinterGatewayFilter 不再实现 Ordered,这样就和 CircuitBreaker 的 filter 一样,执行的是上图红框中的代码,这样,在配置文件中,谁放在后面谁就先执行
  • 代码就不贴出来了,您自行删除 StatePrinterGatewayFilter 中和 Ordered 相干的局部即可
  • 配置文件调整后如下:
server:
  #服务端口
  port: 8081
spring:
  application:
    name: circuitbreaker-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/**
          filters:
            - name: CircuitBreakerStatePrinter
            - name: CircuitBreaker
              args:
                name: myCircuitBreaker
  • 改完了,再次运行 CircuitbreakerTest.java,如下图,这一次,每个申请都会打印出此时断路器的状态:

知识点小结

  • 至此,用于观测断路器状态的自定义过滤器就算实现了,整个过程还是有不少知识点的,咱们来盘点一下:
  1. 惯例的部分过滤器开发步骤
  2. 过滤器执行程序的逻辑
  3. spring 的依赖注入和主动拆卸
  4. 断路器的 filter 源码
  5. java 的反射基本功
  • 本文与《Spring Cloud Gateway 的断路器 (CircuitBreaker) 性能》联合,诚意满满的带给您实践联合实战的体验,心愿能给您学习 Spring Cloud Gateway 的过程中带来一些参考;

你不孤独,欣宸原创一路相伴

  1. Java 系列
  2. Spring 系列
  3. Docker 系列
  4. kubernetes 系列
  5. 数据库 + 中间件系列
  6. DevOps 系列

欢送关注公众号:程序员欣宸

微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游 Java 世界 …
https://github.com/zq2599/blog_demos

正文完
 0