关于Spring Cloud Gateway 学习记录

本文深入探讨SpringCloud Gateway,涵盖其架构、配置、路由管理、断言与过滤器功能,详解网关熔断、重试及限流机制,提供自定义过滤器与鉴权策略的实践案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

微服务网关:Spring Cloud Gateway

 

写之前 我先把官方的文档地址贴出来https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.0.RELEASE/single/spring-cloud-gateway.html

 

1.概述

Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。

网关提供API全托管服务,丰富的API管理功能,辅助企业管理大规模的API,以降低管理成本和安全风险,包括协议适配、协议转发、安全策略、防刷、流量、监控日志等功能。一般来说网关对外暴露的URL或者接口信息,我们统称为路由信息

Spring Cloud Gateway中的几个重要概念:

  •  

路由。路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配

  •  

断言。Java8中的断言函数。Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于http request中的任何信息,比如请求头和参数等。

 

  •  

过滤器。一个标准的Spring webFilter。Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理

 

server:

  port: 9527

 

spring:

  application:

    name: ruoyi-gateway

  devtools:

    restart:

      enabled: true

  profiles:

    active: dev

  cloud:

    config:

      fail-fast: true #快速配置客户端失败

      name: ${spring.application.name}

      profile: ${spring.profiles.active}

      discovery:

        enabled: true

        service-id: ruoyi-config

    gateway:

      discovery:

        locator:

          enabled: true

      routes:

        # 认证中心

        - id: ruoyi-auth

          uri: lb://ruoyi-auth

          predicates:

            - Path=/auth/**

          filters:

            # 验证码处理

            - CacheRequest

            - ImgCodeFilter

            - StripPrefix=1

        # 代码生成

        - id: ruoyi-gen

          uri: lb://ruoyi-gen

          predicates:

            - Path=/gen/**

          filters:

            - StripPrefix=1

        # dfs

        - id: ruoyi-dfs

          uri: lb://ruoyi-dfs

          predicates:

            - Path=/dfs/**

          filters:

            - StripPrefix=1

        #system 模块

        - id: ruoyi-system

          uri: lb://ruoyi-system

          predicates:

            - Path=/system/**

          filters:

            # 限流配置

            - StripPrefix=1

            - name: RequestRateLimiter

              args:

                key-resolver: '#{@remoteAddrKeyResolver}'

                redis-rate-limiter.replenishRate: 10

                redis-rate-limiter.burstCapacity: 20

              # 降级配置

            - name: Hystrix

              args:

                name: fallbackcmd

                fallbackUri: 'forward:/fallback'

 

eureka:

  client: #客户端注册进eureka服务列表内

    service-url:

       defaultZone: http://localhost:7001/eureka

       #defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/      

  instance:

    instance-id:  ${spring.application.name}:${server.port}

    prefer-ip-address: true     #访问路径可以显示IP地址      

 

hystrix:

    command:

      default:  #default全局有效,service id指定应用有效

        execution:

          timeout:

            enabled: true

          isolation:

            thread:

              timeoutInMilliseconds: 5000 #断路器超时时间,默认1000ms

 

上述配置信息 是一个比较全面的Spring Cloud Gateway 配置信息。注册中心基于eureka

第一种路由转发配置:

参数spring.cloud.gateway.discovery.locator.enabled为true,表明Gateway开启服务注册和发现的功能,并且Spring Cloud Gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。spring.cloud.gateway.discovery.locator.lowerCaseServiceId:true是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了)

已认证模块为例:http://localhost:9527/ruoyi-auth/user/info 这样的地址访问,网关将把请求转发到ruoyi-auth 这个服务上,ruoyi-auth是认证模块在eureka上的服务名称(小写)

 

第二种路由转发配置:

spring.cloud.gateway.

routes:

        # 认证中心

        - id: ruoyi-auth

          uri: lb://ruoyi-auth

          predicates:

            - Path=/auth/**

          filters:

            # 验证码处理

            - CacheRequest

            - ImgCodeFilter

            - StripPrefix=1

在上面的配置中,配置了一个Path的predicate(断言),将以/auth/**开头的请求都会转发到uri为lb://ruoyi-auth 的地址上,lb://ruoyi-auth(注册中心服务的名称)即ruoyi-auth服务的负载均衡地址,并用StripPrefix的filter在转发之前将/auth去掉。

已认证模块为例:http://localhost:9527/auth/user/info 这样的地址访问,网关将把请求转发到ruoyi-auth 这个服务上,ruoyi-auth是认证模块在eureka上的服务名称(小写)

 

 

跨域:

spring:

  cloud:

    gateway:

      globalcors:

        corsConfigurations:

          '[/**]':

            allowedOrigins: "http://docs.spring.io"

            allowedMethods:

            - GET

上边这段配置是粘贴的官方的,上面的配置的意思为 允许来自http://docs.spring.io 的get请求访问。 还可以加上 allowHeaders: - Content- Type 这么一段配置,意思是服务器允许请求头中携带Content-Type 字段。

我自己测了一下 发现这个跨域好像不太好使,所以我用了下面的代码:

@Configuration

public class GlobalCorsConfig {

@Bean

    public CorsWebFilter corsFilter() {

        CorsConfiguration config = new CorsConfiguration();

        // cookie跨域

        config.setAllowCredentials(Boolean.TRUE);

        config.addAllowedMethod("*");

        config.addAllowedOrigin("*");

        config.addAllowedHeader("*");

        // 配置前端js允许访问的自定义响应头

        config.addExposedHeader("setToken");

 

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());

        source.registerCorsConfiguration("/**", config);

 

        return new CorsWebFilter(source);

    }

 

}

 

 

 

 

 

Spring Cloud Gateway的filter生命周期不像Zuul那么丰富,它只有两个:“pre”和“post”:

  •  

pre:这种过滤器在请求被路由之前调用。可以利用这个过滤器实现身份验证、在集群中选择请求的微服务、记录调试的信息。

  •  
  •  

post:这种过滤器在路由到服务器之后执行。这种过滤器可用来为响应添加HTTP Header、统计信息和指标、响应从微服务发送给客户端等。

  •  

 

Spring Cloud gateway的filter分为两种:GatewayFilter和Globalfilter。GlobalFilter会应用到所有的路由上,而Gatewayfilter将应用到单个路由或者一个分组的路由上。

 

利用Gatewayfilter可以修改请求的http的请求或者是响应,或者根据请求或者响应做一些特殊的限制。更多时候可以利用Gatewayfilter做一些具体的路由配置。

 

下面的配置是AddRequestParameter Gatewayfilter的相关配置。

spring.cloud.gateway.

 

routes:
     -  id : parameter_route
      uri: http: //localhost:8504/user/info
      filters:
      - AddRequestParameter=foo, bar
      predicates:
      - Method=GET

 

上述配置中指定了转发的地址,设置所有的GET方法都会自动添加foo=bar,当请求符合上述路由条件时,即可在后端服务上接收到Gateway网关添加的参数。

 

另外再介绍一种比较常用的filter,即StripPrefix gateway filter。

spring:
  cloud:
    gateway:
      routes:
         - id: ruoyi-gen

          uri: lb://ruoyi-gen

          predicates:

            - Path=/gen/**

          filters:

            - StripPrefix=1

 

     它会把地址 http://localhost:9527/gen/user/getname 转变为

http://ruoyi-gen/user/getname

 

Gateway请求匹配spring cloud gateway 中断言的种类多达10种

 

我用到的是- Path 这个,根据请求路径来筛选满足条件的请求。

 

 

 

 

Gateway 熔断

Spring Cloud Gateway也可以利用Hystrix的熔断特性,在流量过大时进行服务降级,同时项目中必须加上Hystrix的依赖

spring:
  cloud:
    gateway:
      routes:

        #system 模块

        - id: ruoyi-system

          uri: lb://ruoyi-system

          predicates:

            - Path=/system/**

          filters:

            # 限流配置

            - StripPrefix=1

            - name: RequestRateLimiter

              args:

                key-resolver: '#{@remoteAddrKeyResolver}'

                redis-rate-limiter.replenishRate: 10

                redis-rate-limiter.burstCapacity: 20

              # 降级配置

            - name: Hystrix

              args:

                name: fallbackcmd

                fallbackUri: 'forward:/fallback'

 

 

 

hystrix:

    command:

      default:  #default全局有效,service id指定应用有效

        execution:

          timeout:

            enabled: true

          isolation:

            thread:

              timeoutInMilliseconds: 5000 #断路器超时时间,默认1000ms

上述配置种给出了熔断之后的返回路径。因此,在Gateway服务模块添加/fallback路径,以作为服务熔断时的返回路径。方法有多种, 常见的@RestControll,我这里介绍另一种写法:

 

熔断之后的处理 handler:

@Slf4j

@Component

public class HystrixFallbackHandler implements HandlerFunction<ServerResponse>

{

    @Override

    public Mono<ServerResponse> handle(ServerRequest serverRequest)

    {

        Optional<Object> originalUris = serverRequest.attribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);

        originalUris.ifPresent(originalUri -> log.error("网关执行请求:{}失败,hystrix服务降级处理", originalUri));

        return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).contentType(MediaType.TEXT_PLAIN)

                .body(BodyInserters.fromObject("服务异常"));

    }

}

 

路由配置:

@Configuration

@AllArgsConstructor

public class RouterFunctionConfiguration

{

    private final HystrixFallbackHandler hystrixFallbackHandler;

 

    @Bean

    public RouterFunction<?> routerFunction()

    {

        return RouterFunctions

                .route(RequestPredicates.path("/fallback").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),

                        hystrixFallbackHandler);

    }

}

 

 

Gateway 重试路由器

 

Retry GatewayFilter通过四个参数来控制重试机制,参数说明如下:

  •  

retries:重试次数,默认值是 3 次。

  •  
  •  

statuses:HTTP 的状态返回码,取值请参考:org.springframework.http.HttpStatus。

  •  
  •  

methods:指定哪些方法的请求需要进行重试逻辑,默认值是 GET 方法,取值参考:org.springframework.http.HttpMethod。

  •  
  •  

series:一些列的状态码配置,取值参考:org.springframework.http.HttpStatus.Series。符合的某段状态码才会进行重试逻辑,默认值是 SERVER_ERROR,值是 5,也就是 5XX(5 开头的状态码),共有5个值。

  •  

 配置方法可参考官方文档。

 

 

Gateway 的限流操作。

Spring Cloud Gateway 本身集成了限流操作。Gateway限流操作需要使用redis。

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

 

 

spring:

  redis:

    database: 1

    host: 10.1.4.182

    port: 6379

    password:      # 密码(默认为空)

    timeout: 6000ms  # 连接超时时长(毫秒)

    lettuce:

      pool:

        max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)

        max-wait: -1ms      # 连接池最大阻塞等待时间(使用负值表示没有限制)

        max-idle: 10      # 连接池中的最大空闲连接

        min-idle: 5       # 连接池中的最小空闲连接

这是redis的配置信息。

限流的配置我这里就贴出关键点,完整配置上边贴过了:

 filters:

            # 限流配置

            - StripPrefix=1

            - name: RequestRateLimiter

              args:

                key-resolver: '#{@remoteAddrKeyResolver}'

                redis-rate-limiter.replenishRate: 10

                redis-rate-limiter.burstCapacity: 20

RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:

BurstCapacity:令牌桶的总容量。

replenishRate:令牌通每秒填充平均速率。

Key-resolver:用于限流的解析器的Bean对象的名字。它使用SpEL表达式#{@beanName}从Spring容器中获取bean对象。

 

Key-resolver参数后面的bean需要自己实现,然后注入到Spring容器种。KeyResolver需要实现resolve方法,比如根据ip进行限流,则需要用hostAddress去判断。实现完KeyResolver之后,需要将这个类的Bean注册到Ioc容器中。还可以根据uri限流,同hostname限流是一样的。例如以ip限流为例,在gateway模块中添加以下实现:

/**

 * 路由限流配置

 */

@Configuration

public class RateLimiterConfiguration

{

    @Bean(value = "remoteAddrKeyResolver")

    public KeyResolver remoteAddrKeyResolver()

    {

        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());

    }

}

 

自定义Gatewayfilter

关于自定义gatewayfilter 我这里要将两种方式:

第一种需要实现GatewayFilter和Ordered这两个接口。

/**

 * 当前这种方式只能使用手动加入路由的方式, 配置不好使

 * @author XJin

 *

 */

 

public class RequestTimeFilter implements GatewayFilter,Ordered{

@Override

public int getOrder() {

return 0;

}

@Override

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

exchange.getAttributes().put("requestTimeBegin", System.currentTimeMillis());

return chain.filter(exchange).then(

Mono.fromRunnable(()->{

Long startTime = exchange.getAttribute("requestTimeBegin");

if(startTime!=null) {

System.err.println("请求路径:"+exchange.getRequest().getURI().getRawPath() + "消耗时间: " + (System.currentTimeMillis() - startTime) + "ms");

}

}));

}

 

}

这种方式在application.yml种配置不好使,可能是我自己的原因,我这边采用的是:

    @Bean

    public RouteLocator routeLocator(RouteLocatorBuilder builder) {

        return builder.routes().route(r ->

                r.path("/auth/*")

                        //转发路由

                        .uri("lb://ruoyi-auth")

                        //注册自定义过滤器

                        .filters(new RequestTimeFilter())

                        //给定id

                        .id("ruoyi-auth"))

                .build();

}

 

我在项目中使用的是第二种方式:

/**

 * 当前这种方式只能使用手动加入路由的方式, 配置不好使

 * @author XJin

 *

 */

@Component

public class RequestTimeFilter2 extends AbstractGatewayFilterFactory<RequestTimeFilter2.Config>{

    public RequestTimeFilter2()

    {

        super(Config.class);

    }

    public static class Config

    {

    }

    @Override

    public String name()

    {

        return "RequestTimeFilter2";

    }

@Override

public GatewayFilter apply(Config config) {

return new RequestTimeFilter();

}

 

}

这里边用到了第一种方法里的代码了,这种方式写的话可以在application.yml种配置:

 

 

 

自定义GlobalFilter

Spring Cloud Gateway根据作用范围分为GatewayFilter和GlobalFilter,二者区别如下:

  •  

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

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

 

 

/**

 * 网关鉴权

 */

@Slf4j

@Component

public class AuthFilter implements GlobalFilter, Ordered

{

    // 排除过滤的 uri 地址

    // swagger排除自行添加

    private static final String[]           whiteList = {"/auth/login", "/user/register", "/system/v2/api-docs"};

 

    @Resource(name = "stringRedisTemplate")/*RedisTemplate和StringRedisTemplate的区别:https://blog.youkuaiyun.com/guanripeng/article/details/89884167*/

    private ValueOperations<String, String> ops;

 

    @Override

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)

    {

        String url = exchange.getRequest().getURI().getPath();

        log.info("url:{}", url);

        // 跳过不需要验证的路径

        if (Arrays.asList(whiteList).contains(url))

        {

            return chain.filter(exchange);

        }

        String token = exchange.getRequest().getHeaders().getFirst(Constants.TOKEN);

        // token为空

        if (StringUtils.isBlank(token))

        {

            return setUnauthorizedResponse(exchange, "token can't null or empty string");

        }

        String userStr = ops.get(Constants.ACCESS_TOKEN + token);

        JSONObject jo = JSONObject.parseObject(userStr);

        String userId = jo.getString("userId");

        // 查询token信息

        if (StringUtils.isBlank(userId))

        {

            return setUnauthorizedResponse(exchange, "token verify error");

        }

        // 设置userId到request里,后续根据userId,获取用户信息

        ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(Constants.CURRENT_ID, userId)

                .header(Constants.CURRENT_USERNAME, jo.getString("loginName")).build();

        ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();

        return chain.filter(mutableExchange);

    }

 

    private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange, String msg)

    {

        ServerHttpResponse originalResponse = exchange.getResponse();

        originalResponse.setStatusCode(HttpStatus.UNAUTHORIZED);

        originalResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");

        byte[] response = null;

        try

        {

            response = JSON.toJSONString(R.error(401, msg)).getBytes(Constants.UTF8);

        }

        catch (UnsupportedEncodingException e)

        {

            e.printStackTrace();

        }

        DataBuffer buffer = originalResponse.bufferFactory().wrap(response);

        return originalResponse.writeWith(Flux.just(buffer));

    }

 

    @Override

    public int getOrder()

    {

        return -200;

    }

}

 

代码相对比较多主要是做一个token验证,并根据token取出登陆相关消息以便后续功能使用。针对所有请求。

 

 

 

下面是 关于Spring Cloud Gateway predicate(路由断言工厂)

https://www.cnblogs.com/wgslucky/p/11396579.html 讲的比较详细

 

 

关于Spring Cloud Gateway的全局过滤器

GlobalFilter 有十一个实现类,包括路由转发、负载均衡、ws 路由、netty 路由等全局过滤器

https://www.cnblogs.com/liukaifeng/p/10055862.html 介绍的比较详细如有需要查参考

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值