SpringCloud Gateway的使用 + Nacos动态路由

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
日志服务 SLS,月写入数据量 50GB 1个月
简介: SpringCloud Gateway的使用 + Nacos动态路由

一、简介

1、什么是gateway?
  • SpringCloud Gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,旨在为微服务架构提供简单、有效和统一的API路由管理方式
  • SpringCloud Gateway作为SpringCloud生态系统中的网关,目标是替代Netflix Zuul,在SpringCloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用Zuul 1.x非Reactor模式的老版本。二为了提高网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty
  • SpringCloud Gateway不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:鉴权、监控/指标、流量控制、熔断限流等
2、没有gateway的弊端
  • 客户端多次请求不同的微服务,会增加客户端代码和配置的复杂性,维护成本比价高
  • 认证复杂,每个微服务可能存在不同的认证方式,客户端去调用,要去适配不同的认证
  • 存在跨域的请求,调用链有一定的相对复杂性(防火墙 / 浏览器不友好的协议)
  • 难以重构,随着项目的迭代,可能需要重新划分微服务
3、gateway解决了什么?

为了解决上面的问题,微服务引入了 网关 的概念,网关为微服务架构的系统提供简单、有效且统一的API路由管理,作为系统的统一入口,提供内部服务的路由中转,给客户端提供统一的服务,可以实现一些和业务没有耦合的公用逻辑,主要功能包含认证、鉴权、路由转发、安全策略、防刷、流量控制、监控日志等

4、gateway和zuul的区别
  • Zuul构建于 Servlet 2.5,兼容 3.x,使用的是阻塞式的 API,不支持长连接,比如 websockets
  • Spring Cloud Gateway构建于 Spring 5+,基于 Spring Boot 2.x 响应式的、非阻塞式的 API。同时,它支持 websockets,和 Spring 框架紧密集成,开发体验相对来说十分不错
5、gateway核心概念
  • Route(路由): 路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配
  • Predicate(断言): 参考的是java8的java.util.function.Predicate,开发人员可以匹配Http请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
  • Filter(过滤器): 一个标准的Spring webFilter。SpringCloud Gateway中的Filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。使用过滤器,可以在请求被路由前或者之后对请求进行修改
6、gateway是如何工作的

官方解释:

客户端SpringCloud Gateway发出请求,然后在Gateway Handler Mapping中找到与之请求相匹配的路由,将其发送到Gateway Web Handler,Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

过滤器之间用虚线分开是因为过滤器可能会发在代理请求之前(“pre”)或之后(“post”)执行业务逻辑,这样,Filter在“pre”类型的过滤器可以做参数校验,权限校验,流量监控,日志输出,协议转换等;在“post”类型的过滤器可以做响应内容,响应头的修改,日志的输出,流量监控等有着非常重要的作用

二、构建一个springcloud Gateway服务

1、新建一个微服务

1.1、新建gateway子模块

nacos注册中心和配置中心以及服务服搭建可以参考之前的文章,这里基于之前的项目构建gateway服务

springcloud alibaba微服务 – nacos使用以及注册中心和配置中心的应用(保姆级)

1.2、引入依赖

gateway服务依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>3.0.7</version>
        </dependency>
        <dependency>
            <groupId>com.mdx</groupId>
            <artifactId>mdx-shop-common</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

下面是全局的关于springcloud的依赖

spring-cloud.version:2021.0.1

spring-cloud-alibaba.version: 2021.0.1.0

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>3.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>3.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
            <version>3.1.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>
2、配置服务

2.1、创建启动类

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

2.2、创建application.yml配置文件

使用ip路由的方式:

server:
  port: 9010
spring:
  application:
    name: mdx-shop-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: mdx
        group: mdx
    gateway:
      routes:
        - id: mdx-shop-user             #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:9090    #匹配后提供服务的路由地址
          predicates:
            - Path=/user/**    #断言,路径相匹配的进行路由
        - id: mdx-shop-order
          uri: http://localhost:9091
          predicates:
            - Path=/order/**

2.3、启动并访问Gateway服务

发现报错了…

大致意思是在springboot整合gateway时, gateway组件中的 【spring-boot-starter-webflux】 和 springboot作为web项目启动必不可少的 【spring-boot-starter-web】 出现冲突

我们按照提示: Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency.

在配置文件配置下 spring.main.web-application-type=reactive 就好了

main:
    web-application-type: reactive

接着在重新启动项目,成功启动

然后我们再依次启动order服务和user服务

通过gateway访问user服务:

http://localhost:9010/user/getOrderNo?userId=mdx123456

其中9010端口为网关服务

通过gateway访问order服务:

http://localhost:9010/order/getOrderNo?userId=mdx123456

其中9010端口为网关服务

可见以上gateway均已成功路由到两个服务

2.4、通过微服务名称的方式来路由服务

把 gateway配置文件中的 uri: http://localhost:9090 改为 uri: lb://mdx-shop-user 这种服务名的形式

server:
  port: 9010
spring:
  application:
    name: mdx-shop-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: mdx
        group: mdx
    gateway:
      routes:
        - id: mdx-shop-user             #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://mdx-shop-user    #匹配后提供服务的路由地址
          predicates:
            - Path=/user/**      #断言,路径相匹配的进行路由
        - id: mdx-shop-order
          uri: lb://mdx-shop-order
          predicates:
            - Path=/order/**
  main:
    web-application-type: reactive

再来测试一下user服务

http://localhost:9010/user/getOrderNo?userId=mdx123456

其中9010端口为网关服务

成功返回

2.5、路由websocket服务

将 uri: lb://mdx-shop-user 改为 uri: lb:ws://mdx-shop-user

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

2.6、测试负载均衡

采用这种路由方式 uri: lb://mdx-shop-user

在gateway添加配置:

开启通过服务中心的自动根据 serviceId 创建路由的功能

gateway:
      discovery:
        locator:
          enabled: true

我们在order服务中写一个测试类,如下

/**
     * 测试负载均衡
     * @return
     */
    @GetMapping("lb")
    public String lb(){
        System.out.println("test lb");
        return "lb";
    }

分别启动两个order服务(启动一个order服务之后,修改下端口号再启动一个)

在idea中启动同一个服务的多个端口操作如下:

成功启动了两个order服务

nacos状态如下(启动了两个实例)

我们再来通过网关访问下order服务

http://localhost:9010/order/lb

其中 9010 为网关端口

首先访问一次

我们看到order1服务打印了日志,order2服务没有日志

再访问一次接口

这个时候order2打印了日志,order1没有打印日志

如此实现了简单的负载均衡

三、通过nacos实现动态路由

微服务都是互相独立的,假如我们的网关和其他服务都在线上已经运行了好久,这个时候增加了一个微服务,这个时候要通过网关访问的话需要通过修改配置文件来增加路由规则,并且需要重启项目,所以我们需要实现动态路由

1、创建路由配置接口

新建路由发布接口

/**
 * 路由配置服务
 * @author : jiagang
 * @date : Created in 2022/7/20 11:07
 */
public interface RouteService {
    /**
     * 更新路由配置
     *
     * @param routeDefinition
     */
    void update(RouteDefinition routeDefinition);
    /**
     * 添加路由配置
     *
     * @param routeDefinition
     */
    void add(RouteDefinition routeDefinition);
}

实现类如下

package com.mdx.gateway.service.impl;
import com.mdx.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
/**
 * @author : jiagang
 * @date : Created in 2022/7/20 11:10
 */
@Service
@Slf4j
public class RouteServiceImpl implements RouteService, ApplicationEventPublisherAware {
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    /**
     * 事件发布者
     */
    private ApplicationEventPublisher publisher;
    @Override
    public void update(RouteDefinition routeDefinition) {
        log.info("更新路由配置项:{}", routeDefinition);
        this.routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }
    @Override
    public void add(RouteDefinition routeDefinition) {
        log.info("新增路由配置项:{}", routeDefinition);
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}

其中:

RouteDefinitionWriter:提供了对路由的增加删除等操作

ApplicationEventPublisher: 是ApplicationContext的父接口之一,他的功能就是发布事件,也就是把某个事件告诉所有与这个事件相关的监听器

2、在nacos创建gateway-routes配置文件

将路由信息放到nacos的配置文件下

新建配置文件,并将order服务的路由添加到配置文件

配置路由如下:

[
    {
        "predicates":[
            {
                "args":{
                    "pattern":"/order/**"
                },
                "name":"Path"
            }
        ],
        "id":"mdx-shop-order",
        "filters":[
            {
                "args":{
                    "parts":1
                },
                "name":"StripPrefix"
            }
        ],
        "uri":"lb://mdx-shop-order",
        "order":1
    }
]

这个路由配置对应的就是gateway中的RouteDefinition类

3、在本地配置文件下配置路由的data-id和group和命名空间

gateway:
  routes:
    config:
      data-id: gateway-routes  #动态路由
      group: shop
      namespace: mdx

完整配置文件(删除或者注释掉之前配置在本地文件的路由)

server:
  port: 9010
spring:
  application:
    name: mdx-shop-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: mdx
        group: mdx
    gateway:
      discovery:
        locator:
          enabled: true  #开启通过服务中心的自动根据 serviceId 创建路由的功能
  main:
    web-application-type: reactive
gateway:
  routes:
    config:
      data-id: gateway-routes  #动态路由
      group: shop
      namespace: mdx

4、创建路由相关配置类

创建配置类引入配置

/**
 * @author : jiagang
 * @date : Created in 2022/7/20 14:44
 */
@ConfigurationProperties(prefix = "gateway.routes.config")
@Component
@Data
public class GatewayRouteConfigProperties {
    private String dataId;
    private String group;
  
  private String namespace;
}

5、实例化nacos的ConfigService,交由springbean管理

ConfigService 这个类是nacos的分布式配置接口,主要是用来获取配置和添加监听器

由NacosFactory来创建ConfigService

/**
 * 将configService交由spring管理
 * @author : jiagang
 * @date : Created in 2022/7/20 15:27
 */
@Configuration
public class GatewayConfigServiceConfig {
    @Autowired
    private GatewayRouteConfigProperties configProperties;
    @Autowired
    private NacosConfigProperties nacosConfigProperties;
    @Bean
    public ConfigService configService() throws NacosException {
        Properties properties = new Properties();
        properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosConfigProperties.getServerAddr());
        properties.setProperty(PropertyKeyConst.NAMESPACE, configProperties.getNamespace());
        return NacosFactory.createConfigService(properties);
    }
}

6、动态路由主要实现

项目启动时会加载这个类

@PostConstruc 注解的作用,在spring bean的生命周期依赖注入完成后被调用的方法

package com.mdx.gateway.route;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mdx.common.utils.StringUtils;
import com.mdx.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
 * @author : jiagang
 * @date : Created in 2022/7/20 15:04
 */
@Component
@Slf4j
@RefreshScope
public class GatewayRouteInitConfig {
    @Autowired
    private GatewayRouteConfigProperties configProperties;
    @Autowired
    private NacosConfigProperties nacosConfigProperties;
    @Autowired
    private RouteService routeService;
    /**
     * nacos 配置服务
     */
    @Autowired
    private ConfigService configService;
    /**
     * JSON 转换对象
     */
    private final ObjectMapper objectMapper = new ObjectMapper();
    @PostConstruct
    public void init() {
        log.info("开始网关动态路由初始化...");
        try {
            // getConfigAndSignListener()方法 发起长轮询和对dataId数据变更注册监听的操作
            // getConfig 只是发送普通的HTTP请求
            String initConfigInfo = configService.getConfigAndSignListener(configProperties.getDataId(), configProperties.getGroup(), nacosConfigProperties.getTimeout(), new Listener() {
                @Override
                public Executor getExecutor() {
                    return null;
                }
                @Override
                public void receiveConfigInfo(String configInfo) {
                    if (StringUtils.isNotEmpty(configInfo)) {
                        log.info("接收到网关路由更新配置:\r\n{}", configInfo);
                        List<RouteDefinition> routeDefinitions = null;
                        try {
                            routeDefinitions = objectMapper.readValue(configInfo, new TypeReference<List<RouteDefinition>>() {
                            });
                        } catch (JsonProcessingException e) {
                            log.error("解析路由配置出错," + e.getMessage(), e);
                        }
                        for (RouteDefinition definition : Objects.requireNonNull(routeDefinitions)) {
                            routeService.update(definition);
                        }
                    } else {
                        log.warn("当前网关无动态路由相关配置");
                    }
                }
            });
            log.info("获取网关当前动态路由配置:\r\n{}", initConfigInfo);
            if (StringUtils.isNotEmpty(initConfigInfo)) {
                List<RouteDefinition> routeDefinitions = objectMapper.readValue(initConfigInfo, new TypeReference<List<RouteDefinition>>() {
                });
                for (RouteDefinition definition : routeDefinitions) {
                    routeService.add(definition);
                }
            } else {
                log.warn("当前网关无动态路由相关配置");
            }
            log.info("结束网关动态路由初始化...");
        } catch (Exception e) {
            log.error("初始化网关路由时发生错误", e);
        }
    }
}

如果项目启动时,在发布路由的时候卡在 this.publisher.publishEvent(new RefreshRoutesEvent(this)); 这个地方走不下去

请在GatewayRouteInitConfig这个类加@RefreshScope注解

5、测试动态路由

前面我们已经把本地的yml中的路由注释掉了,现在我们来通过gateway服务来掉一个order服务的接口

接口地址:http://localhost:9010/mdx-shop-order/order/lb

其中9010是网关端口

可以看到路由成功

然后我们再在nacos配置中心加一个user服务的路由

[
    {
        "predicates":[
            {
                "args":{
                    "pattern":"/mdx-shop-order/**"
                },
                "name":"Path"
            }
        ],
        "id":"mdx-shop-order",
        "filters":[
            {
                "args":{
                    "parts":1
                },
                "name":"StripPrefix"
            }
        ],
        "uri":"lb://mdx-shop-order",
        "order":1
    },
    {
        "predicates":[
            {
                "args":{
                    "pattern":"/mdx-shop-user/**"
                },
                "name":"Path"
            }
        ],
        "id":"mdx-shop-user",
        "filters":[
            {
                "args":{
                    "parts":1
                },
                "name":"StripPrefix"
            }
        ],
        "uri":"lb://mdx-shop-user",
        "order":2
    }
]

然后点发布

可以看到gateway的监听器已经监听到配置的改动

不重新启动gateway的情况下再来通过网关访问下user服务

接口地址:http://localhost:9010/mdx-shop-user/user/getOrderNo?userId=mdx123456

其中9010是网关端口

可以看到成功路由

到这里gateway的使用和nacos动态路由就结束了~

创作不易,点个赞吧~👍

最后的最后送大家一句话

白驹过隙,沧海桑田

与君共勉

nacos的基础使用可以看这个

springcloud alibaba微服务 – nacos使用以及注册中心和配置中心的应用(保姆级)

文末送福利啦~

1、Java(SE、JVM)、算法数据结构、数据库(Mysql、redis)、Maven、Netty、RocketMq、Zookeeper、多线程、IO、SSM、Git、Linux、Docker、Web前端相关学习笔记
2、2023最新BATJ大厂面试题集
3、本教程项目源码
领取方式:关注下方公主号,回复:【笔记】、【面试】、【mdx-shop】获取相关福利。

文章持续更新,可以关注下方公众号或者微信搜一搜「 最后一支迷迭香 」获取项目源码、干货笔记、面试题集,第一时间阅读,获取更完整的链路资料。

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
2月前
|
存储 Java Nacos
学成在线笔记+踩坑(4)——【媒资管理模块】上传图片,Nacos+Gateway+MinIO
媒资管理模块简介、MinIO构建分布式文件系统、MinIO数据恢复演示 、【媒资模块】上传图片
学成在线笔记+踩坑(4)——【媒资管理模块】上传图片,Nacos+Gateway+MinIO
|
2月前
|
SpringCloudAlibaba JavaScript 前端开发
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架
分布式组件、nacos注册配置中心、openfegin远程调用、网关gateway、ES6脚本语言规范、vue、elementUI
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架
|
2月前
|
负载均衡 Java Nacos
SpringCloud基础2——Nacos配置、Feign、Gateway
nacos配置管理、Feign远程调用、Gateway服务网关
SpringCloud基础2——Nacos配置、Feign、Gateway
|
2月前
|
Java 开发者 Spring
Spring Cloud Gateway 中,过滤器的分类有哪些?
Spring Cloud Gateway 中,过滤器的分类有哪些?
52 3
|
2月前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
106 5
|
1月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
37 0
|
2月前
|
安全 Java 开发者
强大!Spring Cloud Gateway新特性及高级开发技巧
在微服务架构日益盛行的今天,网关作为微服务架构中的关键组件,承担着路由、安全、监控、限流等多重职责。Spring Cloud Gateway作为新一代的微服务网关,凭借其基于Spring Framework 5、Project Reactor和Spring Boot 2.0的强大技术栈,正逐步成为业界的主流选择。本文将深入探讨Spring Cloud Gateway的新特性及高级开发技巧,助力开发者更好地掌握这一强大的网关工具。
230 6
|
3月前
|
JSON Nacos 开发工具
微服务通过nacos实现动态路由
微服务通过nacos实现动态路由
82 7
|
4月前
|
负载均衡 Java Spring
Spring cloud gateway 如何在路由时进行负载均衡
Spring cloud gateway 如何在路由时进行负载均衡
505 15
|
4月前
|
Java Spring
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
112 3