【SpringCloud学习】服务网关gateway

网关的作用

性能:API高可用,负载均衡,容错机制

安全:权限身份认证,脱敏,流量清洗,后端签(保证全链路可信调用)名,黑名单(非法调用的限制)

日志:日志记录,一旦涉及分布式,全链路跟踪必不可少

缓存:数据缓存

监控:记录请求响应数据,API耗时分析,性能监控

限流:流量控制,错峰流控,可以定义多种限流规则

灰度:线上灰度部署,可以减小风险

路由:动态路由规则

核心概念
  • 路由:路由是网关最基础的部分,路由信息由ID、目标URI、一组断言(路由规则)和一组过滤器组成。如果断言为真,则说明请求的URI和配置匹配

  • 断言:Java8中的断言函数。Spring cloud Gateway中的断言函数输入类型是Spring 5.0框架中的ServerWebExchange。Spring cloud Gateway中的断言函数允许开发者去定义匹配来自于Http Request中的任何信息,比如请求头和参数等。

  • 过滤器:一个标准的Spring Web Filter。Spring cloud Gateway中的filter分为两种类型,分别为Gateway Filter和Global Filter。过滤器将会对请求和相应进行处理。

搭建入门案例

添加依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

配置文件

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: http://localhost:8080/ # 目标uri,路由到微服务地址
          predicates:                 # 断言(判断条件)
            - Path=/api/user/**       # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
server:
  port: 9999
logging:
  level:
    root: info
路由规则

Path

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: http://localhost:8080/ # 目标uri,路由到微服务地址
          predicates:                 # 断言(判断条件)
             - Path=/api/user/**       # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后

Query

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: http://localhost:8080/ # 目标uri,路由到微服务地址
          predicates:                 # 断言(判断条件)
             #- Query=token           # 匹配请求参数中含有token的请求
             - Query=token,abc.        # 匹配请求参数中包含token并且参数值满足正则表达式abc的请求

Query=token,比如,http://localhost:9999/api/user/2?token=123

Query=token,abc. 比如,http://localhost:9999/api/user/2?token=abc1

Method

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: http://localhost:8080/ # 目标uri,路由到微服务地址
          predicates:                 # 断言(判断条件)
             - Method=GET        # 匹配任意GET请求

Datetime

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: http://localhost:8080/ # 目标uri,路由到微服务地址
          predicates:                 # 断言(判断条件)
            # 匹配中国上海时间2022-02-02 20:20:20之后的请求
            - After=2022-02-02T20:20:20.000+08:00[Asia/Shanghai]

RemoteAddr

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service             # 路由ID,全局唯一
          uri: http://localhost:8080/  # 目标uri,路由到微服务地址
          predicates:                  # 断言(判断条件)
            - RemoteAddr=12.12.7.250/0 # 匹配远程地址是RemoteAddr的请求,0表示子网掩码

RemoteAddr=12.12.7.250/0,比如 http://12.12.7.250:9999/api/user/2?token=abc1

Header

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: http://localhost:8080/ # 目标uri,路由到微服务地址
          predicates:                 # 断言(判断条件)
            # 匹配请求头包含request_id并且其值满足正则表达式  \d+的请求
            - Header=request_id,\d+
动态路由(服务发现的路由规则)

动态路由就是面向服务的路由,spring cloud gateway支持与eureka整合,根据serviceId自动注册中心获取服务地址并转发请求,这样做的好处是不仅可以通过单点来访问所有的服务,而且再添加或移除服务实例时不用修改Gateway的路由配置。

添加依赖

        <!-- eureka客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

配置文件

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: lb://user              # lb://根据服务名称从注册中心获取服务请求地址
          predicates:                 # 断言(判断条件)
            - Path=/api/user/**       # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
过滤器

spring cloud gateway根据作用范围划分为 GatewayFilterGlobalFilter,两者区别如下

GatewayFilter:网关过滤器,需要通过spring.cloud.gateway.routes.filters配置在具体的路由下,只作用在当前路由上或者通过spring.cloud.gataway.default-filters配置在全局,作用在所有路由上。

GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上。最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器。它为请求业务以及路由的URI转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。

网关过滤器GatewayFilter
Path路径过滤器

RewritePath GatewayFilter Factory

RewritePath GatewayFilter工厂采用的路径regexp参数和replacement参数。这使用 Java 正则表达式来灵活地重写请求路径。

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: lb://user              # lb://根据服务名称从注册中心获取服务请求地址
          predicates:                 # 断言(判断条件)
            # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
            - Path=/api/user/**,/gateway/**
          filters:
            # 将/gateway/api/user/1,重写为/api/user/1
            # 注:在YAML 的格式中使用$\来代替$。
            - RewritePath=/gateway/?(?<segment>.*),/$\{segment}

注:在YAML 的格式中使用KaTeX parse error: Undefined control sequence: \来 at position 1: \̲来̲代替

PrefixPathGatewayFilterFacotry

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: lb://user      # lb://根据服务名称从注册中心获取服务请求地址
          predicates:                 # 断言(判断条件)
            - Path=/**       # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
          filters:
            # 将/1重写为/api/user/1
            - PrefixPath=/api/user

StripPrefixGatewayFilterFactory

StripPrefix网关过滤器工厂采用一个参数StripPrefix,该参数表示将再请求发送到下游之前从请求中剥离的路径个数

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: lb://user      # lb://根据服务名称从注册中心获取服务请求地址
          predicates:                 # 断言(判断条件)
            - Path=/**       # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
          filters:
            # 将/a/b/api/user/2 重写为/api/user/2
            - StripPrefix=2

SetPathGatewayFilterFactory

SetPath网关过滤器工厂采用路径模板参数,它提供了一种通过允许模板化路径段来操作请求路径的简单方法,使用了Spring Framework中的uri模板,允许多个匹配段。

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: lb://user      # lb://根据服务名称从注册中心获取服务请求地址
          predicates:                 # 断言(判断条件)
            - Path=/a/b/{segment}       # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
          filters:
            # 将/a/b/2,重写为/api/user/2
            - SetPath=/api/user/{segment}
参数过滤器

AddRequestParameter网关过滤器工厂会将指定参数添加到匹配到的下游请求中。

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: lb://user      # lb://根据服务名称从注册中心获取服务请求地址
          predicates:                 # 断言(判断条件)
            - Path=/api/user/**       # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
          filters:
            # 在下游请求中添加flag参数
            - AddRequestParameter=flag,1
状态过滤器

Status状态过滤器

SetStatus网关过滤器工厂采用单个状态参数,它必须是有效的Spring HttpStatus。它可以是整数404或者枚举NOT_FOUND的字符串表示。

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: lb://user      # lb://根据服务名称从注册中心获取服务请求地址
          predicates:                 # 断言(判断条件)
            - Path=/api/user/**       # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
          filters:
            # 任何情况下,响应的HTTP状态都将设置为404
            - SetStatus=404
自定义网关过滤器

自定义网关过滤器需要实现以下两个接口:GatewayFilterOrdered

创建过滤器
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 自定义网关过滤器
 */
public class CustomerGatewayFilter implements GatewayFilter , Ordered {
    private Logger log= LoggerFactory.getLogger(CustomerGatewayFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("自定义网关过滤器被执行--");
        // 继续向下执行,如果不满足则不调用chain.filter
        return chain.filter(exchange);
    }

    /**
     * 过滤器执行顺序,数值越小,优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}
注册网关过滤器
/**
 * 网关路由配置类
 */
@Configuration
public class GatewayRoutesConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder){
        return builder.routes().route(r -> r
                // 断言(判断条件)
                .path("/api/user/**")
                // 目标URI,路由到微服务的地址
                .uri("lb://user")
                // 注册自定义网关过滤器
                .filter(new CustomerGatewayFilter())
                // 路由ID,唯一
                .id("user-service2")).build();
    }
}
自定义过滤器工厂

在上面的自定义过滤器中,有没有办法自定义过滤器工厂类呢?这样就可以在配置文件中配置过滤器了。现在需要实现一个过滤器工厂。

自定义过滤器工厂继承AbstractGatewayFilterFactory,且自定义类名应按照"名称"+GatewayFilterFactory

package com.gateway;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;

/**
 * 命名规则必须按照"名称"+GatewayFilterFactory
 */
public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory <RequestTimeGatewayFilterFactory.Config>{

    private Logger log= LoggerFactory.getLogger(RequestTimeGatewayFilterFactory.class);

    private static final String KEY = "print";

    public RequestTimeGatewayFilterFactory(){
        super(Config.class);
    }

    /**
     * 通过shortcutFieldOrder方法设置Config配置类中的属性,
     * 默认按照shortcutFieldOrder顺序依次赋值
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(KEY);
    }

    @Override
    public GatewayFilter apply(Config config) {
        // grab configuration from Config object
        return (exchange, chain) -> {
//            //If you want to build a "pre" filter you need to manipulate the
//            //request before calling chain.filter
//            ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
//            //use builder to manipulate the request
//            return chain.filter(exchange.mutate().request(builder.build()).build());
            Long beginTime=System.currentTimeMillis();
            log.info("开始时间:{}",beginTime);
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                //ServerHttpResponse response = exchange.getResponse();
                //Manipulate the response in some way
                Long endTime=System.currentTimeMillis();
                if(config.isPrint()){
                    log.info("请求耗时:{}",endTime-beginTime);
                }
            }));
        };
    }

    public static class Config{
        //Put the configuration properties for your filter here
        private boolean print;

        public boolean isPrint(){
            return print;
        }

        public void setPrint(boolean print){
            this.print=print;
        }
    }
}

    @Bean
    public RequestTimeGatewayFilterFactory elapsedGatewayFilterFactory() {
        return new RequestTimeGatewayFilterFactory();
    }

配置文件

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: lb://user              # lb://根据服务名称从注册中心获取服务请求地址
          predicates:                 # 断言(判断条件)
            - Path=/api/user/**       # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
          filters:
            # 限流过滤器
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充速率
                redis-rate-limiter.burstCapacity: 2 # 令牌桶总容量
                key-resolver: '#{@keyResolver}' # 使用SpEL表达式按名称引用bean
            # 自定义的过滤器,参数print=true
            - RequestTime=true
全局过滤器

GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上。最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器。它为请求业务以及路由的URI转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。

包括:
在这里插入图片描述

通过全局过滤器统一鉴权

接下来我们自定义全局过滤器通过token判断用户是否登陆,完成统一鉴权

创建过滤器
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 权限验证过滤器
 */
@Component
public class AccessFilter implements GlobalFilter, Ordered {
    private Logger log= LoggerFactory.getLogger(AccessFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token=exchange.getRequest().getQueryParams().getFirst("token");
        if(token==null){
            log.warn("token is null");
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            DataBuffer dataBuffer=response.bufferFactory().wrap("result".getBytes());
            return response.writeWith(Mono.just(dataBuffer));
        }
        log.info("token is ok");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
网关限流

常见的限流算法有:

  • 计数器算法:时间范围内限制固定次数
  • 漏桶算法:往桶中任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃。会导致请求堆积,网关压力过大;丢弃请求;该算法只要是保护微服务,伤害自己。
  • 令牌桶算法:基于漏桶算法,能够限制请求的速率,也能允许一定程度的突发调用。该算法,存在一个桶,用于存放固定数量的令牌。算法存在一种机制,以一定的速率往桶中放令牌。每次请求调用都要先获取令牌,只有拿到令牌,才能继续执行,否则选择等待可用的令牌。或者直接拒绝。放令牌动作是持续不断的进行,如果桶中的令牌达到上线,就丢弃令牌。
    在这里插入图片描述

spring cloud gateway内部使用的就是该算法,大致描述如下:

  • 所有请求在处理之前需要拿到一个可用的令牌才会被处理;
  • 根据限流大小,设置按照一定的速率往桶中添加令牌
  • 桶设置最大的放置令牌限制,当桶满了,新添加的令牌就会被丢弃或者拒绝。
  • 请求到达后,首先获取令牌桶中的令牌,拿着令牌才可以进行其他业务逻辑。处理业务逻辑之后,将令牌直接删除。
  • 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流。
Gateway限流

添加依赖

        <!--spring dta redis reactive依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <!-- redis common pool2对象池依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

配置文件

spring:
  application:
    name: gateway
  # 路由规则
  cloud:
    gateway:
      routes:
        - id: user-service            # 路由ID,全局唯一
          uri: lb://user              # lb://根据服务名称从注册中心获取服务请求地址
          predicates:                 # 断言(判断条件)
            - Path=/api/user/**       # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
          filters:
            # 限流过滤器
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充速率
                redis-rate-limiter.burstCapacity: 2 # 令牌桶总容量
                key-resolver: '#{@pathKeyResolver}' # 使用SpEL表达式按名称引用bean

注册限流规则类

@Configuration
public class KeyResolverConfiguration {

    private Logger log= LoggerFactory.getLogger(KeyResolverConfiguration.class);

    /**
     * 按照URI限流
     * @return
     */
    @Bean
    public KeyResolver pathKeyResolver(){
        log.info("注册pathKeyResolver");
        // JDK 1.8
        return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
    }
}
    /**
     * 按照参数限流
     * @return
     */
    @Bean
    public KeyResolver keyResolver(){
        log.info("注册pathKeyResolver");
        // JDK 1.8
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
    }
    /**
     * 按照IP限流
     * @return
     */
    @Bean
    public KeyResolver keyResolver(){
        log.info("keyResolver");
        // JDK 1.8
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }

lver");
// JDK 1.8
return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
}
}


/**
 * 按照参数限流
 * @return
 */
@Bean
public KeyResolver keyResolver(){
    log.info("注册pathKeyResolver");
    // JDK 1.8
    return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}

/**
 * 按照IP限流
 * @return
 */
@Bean
public KeyResolver keyResolver(){
    log.info("keyResolver");
    // JDK 1.8
    return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值