前言

2021.11.14,spring cloud 以前只零碎用过几个组件,这次系统学一遍

环境:SpringBoot 2.4.2SpringCloud 2020.0.1SpringCloud Alibaba 2021.1JDK 8MYSQL 5.7

这个系列只讲实现,基本不会深入原理。


代码仓库 gateway-demo 分支Learning Use Cases/Spring Cloud Demo - Gitee.comopen in new window

官方文档:Spring Cloud Gatewayopen in new window

本文介绍 spring cloud 统一 API 网关框架 gateway。它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

Spring Cloud Gateway 底层使用了高性能的通信框架Netty

Spring Cloud Gateway 提供统一的路由方式,且基于 Filter 链的方式,提供了网官基本的功能,例如:安全、监控、指标、限流。

image-20211114133115818


核心概念

Spring Cloud Gateway 有三大核心概念,路由、断言、过滤。

watermarkuZ3poZW5naGVpdG

Route 路由

路由是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。


Predicate 断言

这是一个 Java 8 的 Predicate。

输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。

通俗点讲,就是路由的判断条件。


Filter 过滤器

请求到达网关后,先在 Gateway Handler Mapping 中找到与请求相匹配的路由,将请求发送到 Gateway Web Mapping

这里的 Filter 过滤器与 java 中的拦截器类似,分为了 请求处理之前 和 处理之后 两个方法。

  • 处理前 过滤方法可以用来:登录校验、权限校验、流量监控、请求日志输出、协议转换等
  • 处理后 过滤方法可以完成:处理日志输出、数据额外包装。


Gateway 入门使用

pom 依赖:

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

启动类需要注册到注册中心:

@EnableEurekaClient
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

application.yml 简单路由演示:

server:
  port: 36500
  
spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
          enabled: true
      routes:
        - id: consumer-order-routh    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:56400  #匹配后提供服务的路由地址
          predicates:
            - Path=/order/payByOrderId         # 断言,路径相匹配的进行路由

使用网关服务 36500 端口,调用客户服务 56400 的方法:

image-20211122195032766

还有一种 java 代码配置的路由,但是有点复杂,还是配置来得容易,看看就行

@Configuration
public class GatewayRoute {
    @Bean
    public RouteLocator shivaShowRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("mt_route", r -> r.path("/archives").uri("https://tech.meituan.com"));
        return routes.build();
    }
}

image-20211122195314926


动态路由

将IP端口换为注册中心的服务名,就可以不关心地址,并且可以自动进行负载均衡

image-20211122215348951

      routes:
        - id: consumer-order-routh    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://CONSUMER-ORDER #匹配后提供服务的路由地址
          predicates:
            - Path=/order/payByOrderId         # 断言,路径相匹配的进行路由

断言详解

利用 Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。

如果匹配不成功,会报 404 .

spring-cloud-gateway3

通过时间匹配

Predicate 支持设置一个时间,在请求进行转发的时候,可以通过判断在这个时间之前或者之后进行转发。

断言参数作用示例
After指定时间后- After=2019-01-01T00:00:00+08:00[Asia/Shanghai]
Before指定时间前- Before=2019-01-01T00:00:00+08:00[Asia/Shanghai]
Betwee指定时间区间- Between=2019-01-01T00:00:00+08:00[Asia/Shanghai], 2019-07-01T00:00:00+08:00[Asia/Shanghai]

但是这个时间是 Spring 是通过 ZonedDateTime 的时间,和我们正常格式不太一样。

需要用到了 main 跑一下就行。


Cookie Route Predicate 可以接收两个参数,一个是 Cookie name ,一个是正则表达式。

路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。

routes:
  - id: gateway-service
  uri: https://www.baidu.com
  predicates:
    - Cookie=sessionId, 123456

这个路由表示,存在 sessionId=123456cookie 才匹配成功。


通过 Header 属性匹配

Header Route Predicate 和 Cookie Route Predicate 一样,也是接收 2 个参数,一个 header 中属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。

routes:
  - id: gateway-service
  uri: https://www.baidu.com
  predicates:
    - Header=X-Request-Id, \d+

通过 Host 匹配

Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。它通过参数中的主机地址作为匹配规则。

routes:
  - id: gateway-service
  uri: https://www.baidu.com
  predicates:
    - Host=**.baidu.com,**.shiva.show

通过请求方式匹配

可以通过是 POST、GET、PUT、DELETE 等不同的请求方式来进行路由。

routes:
  - id: gateway-service
  uri: https://www.baidu.com
  predicates:
    - Method=GET

通过请求路径匹配

Path Route Predicate 接收一个匹配路径的参数来判断是否走路由。

routes:
  - id: gateway-service
  uri: https://www.baidu.com
  predicates:
    - Path=/payment/{segment},/customer/{segment}

这个不需要解释


通过请求参数匹配

Query Route Predicate 支持传入两个参数,一个是属性名一个为属性值,属性值可以是正则表达式。

routes:
  - id: gateway-service
  uri: https://www.baidu.com
  predicates:
    - Query=smile

只传入参数名,只要请求中包含 smile 属性的参数即可匹配路由。


通过请求 ip 匹配

Predicate 也支持通过设置某个 ip 区间号段的请求才会路由,RemoteAddr Route Predicate 接受 cidr 符号(IPv4 或 IPv6 )字符串的列表(最小大小为1),例如 192.168.0.1/16 (其中 192.168.0.1 是 IP 地址,16 是子网掩码)。

routes:
  - id: gateway-service
  uri: https://www.baidu.com
  predicates:
    - RemoteAddr=192.168.1.1/24

Filter

Spring Cloud Gateway的过滤器分为 prepost 两种方式。

  • pre:客户端的请求先经过“pre”类型的filter,然后将请求转发到具体的业务服务
  • post:收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端

QQLUQKXENLTWQP5EMD

然后按类型,还分为 GatewayFilterGatewayFilter,这些都是内置的过滤器。

  • GatewayFilter : 需要通过 spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过 spring.cloud.default-filters 配置在全局,作用在指定的路由上。
  • GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过 GatewayFilterAdapter 包装成 GatewayFilterChain 可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。

然后把对应文档放上,需要用到了查一下,功能都比较简陋:

自定义过滤器

自定义过滤器可以帮我们实现:全局日志、统一鉴权,只要就这两。

自定义过滤器 示范:

@Slf4j
@Component
public class GatewayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取token
        List<String> authorization = exchange.getRequest().getHeaders().get("Authorization");
        if (authorization == null || authorization.size() == 0) {
            //没有 token 信息,直接返回
            log.info("【请求链接】:{},请求无 token 令牌", exchange.getRequest().getURI());
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        // 打印日志
        log.info("【请求链接】:{},【请求参数】:{}", exchange.getRequest().getURI(), exchange.getRequest().getQueryParams());

        //转到下一个过滤器
        return chain.filter(exchange);
    }

    /**
     * 加载过滤器的顺序,越小优先级越高
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

请求控制台输出:

c.s.demo.gateway.config.GatewayFilter    : 【请求链接】:http://localhost:36500/order/payByOrderId?id=778,【请求参数】:{id=[778]}

参考文章

尚硅谷SpringCloud框架开发教程(SpringCloudAlibaba微服务分布式架构丨Spring Cloud)_哔哩哔哩_bilibiliopen in new window

跟我学SpringCloud | 第十二篇:Spring Cloud Gateway初探 - 极客挖掘机 - 博客园 (cnblogs.com)open in new window

回到疯狂创客圈-Java高并发社群 (cnblogs.com)open in new window

Spring cloud gateway 详解和配置使用(文章较长)_荡漾-CSDN博客open in new window

spring cloud gateway之filter篇_方志朋的专栏-CSDN博客open in new window