资讯详情

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

概述

定义

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

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特性如下:

网关作为系统唯一的流量入口,包装内部系统的架构,所有要求都首先通过网关,从网关到合适的微服务,优点如下:

  • 简化客户端的工作。网关封装微服务后,客户端只需与网关互动,不需要调用不同的服务。
  • 降低函数之间的耦合度。 服务界面一旦修改,只需修改网关的路由策略,不需要修改调用该函数的每个客户端,从而减少程序间的耦合。
  • 解放开发人员专注于业务逻辑的实现。服务路由(灰度和ABTest)、非业务相关功能,如负载平衡、访问控制、流控熔断降级等,不需要每项服务 API 实现时要考虑。
  • 在前端和后端分离的总体趋势下,大多数浏览器安全同源策略都存在前端要求的跨域问题,网关也是解决跨域问题的完美方法。

流量网关与微服务网关

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

主流网关

  • 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 的核心术语

  • (路由):网关的基本组成部分。它由一个ID、一个目标URI、一组谓词和一组过滤器定义。如果聚合谓词或断言是真的,则匹配路由。
  • (谓词):这是一个Java 8函数谓词,输入类型Spring Framework serverwebexchange,匹配HTTP请求中的任何内容,如头部或参数。
  • (过滤器):这些是由特定工厂建造的GatewayFilter例如,请求和响应可以在发送下游请求之前或之后修改。

工作流程

  • 客户端向Spring Cloud Gateway发出请求。
  • 如果网关处理程序映射决定匹配路由的请求,则将其发送到网关Web处理程序。
  • 该处理程序通过特定于该请求的过滤器链运行请求。虚线分隔过滤器的原因是过滤器可以在发送代理请求之前和之后运行逻辑。
  • 执行所有预筛选逻辑。然后发出代理请求。
  • 发出代理请求后,操作post过滤器逻辑。

实战

加依赖

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

<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><dependency>             <groupId>org.springframework.cloud</groupId>             <artifactId>spring-cloud-starter-gateway</artifactId>         </dependency></pre> 

如果启动器被引入,但不想使用网关,可以设置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、断言集合和过滤器集合

网关路由初体验

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

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

<pre class="prettyprint hljs dust" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><?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></pre>

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

<pre class="prettyprint hljs yaml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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</pre>

启动网关微服务

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

整合Nacos

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

<pre class="prettyprint hljs yaml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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来关闭动态刷新,我们使用注册中心场景大部分就是动态感知,因此基本使用默认的</pre>

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

<pre class="prettyprint hljs yaml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">server:
 port: 4090
spring:
 cloud:
 gateway:
 routes:
 - id: storage_route
 uri: lb://ecom-storage-service
 predicates:
 - Path=/storage-service/**
 filters:
 - StripPrefix=1</pre>

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

<pre class="prettyprint hljs yaml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">spring:
 cloud:
 nacos:
 discovery:
 server-addr: ${spring.cloud.nacos.server-addr}
 group: ecom-group
 namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01      
 username: itsx
 password: itxs123</pre>

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

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

路由断言工厂

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

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

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

将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之间可以访问

<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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;
        }
    }
}</pre>

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

<pre class="prettyprint hljs yaml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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</pre>

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

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

标签: 4080连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台