SpringCloud Alibaba微服务--Gateway使用

发布于:2025-09-13 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、API 网关 (Gateway) 简介

1、什么是gateway

spring cloud gateway是一个服务器端的入口点,作为客户端和后端服务之间的中间层,负责请求路由、组合和协议转换。它充当系统的"前门",所有客户端请求都首先通过API网关,然后被路由到适当的后端服务。它是建立在 Spring Boot 2.x、 Spring WebFlux 和 Project Reactor之上,旨在为微服务架构提供简单、有效和统一的API路由管理方式

2、核心功能

  • 请求路由:将客户端请求转发到相应的后端服务
  • 协议转换:处理不同协议之间的转换(如HTTP到gRPC)
  • 负载均衡:在多个服务实例之间分配请求
  • 认证授权:验证请求的合法性,检查访问权限
  • 限流熔断:控制请求速率,防止系统过载
  • 缓存:缓存常用响应,减少后端压力
  • 监控日志:记录请求指标和日志用于分析和监控
  • 请求/响应转换:修改请求或响应内容

3、gateway在微服务中的优势

  • 解耦客户端与服务端:客户端只需知道网关地址,无需了解内部服务结构
  • 简化客户端:网关可以聚合多个服务请求,减少客户端调用次数
  • 统一安全控制:集中处理认证、授权和安全策略
  • 提高性能:通过缓存、压缩和协议优化提升响应速度
  • 弹性设计:通过熔断、降级等机制提高系统可靠性
  • 易于监控:集中收集所有API调用的指标和日志

4、gateway工作流程

客户端向 Spring Cloud Gateway 发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。过滤器被虚线分割的原因是,过滤器可以在代理请求发送之前和之后运行逻辑。所有的 "pre" (前)过滤器逻辑都被执行。然后发出代理请求。在代理请求发出后,"post" (后)过滤器逻辑被运行。

5、Zuul实现SpringCloud网关及不足之处

在SpringCloud中网关的实现包括两种gateway、zuul。Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。二者主要区别:

对比项 Spring Cloud Gateway Netflix Zuul
架构模型 基于 Reactor 和 Netty 的 异步非阻塞 模型 基于 Servlet 的 同步阻塞 模型
性能 更高吞吐量,支持 长连接(WebFlux) 性能较低,受限于 Servlet 阻塞 IO
依赖 需要 Spring WebFlux 和 Project Reactor 依赖 Spring MVC 和 Servlet API
功能扩展 支持 自定义过滤器断言 和 全局过滤器 主要依赖 Groovy 脚本扩展过滤器
协议支持 支持 HTTP/2WebSocketgRPC 仅支持 HTTP/1.x
服务发现 原生支持 EurekaConsulNacos 需配合 Netflix Eureka 使用
负载均衡 集成 Ribbon 或 Spring Cloud LoadBalancer 依赖 Ribbon
熔断限流 支持 HystrixResilience4jSentinel 主要依赖 Hystrix
配置方式 YAML/Properties + Java DSL(更灵活) 主要依赖 Groovy 脚本或 Java 配置
社区支持 Spring 官方维护,持续更新 Netflix 已停止维护(Zuul 1.x)
适用场景 高并发低延迟 的微服务网关 传统 同步阻塞 架构的简单网关

二、构建SpringCloud Gateway服务

1、新建子模块jd-shop-gateway服务

微服务搭建以及nacos注册和配置方式可以参考之前的文章:

SpringCloud Alibaba微服务框架搭建-CSDN博客

SpringCloud Alibaba微服务--Nacos注册中心和配置中心应用-CSDN博客

2、引入依赖

       <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>3.0.7</version>
        </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>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mdx</groupId>
            <artifactId>mdx-shop-common</artifactId>
            <version>1.0.0</version>
        </dependency>

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

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-commons</artifactId>
        </dependency>
       </dependencies>

3、编写基础配置和路由规则

(1)路由基础配置

  1. 路由id:路由的唯一标示

  2. 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡

  3. 路由断言(predicates):判断路由的规则,

  4. 路由过滤器(filters):对请求或响应做处理

(2)设置bootstrap.yml配置文件

server:
  port: 8091

spring:
  application:
    name: jd-shop-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: jd
      config:
        server-addr: 127.0.0.1:8848
        extension-configs:
          - data-id: ${spring.application.name}.yaml
            group: DEFAULT_GROUP
            refresh: true
        file-extension: yml
        namespace: jd
        group: DEFAULT_GROUP
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      routes:
        - id: mdx-shop-user             #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://127.0.0.1:8021       #匹配后提供服务的路由地址
          predicates:
            - Path=/user/**    #断言,路径相匹配的进行路由

        - id: mdx-shop-order
          uri: http://127.0.0.1:8081
          predicates:
            - Path=/order/**
  main:
    web-application-type: reactive

4、重启服务,测试访问接口

通过gateway访问user服务,访问地址http://localhost:8091/user/getOrderNo?userId=T1234

8091为网关端口号

通过gateway访问order服务,访问地址http://127.0.0.1:8091/order/getOrderId?userId=T1234

8091为网关端口号

5、通过微服务名称的形式进行路由

将uri: http://127.0.0.1:8021 换成 uri: lb://jd-shop-user形式

重启服务,在测试一下user服务接口,访问

http://localhost:8091/user/getOrderNo?userId=T1234

8091为网关端口号

6、测试负载均衡

采用这种路由方式 uri: lb://jd-shop-user
在gateway添加配置:
开启通过服务中心的自动根据 serviceId 创建路由的功能

discovery:
        locator:
          enabled: true
          lower-case-service-id: true

启动两个order服务,为另一个order服务新开一个端口号

启动两个order服务

nacos中可以看到有两个order服务实例

通过gateway访问order服务,访问地址http://127.0.0.1:8091/order/getOrderId?userId=T1234

可以发现访问的是OrderApplication服务接口

再次访问接口,发现访问的是OrderApplication2服务接口,实现了简单的负载均衡

三、通过nacos实现动态路由

对于微服务来说,如果使用配置文件来管理路由规则的话,每增加一个服务或者修改一个服务,都需要重启gateway服务,这样很麻烦,因此我们通过nacos来动态配置路由,就不需要进行服务重启了,编写对应的配置类,实现自动监听nacos配置文件的更新,实现动态刷新。

1、创建路由配置类

新建路由发布接口

public interface RouteService {
    /**
     * 添加路由信息
     * @return
     */
    void addRoute(RouteDefinition routeDefinition);

    /**
     * 修改路由信息
     * @return
     */
    void updateRoute(RouteDefinition routeDefinition);

    /**
     * 删除路由信息
     * @return
     */
    void deleteRoute(String routeId);
}

新建RouteServiceImpl类实现接口

@Service
@Slf4j
public class RouteServiceImpl implements RouteService, ApplicationEventPublisherAware {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

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

    @Override
    public void addRoute(RouteDefinition routeDefinition) {
        log.info("添加路由:{}", routeDefinition);
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    @Override
    public void updateRoute(RouteDefinition routeDefinition) {
        log.info("更新路由:{}", routeDefinition);
        routeDefinitionWriter.delete(Mono.just(routeDefinition.getId())).subscribe();
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    @Override
    public void deleteRoute(String routeId) {
        log.info("删除路由:{}", routeId);
        routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}

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

(1)新建配置

json内容对应着RouteDefinition 类,Json内容:

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

这里面博主没有加过滤链:

 "filters":[
            {
                "args":{
                    "parts":1
                },
                "name":"StripPrefix"  
            }
        ],

作用:当请求路径为 /api/user 时,网关会截掉第一个路径部分(/api),将请求转发到后端服务时路径变为 /user

(2)修改bootstrap.yml配置文件,添加路由配置文件dataId、group等,之前路由规则删除或注释掉。

server:
  port: 8091

spring:
  application:
    name: jd-shop-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: jd
      config:
        server-addr: 127.0.0.1:8848
        extension-configs:
          - data-id: ${spring.application.name}.yaml
            group: DEFAULT_GROUP
            refresh: true
        file-extension: yml
        namespace: jd
        group: DEFAULT_GROUP
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
#      routes:
#        - id: jd-shop-user             #路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: lb://jd-shop-user       #匹配后提供服务的路由地址
##          uri: http://127.0.0.1:8021
#          predicates:
#            - Path=/user/**    #断言,路径相匹配的进行路由
#
#        - id: jd-shop-order
#          uri: lb://jd-shop-order
##          uri: http://127.0.0.1:8081
#          predicates:
#            - Path=/order/**
  main:
    web-application-type: reactive

gateway:
  routes:
    config:
      data-id: gateway-routes  #动态路由
      group: DEFAULT_GROUP
      namespace: jd

3、创建路由相关配置类

(1)创建配置类引入配置

@ConfigurationProperties(prefix = "gateway.routes.config")
@Component
@Data
public class GatewayRoutesConfigProperties {
    private String dataId;
    private String group;
    private String namespace;
}


(2)实例化nacos的ConfigService,交由springbean管理

@Configuration
public class GatewayServiceConfig {

    @Autowired
    private GatewayRoutesConfigProperties 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);
    }

}

(3)路由监听类实现,项目启动时会加载这个类
@PostConstruc 注解的作用,在spring bean的生命周期依赖注入完成后被调用的方法

@Component
@Slf4j
@RefreshScope
public class GatewayRouteInitConfig {
    @Autowired
    private GatewayRoutesConfigProperties 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.updateRoute(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.addRoute(definition);
                }
            } else {
                log.warn("当前网关无动态路由相关配置");
            }
            log.info("结束网关动态路由初始化...");
        } catch (Exception e) {
            log.error("初始化网关路由时发生错误", e);
        }
    }
}

4、测试动态路由

(1)重启服务,通过gateway访问order服务,访问地址http://127.0.0.1:8091/order/getOrderId?userId=T1234

8091为网关端口号

(2)在nacos配置中添加user服务路由规则,点击发布后,gateway的监听器已经监听到配置的改动

在不重启gateway服务前提下,通过gateway访问user服务,访问地址http://localhost:8091/user/getOrderNo?userId=T12345

可以看到成功路由到对应服务

SpringCloud Gateway网关服务就介绍到这里,创作不易,记得点赞收藏哟

上一篇文章:

SpringCloud Alibaba微服务--Sentinel的使用-CSDN博客


网站公告

今日签到

点亮在社区的每一天
去签到