-->
侧边栏壁纸
博主头像
断钩鱼 博主等级

行动起来,活在当下

  • 累计撰写 28 篇文章
  • 累计创建 34 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

SpringCloudGateway

halt
2021-12-10 / 0 评论 / 0 点赞 / 3203 阅读 / 0 字

SpringCloudGateway

官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/

demo代码:https://duangouyu.coding.net/p/demo/d/SCA/git/tree/gateway

Spring Cloud GateWay是Spring Cloud的⼀个全新项⽬,⽬标是取代Netflix Zuul, 它基于Spring5.0+SpringBoot2.0+WebFlux(基于⾼性能的Reactor模式响应式通信 框架Netty,异步⾮阻塞模型)等技术开发,性能⾼于Zuul,官⽅测试,GateWay是 Zuul的1.6倍,旨在为微服务架构提供⼀种简单有效的统⼀的API路由管理⽅式。 Spring Cloud GateWay不仅提供统⼀的路由⽅式(反向代理)并且基于 Filter(定义 过滤器对请求过滤,完成⼀些功能) 链的⽅式提供了⽹关基本的功能,例如:鉴权、 流量控制、熔断、路径重写、⽇志监控等

Gateway 核心概念

Spring Cloud GateWay天⽣就是异步⾮阻塞的,基于Reactor模型 ⼀个请求—>⽹关根据⼀定的条件匹配—匹配成功之后可以将请求转发到指定的服务 地址;⽽在这个过程中,我们可以进⾏⼀些⽐较具体的控制(限流、⽇志、⿊⽩名 单)

  • 路由(route): ⽹关最基础的部分,也是⽹关⽐较基础的⼯作单元。路由由⼀ 个ID、⼀个⽬标URL(最终路由到的地址)、⼀系列的断⾔(匹配条件判断)和 Filter过滤器(精细化控制)组成。如果断⾔为true,则匹配该路由。

  • 断⾔(predicates):参考了Java8中的断⾔java.util.function.Predicate,开发 ⼈员可以匹配Http请求中的所有内容(包括请求头、请求参数等)(类似于 nginx中的location匹配⼀样),如果断⾔与请求相匹配则路由。

  • 过滤器(filter):⼀个标准的Spring webFilter,使⽤过滤器,可以在请求之前 或者之后执⾏业务逻辑。

Gateway 中的 predicates

参考:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/index.html#gateway-request-predicates-factories

快速使用

创建项目

gateway需要依赖springboot启动 不能添加 mvc 依赖,因为他是基于 webflux 的

添加依赖

接入 nacos

<dependencies>
    <!-- nacos 注册中心 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- nacos 配置中心 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <!-- bootstrap 解决无法读取 bootstrap.yml -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    <!-- gateway 网关 -->
    <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>
    <!-- fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
    <!-- 读取配置 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <!-- loadbalancer 解决在 gateway route 中 uri 无法 lb://service-name 的方式进行转发的方式 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

添加配置

启用网关与注册中心集成时,会自动生成路由策略,每个服务的请求路径为,GatewayIp:port/服务名/路径

spring:
  main:
    allow-bean-definition-overriding: true
  application:
    name: gateway
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
        # 注册中心地址
        server-addr: 192.168.59.102:8848
      config:
        # 配置中心地址
        server-addr: 192.168.59.102:8848
        # nacos config dataId 的后缀,也是配置内容的文件扩展名
        file-extension: yaml
#    gateway:
#      discovery:
#        locator:
#          # 启用网关集成 自动从注册心获取服务并添加路由规则 路由前缀就是服务名
#          enabled: true
#      # 路由策略
#      routes:
#        # id 唯一
#        - id: consumer-service
#          # 匹配成功后的路由地址
#          uri: lb://consumer-service
#          # 谓词匹配规则
#          predicates:
#            - Path=/consumer/**
# 端点监控
management:
  endpoints:
    web:
      exposure:
        include: "*"

动态路由

Gateway中的路由配置信息是写在配置文件中的如果需要更改则需要进行重启,如果我们接入nacos配置中心,可以实现动态的路由更新

1. 配置文件方式

Gateway 会自动对配置进行刷新

推荐使用一个专门的配置文件进行路由的存储

本地配置文件

spring:
  cloud:
    nacos:
      discovery:
        # 注册中心地址
        server-addr: 192.168.59.102:8848
      config:
        # 配置中心地址
        server-addr: 192.168.59.102:8848
        # nacos config dataId 的后缀,也是配置内容的文件扩展名
        file-extension: yaml
        # 指定一个扩展配置文件
        extension-configs[0]:
          data-id: gateway-dynamic-route.yaml
          # 自动刷新
          refresh: true

Nacos配置中心

创建 gateway-dev.yaml 内容如下

spring:
  cloud:
    gateway:
      discovery:
        locator:
          # 启用网关集成 自动从注册心获取服务并添加路由规则 路由前缀就是服务名
          enabled: true
      # 路由策略
      routes:
        # id 唯一
        - id: consumer-service
          # 匹配成功后的路由地址
          uri: lb://consumer-service
          # 谓词匹配规则
          predicates:
            - Path=/consumer/**

2. 手动监听并更新

步骤

  1. 创建配置文件用来保存 Nacos 配置中心与动态路由配置文件配置
  2. 创建 Gateway 路由操作工具类,实现路由更新时通知 Gateway 进行同步更新
  3. 创建 NacosConfig 客户端,监听 Nacos 中动态路由配置文件的更新,当 Gateway 启动和 Nacos 中配置文件发生变更时调用工具类去更新路由
  4. Naocs 中创建对应的配置文件 json 格式

这个配置方式参考了 https://coding.imooc.com/class/522.html 课程中的配置方式,如果没有特殊需求的话,感觉第一种更直观并且简单

配置类

package top.mengshuo.config.gateway;

import lombok.Data;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author mengshuo
 * @since 2021-11-26
 */
@ToString
@Data
@ConfigurationProperties("spring.cloud.gateway.nacos-dynamic-route")
@Configuration
public class DynamicRouteProperties {

    /**
     * nacos服务器地址 不配置默认为nacos配置中心地址
     */
    @Value("${spring.cloud.gateway.nacos-dynamic-route.server-addr:${spring.cloud.nacos.config.server-addr}}")
    private String serverAddr;

    /**
     * 配置文件命名空间 默认与配置中心一致
     */
    @Value("${spring.cloud.gateway.nacos-dynamic-route.namespace:${spring.cloud.nacos.config.namespace:}}")
    private String namespace;

    /**
     * 配置文件分组 默认与配置中心一致
     */
    @Value("${spring.cloud.gateway.nacos-dynamic-route.group:${spring.cloud.nacos.config.group:DEFAULT_GROUP}}")
    private String group ;

    /**
     * 默认为 应用名 + dynamic-route
     */
    @Value("${spring.application.name}-dynamic-route")
    private String dataId;

    /**
     * 超时时间
     */
    private Long timeout = 3000L;

}

路由操作类

package top.mengshuo.config.gateway;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * 对路由进行动态操作
 *
 * @author mengshuo
 * @since 2021-11-26
 */
@SuppressWarnings("all")
@Slf4j
@Service
public class DynamicRouteOperation implements ApplicationEventPublisherAware {

    /**
     * 操作内存中路由对象 增 删
     */
    private final RouteDefinitionWriter routeDefinitionWriter;

    /**
     * 获取内存中的路由对象 查
     */
    private final RouteDefinitionLocator routeDefinitionLocator;

    /**
     * 事件发布器
     */
    private ApplicationEventPublisher publisher;


    /**
     * 构造注入
     *
     * @param routeDefinitionWriter  写路由定义
     * @param routeDefinitionLocator 获取路由定义
     */
    public DynamicRouteOperation(
            RouteDefinitionWriter routeDefinitionWriter,
            RouteDefinitionLocator routeDefinitionLocator) {
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.routeDefinitionLocator = routeDefinitionLocator;
    }

    /**
     * 设置事件发布器
     *
     * @param applicationEventPublisher 事件发布器
     */
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }


    /**
     * 批量更新路由
     *
     * @param definitions 路由信息
     * @return res
     */
    public String updateList(List<RouteDefinition> definitions) {

        if (!ObjectUtils.isEmpty(definitions)) {
            log.info("Gateway route update [{}]", definitions);

            // 获取 Gateway 中已经存在的路由
            List<RouteDefinition> locatorRouteDefinitions = 
                    this.routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
            // 如果当前路由不为空 清除所有旧的路由定义
            if (!ObjectUtils.isEmpty(locatorRouteDefinitions)) {
                locatorRouteDefinitions.forEach(routeDefinition -> {
                    log.info("delete route definition [{}]", routeDefinition);
                    this.deleteById(routeDefinition.getId());
                });
            }
            // 添加路由配置
            definitions.forEach(this::addRouteDefinition);

        } else {
            log.warn("new route definitions is empty");
        }
        return "success";
    }

    /**
     * 增加路由定义
     *
     * @param routeDefinition 路由对象
     * @return res
     */
    private String addRouteDefinition(RouteDefinition routeDefinition) {
        log.info("gateway add route [{}]", routeDefinition);
        // 保存路由配置
        this.routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        // 发布事件通知给 Gateway ,同步路由定义
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }


    /**
     * 删除路由配置
     *
     * @param id 路由id
     * @return res
     */
    private String deleteById(String id) {
        try {
            log.info("gateway delete route, routeId: [{}]", id);
            // 删除路由
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
            // 发布事件通知给 Gateway ,同步路由定义
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            log.error("gateway delete route failed, routeId: [{},{}]", id, e.getMessage());
            return "delete failed";
        }
    }
}

从Nacos中获取配置

在配置类初始化完成后初始化

package top.mengshuo.config.gateway;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 监听nacos动态路由配置
 *
 * @author mengshuo
 * @since 2021-11-26
 */
@Slf4j
@Component
@DependsOn("dynamicRouteProperties")
public class DynamicRouteByNacos {

    /**
     * 自定义配置
     */
    private final DynamicRouteProperties properties;
    /**
     * 动态路由操作工具类
     */
    private final DynamicRouteOperation routeOperation;

    /**
     * Nacos 配置中心客户端
     */
    private ConfigService configService;

    /**
     * 构造方法初始化
     *
     * @param routeOperation 工具类
     * @param properties     自定义配置
     */
    public DynamicRouteByNacos(
            DynamicRouteOperation routeOperation,
            DynamicRouteProperties properties) {
        this.routeOperation = routeOperation;
        this.properties = properties;
    }

    /**
     * bean 初始化完成后调用
     */
    @PostConstruct
    public void initGatewayDynamicRoute() {
        log.info("Gateway Dynamic Route init ....");
        // 初始化config客户端
        this.configService = this.initConfigService();
        if (this.configService == null) {
            return;
        }
        try {
            log.info("init route ...");
            // 获取配置信息
            String routeConfig = this.configService.getConfig(
                    this.properties.getDataId(),
                    this.properties.getGroup(),
                    this.properties.getTimeout());
            log.info("gateway route config [{}]", routeConfig);
            // 反序列化
            List<RouteDefinition> definitionList = JSON.parseArray(routeConfig, RouteDefinition.class);
            // 添加进gateway
            this.routeOperation.updateList(definitionList);
            // 添加监听器
            this.dynamicRouteByNacosListener();
        } catch (NacosException e) {
            log.info("init route error [{}]", e.getMessage(), e);
        }
        log.info("Gateway Dynamic Route init success ");

    }

    /**
     * 初始化NacosConfig
     *
     * @return 返回NacosConfig
     */
    private ConfigService initConfigService() {
        log.info("init nacos config client ...");
        try {
            Properties properties = new Properties();
            properties.setProperty("serverAddr", this.properties.getServerAddr());
            properties.setProperty("namespace", this.properties.getNamespace());
            return NacosFactory.createConfigService(properties);
        } catch (NacosException e) {
            log.error("init gateway nacos config error [{}]", e.getMessage(), e);
            return null;
        }
    }


    /**
     * 监听nacos下发的配置信息
     */
    private void dynamicRouteByNacosListener() {
        // 给NacosConfig添加监听器
        try {
            this.configService.addListener(this.properties.getDataId(), this.properties.getGroup(), new Listener() {
                /**
                 * 提供自定义线程池 可选
                 * @return res
                 */
                @Override
                public Executor getExecutor() {
                    return null;
                }

                /**
                 * 监听器收到配置更新
                 * @param configInfo nacos中最新配置信息
                 */
                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info("receive nacos config update [{}]", configInfo);
                    // 解析最新数据
                    List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
                    // 更新路由信息
                    routeOperation.updateList(routeDefinitions);
                }
            });
        } catch (NacosException e) {
            log.error("init nacos config listener error [{}]", e.getMessage(), e);
        }
    }
}

Nacos 添加配置

对应的配置实体 org.springframework.cloud.gateway.route.RouteDefinition

[
    {
        "id":"consumer-service-route",
        "predicates":[
            {
                "name":"Path",
                "args":
                    "_genkey_0":"/consumer/**"
                }
            }
        ],
        "filters":[

        ],
        "uri":"lb://consumer-service",
        "order":0
    }
]

过滤器 Filter

过滤器优先级:Order 越大优先级越低,越晚被执行,Order 越小优先级越高,优先执行

全局过滤器:所有请求都会执行

局部过滤器:只有声明了相关配置才会执行

全局过滤器

Gateway 中的局部过滤器:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

实现全局过滤器接口 GlobalFilter ,Ordered 接口指定过滤器执行顺序

package top.mengshuo.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 全局过滤器
 *
 * @author mengshuo
 * @since 2021-12-19
 */
@Component
@Slf4j
public class SimpleGlobalFilter implements GlobalFilter, Ordered {


    private static final String TOKEN_PREFIX = "token";

    /**
     * 过滤器
     *
     * @param exchange http 请求交互
     * @param chain    过滤器链
     * @return res
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("请求经过全局过滤器");
        String token = exchange.getRequest().getHeaders().getFirst(TOKEN_PREFIX);
        // 如果 token 不为空时继续执行
        if (!ObjectUtils.isEmpty(token)) {
            return chain.filter(exchange);
        }
        // 如果为空时 结束请求
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();

    }

    /**
     * 执行顺序
     *
     * @return res
     */
    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE + 1;
    }
}

局部过滤器

Gateway 中的全局过滤器:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters

内部类 Config 指定配置项

需要在配置文件中启用,才会生效,可以使用匿名内部类方式创建,也可以实现 GatewayFilter 接口后再初始化

类名按照 **GatewayFilterFactory 方式命名,** 就是配置文件中过滤器的名

package top.mengshuo.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 过滤器工厂
 *
 * @author mengshuo
 * @since 2021-12-19
 */
@Slf4j
@Component
public class SimpleGatewayFilterFactory extends AbstractGatewayFilterFactory<SimpleGatewayFilterFactory.Config> {


    /**
     * 返回配置项的提示信息, config 类的属性信息
     *
     * @return res
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("param");
    }

    /**
     * 将自定义过滤器加入到gateway
     *
     * @param config 自定义的路由配置
     * @return res
     */
    @Override
    public GatewayFilter apply(SimpleGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            log.info("配置信息: " + config.getParam());
            return chain.filter(exchange);
        };
    }


    public static class Config {
        private String param;

        public String getParam() {
            return param;
        }

        public void setParam(String param) {
            this.param = param;
        }
    }
}

测试

测试访问

日志信息

路由配置

0
博主关闭了所有页面的评论