0
点赞
收藏
分享

微信扫一扫

快速解决 npm 安装 node-sass 速度慢/错误的问题(nexus私服问题见上一篇博客)

快乐小鱼儿_9911 2024-01-04 阅读 9

目录

1. 过滤器的作用

2. Spring Cloud Gateway 过滤器的类型

2.1 内置过滤器

2.1.1 AddResponseHeader

2.1.2 AddRequestHeader

2.1.3 PrefixPath

2.1.4 RequestRateLimiter

2.1.5 Retry

2.2 自定义过滤器


1. 过滤器的作用

过滤器通常用于拦截、处理或修改数据流和事件流,在数据流中执行特定的操作或转换。

过滤器主要在以下几个方面发挥作用:

  1. 功能扩展和定制:过滤器允许您自定义和扩展网关的功能,以满足特定需求,如请求和响应的修改、路由规则的动态配置等。

  2. 数据校验和过滤:通过过滤器,您可以检查、验证和过滤传入或传出的数据,确保请求和响应的合法性和一致性。

  3. 安全保护:过滤器可以用于实施安全策略,如认证、授权、防止攻击等,以增强网关的安全性。

  4. 性能优化:通过过滤器,您可以对请求和响应进行性能优化,如缓存、压缩、请求路由的智能选择,以提高网关的性能。

  5. 统一处理:过滤器允许您在网关层面执行共享的处理逻辑,如日志记录、监控、审计等,以确保整个微服务体系的一致性和可维护性。

  6. 逻辑复用:通过过滤器,您可以将一些常见的操作抽象出来,以实现逻辑的复用,减少重复代码和维护工作。

2. Spring Cloud Gateway 过滤器的类型

Spring Cloud Gateway 过滤器可以分为两大类:

1. 内置过滤器

  • 局部的内置过滤器
  • 全局的内置过滤器

2. 自定义过滤器

2.1 内置过滤器

内置过滤器常见的有以下几种:

  1. AddResponseHeader
  2. AddRequestHeader
  3. AddRequestParameter(和 AddRequestHeader 相似)
  4. PrefixPath
  5. RequestRateLimiter
  6. Retry

Spring Cloud Gateway 过滤器常见有这么几种,实际上它有30多种,可以借助官方文档加以了解:Spring Cloud Gateway

过滤器又分为前置过滤器后置过滤器

在目标方法返回之前执行的过滤器就叫做前置过滤器(AddRequestXXX),在目标方法返回之后执行的过滤器就叫做后置过滤器。(AddResponseXXX)

前置工作:准备 user-service 和 order-service 两个模块,并且配置好 naocs 连接信息。

① user-service:创建一个 controller

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired  // 获取动态端口
    private ServletWebServerApplicationContext context;

    @RequestMapping("/getname")
    public String getName() {
        return context.getWebServer().getPort() +
                "--UserService:name=java-"+
                new Random().nextInt(100);
    }
}
server.port=0
spring.application.name=user-service-gateway
spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos

② order-service:创建一个 controller

@RestController
@RequestMapping("/order")
public class OrderController {

    @RequestMapping("/getcount")
    public int getCount() {
        return new Random().nextInt(1000);
    }
}
server.port=0
spring.application.name=order-service-gateway
spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos

2.1.1 AddResponseHeader

spring:
  cloud:
    nacos: # 配置注册中心
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
    gateway: # 配置网关
      routes:
        - id: userservice
          uri: lb://user-service-gateway   # loadbalancer
          predicates:
            - Path=/user/**
          filters:
            - AddResponseHeader=My-Resp-Header, www.baidu
server:
  port: 10086

在配置过滤器之前,我在网关服务中引入 Nacos 和 LoadBalancer 之后,使用10086 端口去访问userservice服务时,查看对应的响应头:

配置了 AddResponseHeader 过滤器之后,再去访问userservice服务时,查看对应的响应头:

上述的设置,设置的是局部过滤器,如果此时在网关服务中再新增一组 id (如下),此时再去访问order-service服务时,响应头中就不会包含 My-Resp-Header 这个标头了。

spring:
  cloud:
    gateway: # 配置网关
      routes:
        - id: userservice  # 用户服务
          uri: lb://user-service-gateway   # loadbalancer
          predicates:
            - Path=/user/**
          filters:
            - AddResponseHeader=My-Resp-Header, www.baidu
        - id: orderservice  # 订单服务
          uri: lb://order-service-gateway
          predicates:
            - Path=/order/**

此时还想访问 orderservice 服务时,也在响应头中看到对应的标头,要么就把 filters 照搬到下面,但是这样做,代码就不具备维护性了,发生修改的时候,这将会是一个体力活;要么就使用全局过滤器。

全局过滤器的配置

spring:
  cloud:
    gateway: # 配置网关
      routes:
        - id: userservice  # 用户服务
          uri: lb://user-service-gateway   # loadbalancer
          predicates:
            - Path=/user/**
          filters:
            - AddResponseHeader=My-Resp-Header, www.baidu
        - id: orderservice  # 订单服务
          uri: lb://order-service-gateway
          predicates:
            - Path=/order/**
      default-filters:  # 全局过滤器
        - AddResponseHeader=MyApplication-Resp-Header, gateway.org

2.1.2 AddRequestHeader

spring:
  cloud:
    nacos: # 配置注册中心
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
    gateway: # 配置网关
      routes:
        - id: userservice  # 用户服务
          uri: lb://user-service-gateway   # loadbalancer
          predicates:
            - Path=/user/**
          filters:
            - AddResponseHeader=My-Resp-Header, www.baidu
      default-filters:
        - AddRequestHeader=My-Req-Marking, www.baidu

如何拿到前置过滤器,可以在 userservice 服务里边写一个打印请求头的 controller:

@RequestMapping("/print-header")
public void printHeader(HttpServletRequest request) {
    Enumeration<String> headers = request.getHeaderNames();
    while(headers.hasMoreElements()) {
        String key = headers.nextElement();
        String value = request.getHeader(key);
        System.out.println(key +": " + value);
    }
}

此时运行userservice 和 gateway,去反问 print-header 接口, 查看 userservice 的控制台:

2.1.3 PrefixPath

spring:
  cloud:
    nacos: # 配置注册中心
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
    gateway: # 配置网关
      routes:
      - id: userservice
        uri: lb://user-service-gateway
        predicates:
        - Path=/user/**
        filters:
        - PrefixPath=/v2

假设此时 userservice 中的 UserController 升级为了 v2 版本,但是我的接口 /user/** 已经 对外公布了,此时还想让 localhost:10086/user/getname 访问到 v2 版本的 controller,肯定是不允许再修改后端的接口了,也不能让前端跟着改,使用 PrefixPath 就可以很好的调整。

第一版代码:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired  // 获取动态端口
    private ServletWebServerApplicationContext context;

    @RequestMapping("/getname")
    public String getName() {
        return context.getWebServer().getPort() +
                "--UserService:name=java-"+
                new Random().nextInt(100);
    }
}

第二版代码: 

@RequestMapping("/v2/user")
@RestController
public class UserControllerV2 {
    @Autowired  // 获取动态端口
    private ServletWebServerApplicationContext context;

    @RequestMapping("/getname")
    public String getName() {
        return context.getWebServer().getPort() +
                "--V2:UserService:name=java-"+
                new Random().nextInt(10);
    }
}

当我们配置了 PrefixPath 之后,再使用原来的 localhost:10086/user/getname 去访问服务的时候,就可以正常的访问到 v2 版本的 controller 了,无需再去修改前后端接口。

2.1.4 RequestRateLimiter

这是 Spring Cloud Gateway 内置的网关限流过滤器,它使用了令牌桶的限流算法。

Spring Cloud  Gateway 当前版本支持和 Redis 一起实现限流功能,Spring Cloud Gateway 选择 Redis 作为限流方案的一个重要支撑是因为 Redis 的一些特性可以很好地满足限流中对于性能、一致性和分布式处理的要求。

它的实现步骤总共分为三步:

  1. 添加 Redis 框架依赖
  2. 创建限流规则
  3. 配置限流过滤器

a.添加 Redis 框架依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

注意事项:Redis版本太低,此功能设置会无效,建议使用 Redis 版本 5.x+。

b.创建限流规则

限流规则,既可以针对某一个 IP 做限流,也可以针对 URL 进行限流(所有的 IP 访问 URL 都会限流)

创建一个类,根据IP进行限流:

@Component
public class IpAddressKeyResolver implements KeyResolver {
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().
                getRemoteAddress().getHostName());
    }
}

创建一个类,根据URL进行限流:

@Component
public class UrlKeyResolver implements KeyResolver {
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        // 获取请求的URI路径作为限流的key
        String path = exchange.getRequest().getURI().getPath();
        return Mono.just(path);
    }
}

c.设置限流过滤器

spring:
  cloud:
    nacos: # 配置注册中心
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
    gateway: # 配置网关
      routes:
      - id: userservice
        uri: lb://user-service-gateway
        predicates:
        - Path=/user/**
        filters:
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 1  # 每秒请求数
            redis-rate-limiter.burstCapacity: 1  # 最大请求数
            key-resolver: "#{@ipAddressKeyResolver}"  # spEL表达式

  data:
    redis:
      host: 127.0.0.1
      port: 16379
      database: 0

注意,内置的限流过滤器 name 必须等于"RequestRateLimiter" ,其他参数的含义如下:

这三步完成之后,就表示每个 IP  每秒钟只能访问一次,如果刷新页面刷新的太快,就会出现如下页面:

2.1.5 Retry

在 OpenFeign 里面呢,也有个 Retry 超时重试,而且它还可以自定义重试规则,为什么 Gateway 还要有一个 Retry 呢 ?

请求重试过滤器配置案例:

spring:
  cloud:
    nacos: # 配置注册中心
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
    gateway: # 配置网关
      routes:
      - id: userservice
        uri: lb://user-service-gateway
        predicates:
        - Path=/user/**
        filters:
        - name: Retry  # 重试过滤器
          args:
            retries: 3
            statuses: GATEWAY_TIMEOUT
            methods: GET
            series: SERVER_ERROR
            backoff:
              firstBackoff: 10ms  # 第一次重试间隔
              maxBackoff: 50ms  # 最大重试间隔
              factor: 2  # firstBack * (factor ^ n)   # 重试系数
              basedOnPreviousValue: false  # 基于上次重试时间加上重试系数来计算

注意,重试过滤器的 name 必须等于 "Retry" ,因为 "Retry" 就是内置重试过滤器的名字,改为其他框架就无法识别了;其他参数的含义如下:

【案例演示】

在 userservice 服务中,写一个接口去触发 GATEWAY_TIMEOUT:

@RequestMapping("/504")
public void return504(HttpServletResponse response) {
    System.out.println("------- Do return504 method. ------");
    response.setStatus(504);
}

启动 userservice 模块和 gateway 模块,此时 userservice 服务自动注入到 nacos 中,使用原生服务接口访问:

原生服务接口:192.168.10.83:63129/user/504,触发 504 

此时查看控制台 :打印了一次

再使用 Gateway 网关去访问 userservice 服务, localhost:10086/user/504,触发 504

此时查看控制台:打印了 4 次(清除上面的打印后)

可见重试过滤器确实生效了。

【注意事项】

2.2 自定义过滤器

代码案例:使用 Spring Cloud Gateway 提供的全局过滤器实现统一认证授权。

@Component
public class AuthFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange,   // 执行的事件
                             GatewayFilterChain chain)     // 过滤器链
    {

        // 未登录判断逻辑,当参数中 username=admin && password=admin 继续执行,
        // 否则退出执行

        // 得到 Request 对象 (reactive web)
        ServerHttpRequest request = exchange.getRequest();
        // 得到 Response 对象 (reactive web)
        ServerHttpResponse response = exchange.getResponse();
        String username = request.getQueryParams().getFirst("username");
        String password = request.getQueryParams().getFirst("password");
        if (username != null && username.equals("admin")
                && password != null && password.equals("admin")) {
            // 已经登录,执行下一步
            return chain.filter(exchange);
        } else {
            // 设置无权限 401
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 执行完成,不用继续执行后续流程了
            return response.setComplete();
        }
    }
}

在访问相对应服务的时候,可以先不加用户名密码试一次,它会触发 401,再加上 username=admin&password=admin,就可以正常访问到相应服务了。

当有多个过滤器时,我们还可以给过滤器指定它的执行顺序:

  1. 实现 Ordered 接口
  2. 重写 getOrder 方法
@Override
public int getOrder() {
    // 此值越小越先执行
    return 1;
}

举报

相关推荐

0 条评论