3

SpringCloud Gateway微服务网关实战与源码分析-上 - itxiaoshen

 1 year ago
source link: https://www.cnblogs.com/itxiaoshen/p/16460647.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

SpringCloud Gateway微服务网关实战与源码分析-上

Spring Cloud Gateway 官网地址 https://spring.io/projects/spring-cloud-gateway/ 最新版本3.1.3

Spring Cloud Gateway 文档地址 https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/

Spring Cloud Gateway GitHub源码地址 https://github.com/spring-cloud/spring-cloud-gateway

Spring Cloud Gateway使用了WebFlux技术,而WebFlux技术底层又基于高性能的Reactor模式通信框架Netty。Spring Cloud Gateway基于Spring 5、Spring Boot 2和project Reactor技术上构建异步非阻塞的高吞吐量API网关,提供一种简单且有效的方式来路由到API,并为它们提供横切关注点如安全性、监控/指标和弹性等。Spring Cloud Gateway特性如下:

Spring Cloud Gateway特性如下:

  • 能够在任何请求属性上匹配路由。
  • 谓词和过滤器是特定于路由的。
  • 集成断路器。
  • 集成Spring Cloud DiscoveryClient
  • 编写谓词和过滤器编写易用。
  • 限制请求速率。
image-20220707155604657

网关作为系统的唯一流量入口,封装内部系统的架构,所有请求都先经过网关,由网关将请求路由到合适的微服务,优势如下:

  • 简化客户端的工作。网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务。
  • 降低函数间的耦合度。 一旦服务接口修改,只需修改网关的路由策略,不必修改每个调用该函数的客户端,从而减少了程序间的耦合性。
  • 解放开发人员把精力专注于业务逻辑的实现。由网关统一实现服务路由(灰度与ABTest)、负载均衡、访问控制、流控熔断降级等非业务相关功能,而不需要每个服务 API 实现时都去考虑。
  • 现在前后端分离大趋势下,目前大部分浏览器安全同源策略出现前端请求的跨域问题,网关也是解决跨域问题的一种较完美方式。

流量网关与微服务网关

image-20220707155407770

流量网关(如典型Nginx网关)是指提供全局性的、与后端业务应用无关的策略,例如 HTTPS证书卸载、Web防火墙、全局流量监控等。而微服务网关(如Spring Cloud Gateway)是指与业务紧耦合的、提供单个业务域级别的策略,如服务治理、身份认证等。也就是说,流量网关负责南北向流量调度及安全防护,微服务网关负责东西向流量调度及服务治理。

image-20220707154737455
  • Kong 网关:Kong 的性能非常好,非常适合做流量网关,但是对于复杂系统不建议业务网关用 Kong,主要是工程性方面的考虑。基于OpenResty或Nginx+Lua实现。
  • Zuul1.x 网关:Zuul 1.0 的落地经验丰富,但是性能差、基于同步阻塞IO,适合中小架构,不适合并发流量高的场景,因为容易产生线程耗尽,导致请求被拒绝的情况。
  • Gateway 网关:功能强大丰富,性能好,官方基准测试 RPS (每秒请求数)是Zuul的1.6倍,能与 SpringCloud 生态很好兼容,单从流式编程+支持异步上也足以让开发者选择它了。
  • Zuul 2.x:性能与 gateway 差不多,基于非阻塞的,支持长连接,但 SpringCloud 没有集成 zuul2 的计划,并且 Netflix 相关组件都宣布进入维护期,前景未知。

从发展趋势上看,Spring Cloud Gateway作为Spring Cloud生态体系中的网关,目标替代Netflix的Zuul且势在必行。

进一步研究 Spring Cloud Gateway 的配置及其使用之前,我们先了解几个 Spring Cloud Gateway 的核心术语

  • Route(路由):网关的基本组成部分。它由一个ID、一个目标URI、一组谓词和一组过滤器定义。如果聚合谓词或者说断言为真,则匹配路由。
  • Predicate(谓词):这是一个Java 8函数谓词,输入类型是Spring Framework serverwebexchange,匹配HTTP请求中的任何内容,如头或参数。
  • Filter(过滤器):这些是使用特定工厂构建的GatewayFilter实例,可以在发送下游请求之前或之后修改请求和响应。
image-20220708110347157
image-20220708105636341
  • 客户端向Spring Cloud Gateway发出请求。
  • 如果网关处理程序映射决定一个请求匹配一个路由,它被发送到网关Web处理程序。
  • 此处理程序通过特定于该请求的过滤器链运行请求。用虚线分隔过滤器的原因是,过滤器可以在发送代理请求之前和之后运行逻辑。
  • 执行所有“预”筛选逻辑。然后发出代理请求。
  • 发出代理请求后,运行“post”过滤器逻辑。

项目或模块中加入spring-cloud-starter-gateway

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

如果引入了启动器,但又不希望启用网关,则可以通过设置spring.cloud.gateway.enabled=false来禁用。全部详细配置可以查阅官网,Spring Cloud Gateway详细配置说明 https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/appendix.html

官网提供两种配置谓词和过滤器的方法,分别是shortcuts and fully expanded arguments,译为快捷方式和完全扩展的参数方式,后续例子我们都使用快捷方式,这种方式简洁舒畅,官方的例子也大都是使用快捷方式。

路由配置Route 主要由路由id、目标uri、断言集合和过滤器集合组成

  • id:路由标识,要求唯一,名称任意(默认值 uuid,一般不用,需要自定义)
  • uri:请求最终被转发到的目标地址
  • order: 路由优先级,数字越小,优先级越高
  • predicates:断言数组,即判断条件,如果返回值是boolean,则转发请求到 uri 属性指定的服务中
  • filters:过滤器数组,在请求传递过程中,对请求做一些修改

网关路由初体验

利用前面库存微服务提供的deduct接口,端口为4080,启动库存微服务,访问http://localhost:4080/deduct,显示成功

image-20220708180815560

创建网关微服务模块,pom文件依赖如下,由于后面我们有Gateway整合Nacos和Sentinel的示例,所以这里把其他依赖也先加进来

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>simple-ecommerce</artifactId>
        <groupId>cn.itxs</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>ecom-gateway</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <name>ecom-gateway</name>
    <description>a simple electronic commerce platform demo tutorial for gateway service</description>

    <properties>
        <spring-cloud-loadbalancer.version>3.1.3</spring-cloud-loadbalancer.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
            <version>${spring-cloud-loadbalancer.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
    </dependencies>

</project>

bootstrap.yml配置文件加入建议路由配置如下

server:
  port: 4090
spring:
  application:
    name: ecom-gateway
  cloud:
    gateway:
      routes:
        - id: storage_route
          uri: http://localhost:4080
          predicates:
            - Path=/storage-service/**
          filters:
            - StripPrefix=1

启动网关微服务

image-20220708180700568

访问网关提供api接口http://localhost:4090/storage-service/deduct,匹配storage-service为真后通过过滤器去掉一层也即是storage-service路径去掉,然后转发至uri地址,最终转发url为http://localhost:4080/deduct ,成功返回结果

image-20220708181428005

整合Nacos

本地配置文件bootstrap.yml改为如下,commons-dev.yaml包含服务注册的组

spring:
  application:
    name: ecom-gateway
  profiles:
    active: dev
  cloud:
    nacos:
      # 注册中心信息放在配置中心上,每个程序一般只配置配置中心的信息
      server-addr: 192.168.50.95:8848
      config:
        server-addr: ${spring.cloud.nacos.server-addr}
        file-extension: yaml
        namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01
        group: gateway-group
        extension-configs:
          - dataId: commons-dev.yaml
            group: commons-group
            refresh: true
        username: itsx
        password: itxs123
        enabled: true # 默认为true,设置false 来完全关闭 Spring Cloud Nacos Config
        refresh-enabled: true # 默认为true,当变更配置时,应用程序中能够获取到最新的值,设置false来关闭动态刷新,我们使用注册中心场景大部分就是动态感知,因此基本使用默认的

将路由配置也一并放在Nacos中配置ecom-gateway-dev.yaml,内容如下,uri这里使用的是库存微服务名称,lb是做负载均衡处理

server:
  port: 4090
spring:
  cloud:
    gateway:
      routes:
        - id: storage_route
          uri: lb://ecom-storage-service
          predicates:
            - Path=/storage-service/**
          filters:
            - StripPrefix=1

Nacos中commons-dev.yaml的关于Nacos注册中心使用配置如下,库存微服务也是使用这个,服务注册和发现都在ecom-group组

spring:
  cloud:
    nacos:
      discovery:
        server-addr: ${spring.cloud.nacos.server-addr}
        group: ecom-group
        namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01      
        username: itsx
        password: itxs123
image-20220708183133992

启动库存微服务和网关微服务,都注册到同一个组里面

image-20220708183031348

再次访问http://localhost:4090/storage-service/deduct ,正常返回结果,到此我们已经成功整合Nacos

路由断言工厂

Route Predicate Factories为路由断言工厂,官网提供12种路由工厂,如果都没有满足你的需求,还可以自定义路由断言工厂

image-20220708113412918

我们先配置一个未来时间的after断言- After=2022-07-09T23:42:47.789-08:00[Asia/Shanghai]

image-20220709001959822

可以直接访问本机IP,返回一个错误的页面

image-20220709105659263

将after断言改为- Before=2022-07-09T23:42:47.789-08:00[Asia/Shanghai]后则可以正常访问。

  • 其他还有许多规则详细可以查阅官网,如下面,各位可以自己一一尝试
    • Between=2022-07-08T23:42:47.789-08:00[Asia/Shanghai], 2022-07-09T23:42:47.789-08:00[Asia/Shanghai]
    • Cookie=chocolate, ch.p
    • Header=X-Request-Id, \d+
    • Host=**.somehost.org,anotherhost.org
    • Method=GET,POST
    • Query=green
    • RemoteAddr=192.168.1.1/24
    • Weight=group1, 8 Weight=group1, 2
    • XForwardedRemoteAddr=192.168.1.1/24

自定义路由断言工厂

当官方提供的所有断言工厂无法满足业务需求时,还可以自定义断言工厂。添加自定义断言工厂类自定断言工厂主要注意一下几点:

  • 需要声明是Springboot的Bean,添加注解@Component,名称必须以RoutePredicateFactory结尾,这个是命名约束。如果不按照命名约束来命名,那么就会找不到该断言工厂。前缀就是配置中配置的断言。
  • 可以直接复制Gateway中已经实现的断言工厂,修改对应的内容,避免踩坑。
  • 继承父类AbstractRoutePredicateFactory,并重写方法。
  • 需要定义一个Config静态内部类,声明属性来接收 配置文件中对应的断言的信息。
  • 在重写的shortcutFieldOrder方法中,绑定Config中的属性。传入数组的内容需要与Config中的属性一致。
  • 在重写的apply方法中,实现具体验证逻辑, true就是匹配成功 false匹配失败。

新建一个库存数量的路由断言工厂QuantityRoutePredicateFactory.java,如库存在100和200之间可以访问

package cn.itxs.ecom.gateway.factory;

import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

// 自定义路由断言工厂
@Component
public class QuantityRoutePredicateFactory extends AbstractRoutePredicateFactory<QuantityRoutePredicateFactory.Config>{


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

    // 将配置文件中的值按返回集合的顺序,赋值给配置类
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(new String[]{"minQuantity", "maxQuantity"});
    }

    @Override
    public Predicate<ServerWebExchange> apply(Consumer<Config> consumer) {
        return super.apply(consumer);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        // 创建网关断言对象
        // 检查
        return serverWebExchange -> {
            // TODO 获取请求参数age,判断是否满足如配置的[100, 200)
            MultiValueMap<String, String> queryParams = serverWebExchange.getRequest().getQueryParams();
            String quantity = queryParams.getFirst("quantity");
            if (StringUtils.hasText(quantity) && quantity.matches("[0-9]+")) {
                int iQuantity = Integer.parseInt(quantity);
                if (iQuantity >= config.getMinQuantity() && iQuantity < config.getMaxQuantity()) {
                    return true;
                }
            }
            return false;
        };
    }


    // 配置类,属性用于接收配置文件中的值
    @Validated
    public static class Config {
        private int minQuantity;
        private int maxQuantity;

        public int getMinQuantity() {
            return minQuantity;
        }

        public void setMinQuantity(int minQuantity) {
            this.minQuantity = minQuantity;
        }

        public int getMaxQuantity() {
            return maxQuantity;
        }

        public void setMaxQuantity(int maxQuantity) {
            this.maxQuantity = maxQuantity;
        }
    }
}

Nacos网关的配置中增加自定义路由断言工厂配置Quantity

server:
  port: 4090
spring:
  cloud:
    gateway:
      routes:
        - id: storage_route
          uri: lb://ecom-storage-service
          predicates:
            - Path=/storage-service/**
            - Quantity=100,200
          filters:
            - StripPrefix=1

启动网关微服务,访问http://localhost:4090/storage-service/deduct?quantity=99 ,没有匹配路由策略

image-20220709113544247

而访问http://localhost:4090/storage-service/deduct?quantity=100 ,能够正确返回库存微服务接口结果

image-20220709113604546

**本人博客网站 **IT小神 www.itxiaoshen.com


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK