SpringCloud GateWay网关

1、网关介绍

        微服务网关(Microservices Gateway)是微服务架构中的核心组件,充当所有客户端请求的统一入口,负责请求的路由、过滤和聚合等操作。它是微服务与外部系统(如Web、移动端)之间的中间层,简化了客户端的调用复杂度,并提供了统一的治理能力。

2、网关的作用

        网关的作用有:统一入口、请求路由、负载均衡、流量控制、身份认证、协议转换、系统监控、安全防护。  

1、统一入口

        统一入口,也叫路由转发。将客户端请求动态路由到对应的微服务实例(如 /order 请求转发到订单服务)。并且支持路径重写、负载均衡。前端不需要知道每一个后端服务的地址,只要知道网关地址就行了。在企业系统中,会统一通过api网关对外暴露接口,屏蔽内部服务拆分带来的复杂性。比如对于财务模块,接口都是以/api/fc/xxx的形式。有/fc/AAA,/fc/BBB,/fc/CCC,这些接口可能部署在不同的服务器上,那么对应的ip地址也不一样。有了网关的话,就不用记住那么多ip地址了。都走网关就行了。确保前端只与网关通信,无需感知后端服务地址。

2、请求路由

        根据请求特征(如URL路径、HTTP头、参数)将请求精准分发至对应后端服务,支持动态路由策略。静态路由通常是预定义以硬编码的形式写在配置文件yml中的。项目启动后,静态路由就生效了,如果想要修改的话,需要重启项目。而动态路由是运行时加载,可以写在数据库中,或者配置中心apollo,运行的时候读取配置。

3、负载均衡

        通过算法(如轮询、加权轮询、最少连接)将流量分发到多个服务器实例,避免单点过载,提升系统可用性。在一些高并发的场景中,通过网关将请求分散至多台服务器,防止单台服务器宕机。

4、流量控制

        通过限流(Rate Limiting)、熔断(Circuit Breaking)和降级(Degrade)策略,防止系统过载导致服务中断,保障核心服务稳定。

        限流:可以通过控制请求速率防止服务过载。比如,可以使用网关内置限流,使用Redis+Lua脚本实现分布式限流。Redis作为分布式计数器存储,确保多节点限流一致性。Lua脚本保证检查+更新计数的原子性操作。

        熔断:当下游服务响应超时或报错时,快速失败避免资源耗尽。防止一个服务的故障扩撒到整个系统,产生雪崩效应。可以使用Hystrix实现服务熔断。Hystrix实现服务熔断其实就是有点类似于java里面的异常处理机制。

        降级:一旦程序发生异常则直接将当前服务熔断,并把发生异常的服务降级(前提是要有次级服务)。一级降二级,二级降三级......依次类推。通常会采用多级降级策略:

        一级降级(轻度):

        场景:服务器资源使用率达到70%

        动作:关闭一些非核心任务:日志分析、数据备份、广告推送等。

        降低服务质量:降低图片视频的清晰度。

        启用本地缓存:一些页面读取本地缓存信息,而不是直接读取远程的信息。

        二级降级(中度):

        场景:服务器资源使用率达到90%

        动作:关闭非核心API,简化业务流程,同步改异步(实时性改为非实时性,延迟写入数据

        库,先存内存队列)。

        三级降级(重度):

        场景:服务器资源耗尽

        动作:仅保留核心功能,如电商系统仅保留下单、支付功能。商品详情页仅返回静态HTML

        (无实时数据),用户信息返回预 设默认值(如匿名用户头像)。进入安全模式,拒绝所有

        的写操作,关闭第三方服务调用(短信通知),启用本地事 务日志。

        实现服务熔断、降级,可以引入Hystrix,在相应的接口上加上:@HystrixCommand(fallbackMethod = "AAA")。

        如以下三个服务:AAA、BBB、CCC。AAA发生故障,会进行熔断并降级到BBB,如果BBB再发生故障,会熔断并降级到CCC。

@GetMapping("/AAA")
@ResponseBody
@HystrixCommand(fallbackMethod = "BBB")
public float AAA() {
    ......
}
 
@HystrixCommand(fallbackMethod = "CCC")
public float BBB() {
    ......
}

public float CCC() {
    ......
}

 

5、身份认证

        在企业内网中,用户通过网关输入账号密码去访问内部的OA系统。在调用内部服务API时,也会经过网关,网关校验token,确保调用方权限。

6、协议转换

        在不同协议间转换数据格式,如Http转Https、WebSocket、Modbus转OPC UA,实现异构系统互联。

        Http:超文本传输协议,主要用于Web/移动端/微服务,是明文传输,通常用于测试环境,默认端口是80。

        Https:在Http的基础上进行了加密,即:Http+SSL/TLS,SSL(安全套接层协议)是用于网络通信中数据加密身份验证。数据传输时通过SSL进行加密,并通过数字证书验证服务器身份,确保用户访问的是真实网站,而非假冒伪劣网站。SSL通常是由权威机构CA签发的。默认端口443。

        WebSocket:全双工长连接。实时双向通信。主要用于:实时聊天、在线协作、网游中多玩家。WS默认端口80,WSS默认端口443。

        Modbus:该协议用于工业设备,如PCL控制。

        OPC UA:该协议是一种面向服务的工业通信框架。

7、系统监控

        收集请求日志,实时监控系统状态,辅助故障排查与优化。

8、安全防护

        网关可以提供访问控制、流量过滤、加密通信等安全功能。

3、实现一个网关

        我使用的是Spring Cloud Gateway。不过也可以用Nginx作为网关。因为SpringCloudGateway是java写的,运行在JVM上,因此对于基于SpringCloud框架的微服务来说,SpringCloudGateway更适用。

        对于非SpringCloud框架的服务来说,使用Nginx更适合了。Nginx是基于C语言开发的,性能比SpringCloud更好。Nginx支持反向代理,所谓反向代理就是代理服务端去接收客户端的请求,并分发给对应的内部服务。网关起的就是一个反向代理的作用。隐藏服务细节,客户端无需感知后端服务地址。Nginx通常会用于高并发的场景,Nginx作为入口网关,处理静态资源请求,并将动态请求转发到后端应用服务器(如Tomcat、Node.js),同时实现SSL终止、缓存、负载均衡等功能。

        正向代理就是代理客户端向服务端发起请求。通常用于各种VPN。

3.1、创建一个Eureka注册中心

        Step1、引入eureka依赖 pom.xml

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>eureka-center</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <springframework.version>1.5.4.RELEASE</springframework.version>
        <springframework.version1>1.3.5.RELEASE</springframework.version1>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <version>${springframework.version1}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${springframework.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

        Step2、application.yml

server:
  port: 8001

#Eureka配置
eureka:
  instance:
    hostname: localhost  #Eureka服务端的实例名称
  client:
    register-with-eureka: false  #是否向eureka注册中心注册自己,因为这里本身就是eureka服务端,所以无需向eureka注册自己
    fetch-registry: false #fetch-registry为false,则表示自己为注册中心
    service-url:   #监控页面
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

        Step3、启动类

package com.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * @author: Wulc
 * @createTime: 2025-05-02
 * @description:
 * @version: 1.0
 */

@SpringBootApplication
@EnableEurekaServer  //使eureka服务端可以工作
public class SpringcloudEurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringcloudEurekaApplication.class, args);
    }
}

        项目启动成功后,浏览器输入:http://localhost:8001/ 一个Eureka注册中心就已经好了。

 

3.2、创建一个网关服务

        Step1、引入gateway依赖 ​​​​​ pom.xml

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>my-gateway</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <exclusions>
                <!-- 排除可能引入的Spring MVC依赖 -->
<!--                Spring Cloud Gateway基于WebFlux响应式框架(非阻塞式),而Spring MVC是传统的Servlet-based框架(阻塞式)。-->
<!--                当两者同时存在于classpath时,Spring Boot无法决定使用哪种Web服务器(Tomcat vs Netty),导致启动失败。-->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--        loadbalancer是负载均衡,对应yml中的lb-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>

    <!--    使用dependencyManagement统一管理SpringCloud组件,集中定义所有SpringCloud相关组件的兼容版本,避免手动指定每个依赖的版本号,-->
    <!--    解决版本冲突问题。我这里使用了2021.0.3,对应的是Springboot2.6.x-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2021.0.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

         Step2、编写yml文件 application.yml

server:
  port: 80

spring:
  main:
    web-application-type: reactive  # 强制使用WebFlux
  application:
    name: my-gateway-service
  profiles:
    include: route  #使用application-route.yml里面的配置

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8001/eureka/  # Eureka注册中心地址
    register-with-eureka: true
    fetch-registry: true
  instance:
    prefer-ip-address: true
    instance-id: ${spring.application.name}:${server.port}

        application-route.yml

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true  # 开启从注册中心动态创建路由
          lower-case-service-id: true  # 服务名小写
      routes:
        - id: route1
          uri: lb://wulc-test-consumer-server  # lb表示负载均衡 loadbalance
          predicates: #断定,遵守哪些规则,就把请求转发给wulc-test-consumer-server这个服务
            - Path=/api/wulc/**
        - id: route2
          uri: lb://wulc-test-server
          predicates:
            - Path=/api/test/**
          filters:
          	#StripPrefix=1表示移除请求路径中的第1个路径,
          	#即:如果前端的请求是/api/test/getMsg,那么gateway网关转发时就会找/test/getMsg
            - StripPrefix=1		
          order: 0

        Step3、启动类

package com.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @author Wulc
 * @date 2025/5/4 13:56
 * @description
 */
@SpringBootApplication
@EnableDiscoveryClient
public class ApplicationStarter {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationStarter.class, args);
    }
}

        启动成功后,在eureka上服务注册成功了。

3.3、创建一个普通服务

        Step1、引入相关依赖 pom.xml

<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">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>wulc-test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>wulc-test</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <springframework.version>1.5.4.RELEASE</springframework.version>
    <springframework.version1>1.3.5.RELEASE</springframework.version1>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>${springframework.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>${springframework.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <version>${springframework.version}</version>
    </dependency>
    <!--        eureka-->
    <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-eureka</artifactId>
      <version>${springframework.version1}</version>
    </dependency>
  </dependencies>

</project>

        Step2、yml文件 application.yml

server:
  port: 8084

spring:
  application:
    name: wulc-test-server

#eureka配置,服务注册到哪?
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8001/eureka/
  instance:
    #修改eureka上默认描述信息
    instance-id: ${spring.application.name}:${server.port}

        Step3、controller

package com.test.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: Wulc
 * @createTime: 2025-05-01
 * @description:
 * @version: 1.0
 */

@RestController
@RequestMapping("/test")
public class MsgController {

    @GetMapping("/getMsg")
    public String getMsg(){
        System.out.println("成功获取信息");
        return "成功获取信息";
    }
}

        Step4、启动类

package com.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @author: Wulc
 * @createTime: 2025-05-01
 * @description:
 * @version: 1.0
 */
@EnableDiscoveryClient
//@EnableEurekaClient
@SpringBootApplication
public class ApplicationStarter {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationStarter.class, args);
    }
}

        注意:@EnableDiscoveryClient和@EnableEurekaClient都是用于将微服务注册到服务注册中心的注解。但@EnableEurekaClient仅支持Eureka,而@EnableDiscoveryClient同时支持:Eureka、Consul、Zookeeper、Nacos 等。

        启动成功后,在Eureka上注册了该服务。

  

        通过网关,成功转发到了wulc-test-server服务上的/test/getMsg接口。

        我们可以分别用8085、8086端口号再启动两个服务,作为集群,来验证负载均衡。

 

         使用postman访问网关接口:http://localhost:80/api/test/getMsg 33次。可以看到gateway网关,将33次请求转发到了wulc-test-server:8084、wulc-test-server:8085、wulc-test-server:8086三个服务上面,起到了一个负载均衡的作用。

 

4、网关跨域

        所谓跨域,是指不同源的客户端/服务端,在没有对方授权的情况下是不允许发送/接收对方的数据资源的,会产生“跨域”情况。“跨域”是浏览器的一种保护机制,是由同源策略所导致的限制。所谓同源策略指的是:只有当两个url协议、域名和端口完全一致时,才认为是同源,否则就是跨域。跨域一般是在前端访问后端接口时产生的。在前端服务器上可以ping通相应的后端服务器,但是由于前后端服务器不同源,因此产生跨域无法访问。

 

        常见的跨域解决方法有,在Springboot控制类或者方法上加@CrossOrigin注解。

        @CrossOrigin注解有:value、origins、allowedHeaders、exposedHeaders、methods、allowCredentials、maxAge这些属性。

(1)value和origins是等价的,用于指定允许访问资源的来源,例:

// 允许单个源
@CrossOrigin(origins = "http://localhost:3000")

// 允许多个源
@CrossOrigin(origins = {"http://site1.com", "https://site2.com"})

// 允许所有源(慎用,生产环境不推荐)
@CrossOrigin(origins = "*")

(2)allowedHeaders,定义客户端可以在请求中携带的 HTTP 头字段(如 `Authorization`、`Content-Type`)。默认仅允许简单头(`Accept`、`Accept-Language`、`Content-Language`、`Content-Type`)。例:

// 允许自定义头
@CrossOrigin(allowedHeaders = {"X-Custom-Header", "Authorization"})

// 允许所有头(开放权限,慎用)
@CrossOrigin(allowedHeaders = "*")

(3)exposedHeaders,指定哪些响应头可以被浏览器访问(默认只能读取简单响应头)。例:

// 暴露自定义头
@CrossOrigin(exposedHeaders = "X-Total-Count")

// 暴露多个头
@CrossOrigin(exposedHeaders = {"X-Header1", "X-Header2"})

 (4)methods,限制允许的http方法,如get、post。例:

// 只允许 GET 和 POST
@CrossOrigin(methods = {RequestMethod.GET, RequestMethod.POST})

// 允许所有方法(开放权限,慎用)
@CrossOrigin(methods = "*")

(5)allowCredentials,是否允许凭据,用于控制是否允许浏览器发送凭据(如 CookieAuthorization 头)。默认 false(不允许),设为 true 时需配合 origins 明确指定来源(不能为 *)。例:

// 允许携带 Cookie
@CrossOrigin(allowCredentials = "true", origins = "http://trusted-site.com")
fetch("http://your-api.com/data", {
  credentials: "include"  // 发送 Cookie
});

(6)maxAge,预检请求缓存时间。所谓缓存预检是浏览器在有效时间内,不会重复对同一跨域请求进行检查,直接发送真实的请求。即,在第一次发送跨域访问时,浏览器会检查跨域请求的合法性。如果合法的话,那么在设置的maxAge时间内,如果是同一请求方再次发起跨域访问,浏览器直接跳过跨域合法性检查阶段。从而提升性能。例:

@CrossOrigin(origins = "http://xxx.com", maxAge = 3600)		maxAge单位为秒,即3600秒

        当然因为在实际中,前后端交互会涉及很多的服务和接口。如果一个服务一个服务,一个接口一个接口,一个控制类一个控制类的,去加@CrossOrigin,去各种配置,写CorsConfiguration类的话,会很麻烦,不便于统一管理。因此,我们可以统一在网关这层进行全局跨域规则配置。统一管理所有后端服务的跨域配置。

        我这里给一个简单的网关跨域yml配置。详细的配置方法可以参考一下文档:Spring Cloud Gateway 中文文档 或者问一下AI。

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origin-patterns: '*'  #允许所有的跨域
            allowed-headers: '*'  #允许所有的头
            allowed-methods: '*'  #允许所有的请求方式

5、总结

        其实SpringCloud Gateway网关的核心就是:路由、断言、过滤器。路由就是告诉网关该转发到哪个对应的后端服务上,断言就是转发规则,过滤器就是对请求api的过滤,比如前端调用后端接口,都会加一个/api前缀用于区分接口的类型(普通接口、feign接口)。但是在写后端接口时,通常不会在Controller里面@RequestMapping上加上/api,因此需要配置过滤器,网关转发请求时,把前缀去掉。网关通常可以用于身份认证。利用网关进行身份认证有两种方式:一种是在网关层直接校验token,另一种是网关将认证请求转发到专门的鉴权服务,由专门的鉴权服务校验token,并把校验结果返回网关,如果有权限,则网关再转发到相应的后端服务上。

        网关直接校验token(推荐使用这种)

 

        网关转发给鉴权服务校验token(比较复杂)

 

        微服务之间的调用,默认是不经过网关的。直接去注册中心,找到要调用的服务在哪里就行了,通过feign调用。

        微服务之间的调用,也是可以经过网关的。虽然从技术上可以实现微服务之间调用经过网关,但是实际中不建议经过网关,网关是对接前端的,后端之间的微服务调用,直接调用就行了,使用feign调用。这样还省了一层路径。除非外部后端系统要调用你这个服务的接口,最好加一层网关,进行身份认证。

        在典型的微服务架构中,无论是前端的请求,还是后端对前端请求的响应,都必须经过网关。

         为什么不能绕过网关?

6、参考资料

63、Gateway - 总结哔哩哔哩bilibili

基于Eureka的网关服务(gateway)配置-CSDN博客

SpringCloud微服务中gateway网关的使用(一)——(介绍+gateway与zuul的区别+gateway实现的两种方式)_SpringCloud-CSDN专栏

springcloud-eureka与gateway简易搭建_gateway注册到eureka-CSDN博客

网关 GateWay 的使用详解、路由、过滤器、跨域配置_gateway配置路由转发-CSDN博客

注解@CrossOrigin解决跨域问题 - 淼淼之森 - 博客园

文心一言

DeepSeek - 探索未至之境

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

金斗潼关

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值