跳到主要内容微服务网关设计与实战:Spring Cloud Gateway 全解析 | 极客日志Javajava
微服务网关设计与实战:Spring Cloud Gateway 全解析
微服务网关作为微服务架构的核心入口,负责统一路由转发、安全防护及流量控制。基于 Spring Cloud Gateway 详解其核心架构(路由、断言、过滤器),涵盖静态与动态路由配置、JWT 统一认证、全局日志收集、跨域处理等实战功能。同时整合 Sentinel 实现限流熔断,并探讨高可用部署方案与性能优化策略,帮助开发者构建稳定高效的微服务网关系统。
云间运维25 浏览 微服务网关设计与实战:Spring Cloud Gateway 全解析

学习目标与重点
学习目标
- 理解微服务网关的核心价值、应用场景及主流网关技术对比,掌握 Spring Cloud Gateway 的核心架构与工作原理。
- 熟练使用 Spring Cloud Gateway 实现路由转发、动态路由、统一认证、限流熔断、日志监控等核心功能。
- 能够独立设计并搭建高可用、高性能的微服务网关,解决微服务架构中的接口统一管理、安全防护等问题。
- 掌握网关的性能优化、故障排查方法,结合实际场景进行网关架构设计与落地。
学习重点
- 微服务网关的核心作用与 Spring Cloud Gateway 的核心组件(路由、断言、过滤器)。
- 路由规则配置(静态路由、动态路由)与断言工厂的实战应用。
- 全局过滤器与局部过滤器的开发,实现统一认证、权限校验、请求改写等功能。
- 网关与 Sentinel、Nacos 的整合,实现限流、熔断、动态配置。
- 网关的高可用部署与性能优化方案。
微服务网关核心概念与技术选型
微服务网关的核心价值
在微服务架构中,客户端(Web/APP)需要与多个微服务直接通信,会面临接口分散、认证复杂、跨域问题、流量控制困难等挑战。微服务网关作为整个微服务架构的'入口',统一接收客户端请求,再转发到对应的微服务,相当于'流量调度中心'和'安全防护墙'。
微服务网关的核心价值体现在以下 6 个方面:
- 统一入口管理:客户端只需对接网关,无需记忆多个微服务的地址,简化客户端开发与维护。例如电商系统中,客户端通过
/api/user/*、/api/order/* 等统一路径访问不同服务,无需关心用户服务、订单服务的具体部署地址。
- 路由转发:根据请求路径、请求头、参数等规则,将请求转发到对应的微服务,支持负载均衡、路径重写等功能。
- 安全防护:集中处理认证授权、签名校验、防 SQL 注入、防 XSS 攻击等安全策略,避免每个微服务重复开发安全逻辑。
- 流量控制:对进入系统的流量进行限流、熔断、降级,保护后端微服务不被流量峰值压垮。
- 跨域解决方案:集中处理跨域请求(CORS),无需每个微服务单独配置跨域规则。
- 监控与日志:统一收集请求日志、监控请求耗时、错误率等指标,便于问题排查与系统运维。
主流微服务网关技术对比
目前 Java 生态中主流的微服务网关有 3 种:Spring Cloud Gateway、Zuul、Kong,各有优劣,需根据业务场景选型:
| 技术选型 | 核心优势 | 不足之处 | 适用场景 |
|---|
| Spring Cloud Gateway | 1. 基于 Spring 生态,与 Spring Cloud 无缝整合; 2. 基于 Netty 异步非阻塞,性能优异(吞吐量是 Zuul 的 2-3 倍); 3. 支持动态路由、灵活的断言与过滤器; 4. 原生支持 WebSocket、限流、熔断等功能 | 1. 仅支持 Spring Boot/Spring Cloud 项目; 2. 学习成本略高于 Zuul | Spring Cloud 生态的微服务项目,对性能要求较高的场景 |
| Zuul(1.x/2.x) |
1. 简单易用,与 Spring Cloud 整合成熟; 2. 社区成熟,文档丰富; 3. Zuul 2.x 支持异步非阻塞 |
1. Zuul 1.x 基于 Servlet 同步阻塞,性能较差; 2. 功能扩展性不如 Spring Cloud Gateway |
| Kong | 1. 基于 Nginx,性能极高,支持高并发; 2. 支持多语言微服务(不局限于 Java); 3. 丰富的插件生态(认证、限流、监控等) | 1. 与 Spring Cloud 生态整合不够紧密; 2. 配置复杂,二次开发成本高 | 多语言混合开发的微服务项目,对性能和并发要求极高的场景 |
推荐选型:Spring Cloud Gateway 是目前 Spring Cloud 生态的首选网关,兼顾性能、易用性与扩展性,本章重点讲解其设计与实战。
Spring Cloud Gateway 核心架构
Spring Cloud Gateway 基于异步非阻塞模型(Netty+Reactor)实现,核心架构包含 3 个核心组件,流程如下:
客户端请求 → 网关接收请求 → 路由匹配(断言 Predicate) → 过滤器链执行(Filter) → 转发到微服务 → 微服务响应 → 过滤器链反向执行 → 响应客户端
核心组件详解
- 路由(Route):网关的基本转发规则,包含 3 个关键信息:
- 路由 ID(Route ID):唯一标识路由的字符串(如
user-service-route)。
- 目标 URI(URI):请求最终转发到的微服务地址(如
lb://user-service,lb 表示负载均衡)。
- 断言集合(Predicate):判断请求是否匹配该路由的条件(如路径匹配
/api/user/**、请求方法为 GET)。
- 过滤器集合(Filter):请求转发前后执行的逻辑(如添加请求头、权限校验)。
- 断言(Predicate):本质是'条件判断函数',Spring Cloud Gateway 内置了多种断言工厂(如路径断言、请求头断言、时间断言),支持组合使用。只有当所有断言都满足时,请求才会匹配对应的路由。
- 过滤器(Filter):分为全局过滤器(Global Filter)和局部过滤器(Gateway Filter):
- 局部过滤器:仅对特定路由生效,需在路由配置中显式引用。
- 全局过滤器:对所有路由生效,无需配置,常用于统一认证、日志收集等全局功能。
- 过滤器执行顺序:请求转发前执行'pre'逻辑(如认证、参数校验),微服务响应后执行'post'逻辑(如响应改写、日志记录)。
Spring Cloud Gateway 环境搭建与基础路由配置
环境准备
技术栈选型
- 开发语言:Java 11
- 构建工具:Maven 3.6+
- 框架:Spring Boot 2.7.x、Spring Cloud 2021.0.4、Spring Cloud Gateway 3.1.4
- 依赖组件:Nacos Discovery(服务发现)、Sentinel(限流熔断)、Spring Security(认证)
- 开发工具:IntelliJ IDEA、Postman(接口测试)
项目初始化
① 创建 Maven 项目,命名为 gateway-service,引入核心依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
⚠️ 注意:Spring Cloud Gateway 基于 Netty 实现,与 Spring MVC 的 spring-boot-starter-web 存在冲突,必须排除 spring-boot-starter-web 依赖,否则启动报错。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
③ 编写基础配置文件 application.yml:
spring:
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- RewritePath=/api/user/(?<segment>.*), /${segment}
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- RewritePath=/api/order/(?<segment>.*), /${segment}
server:
port: 8080
logging:
level:
org.springframework.cloud.gateway: DEBUG
org.springframework.web.reactive: INFO
基础路由测试
启动依赖服务
① 启动 Nacos Server(确保 user-service、order-service 已注册到 Nacos)。
② 启动 user-service(端口 8081)、order-service(端口 8082)。
③ 启动 gateway-service(端口 8080)。
测试路由转发
使用 Postman 发送请求,验证路由转发功能:
- 请求地址:
http://localhost:8080/api/user/1
- 预期结果:网关将请求转发到
user-service 的 /1 接口,返回用户信息:
- 请求地址:
http://localhost:8080/api/order/1
- 预期结果:网关将请求转发到
order-service 的 /1 接口,返回订单信息:
{"orderId":1695888888888,"userId":1,"username":"张三 1","productName":"Java 编程思想","price":89.0,"count":1,"totalAmount":89.0,"status":"已创建"}
{"id":1,"username":"张三 1","phone":"13800138001","address":"北京市朝阳区"}
- 由于开启了
discovery.locator.enabled=true,网关会自动生成路由:http://localhost:8080/服务名/接口路径
- 测试地址:
http://localhost:8080/user-service/user/1(直接通过服务名访问),返回结果与步骤 1 一致。
核心断言工厂实战
Spring Cloud Gateway 内置了 11 种断言工厂(Predicate Factory),支持根据路径、请求头、请求参数、时间、Cookie 等条件匹配路由。断言工厂的命名格式为 XXXRoutePredicateFactory,配置时只需写前缀(如 Path 对应 PathRoutePredicateFactory)。
常用断言工厂示例
修改 application.yml 中的路由断言配置,演示常用断言的使用:
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/api/user/**, /user/**
- Method=GET
- Header=X-Request-Id, \d+
- Query=token, auth.*
- After=2024-01-01T00:00:00+08:00[Asia/Shanghai]
filters:
- RewritePath=/api/user/(?<segment>.*), /${segment}
- RewritePath=/user/(?<segment>.*), /${segment}
断言测试
- 符合所有断言的请求:
- 请求地址:
http://localhost:8080/api/user/1?token=auth123
- 请求头:
X-Request-Id: 123456
- 请求方法:GET
- 预期结果:路由匹配成功,返回用户信息。
- 不符合断言的请求:
- 缺少
X-Request-Id 请求头 → 路由不匹配,返回 404。
- 请求方法为 POST → 路由不匹配,返回 404。
token 参数值为 test123(不包含 auth) → 路由不匹配,返回 404。
自定义断言工厂(可选)
若内置断言无法满足需求,可自定义断言工厂。例如,实现一个'IP 白名单'断言,仅允许指定 IP 访问:
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
@Component
public class IpWhiteListRoutePredicateFactory extends AbstractRoutePredicateFactory<IpWhiteListRoutePredicateFactory.Config> {
@Validated
public static class Config {
@NotEmpty(message = "IP 白名单不能为空")
private List<String> allowedIps;
public List<String> getAllowedIps() { return allowedIps; }
public void setAllowedIps(List<String> allowedIps) { this.allowedIps = allowedIps; }
}
public IpWhiteListRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("allowedIps");
}
@Override
public GatewayPredicate apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String clientIp = request.getRemoteAddress().getAddress().getHostAddress();
return config.getAllowedIps().contains(clientIp);
}
@Override
public String toString() {
return "IpWhiteList: " + config.getAllowedIps();
}
};
}
}
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/api/user/**
- IpWhiteList=127.0.0.1, 192.168.1.100
filters:
- RewritePath=/api/user/(?<segment>.*), /${segment}
- 本地 IP(127.0.0.1)访问 → 正常返回。
- 其他 IP(如 192.168.1.101)访问 → 路由不匹配,返回 404。
过滤器开发实战:全局功能与局部定制
过滤器分类与执行顺序
Spring Cloud Gateway 的过滤器分为局部过滤器和全局过滤器,核心区别在于作用范围和配置方式:
| 过滤器类型 | 作用范围 | 配置方式 | 典型场景 |
|---|
| 局部过滤器 | 特定路由 | 在路由的 filters 节点中配置 | 路径重写、局部限流、特定路由的参数处理 |
| 全局过滤器 | 所有路由 | 实现 GlobalFilter 接口,无需配置 | 统一认证、日志收集、跨域处理、全局限流 |
过滤器执行顺序
- 局部过滤器:按配置文件中 filters 的顺序执行。
- 全局过滤器:按
Ordered 接口的 getOrder() 方法返回值排序(值越小,执行优先级越高)。
- 整体顺序:所有过滤器按优先级排序,'pre'逻辑按顺序执行,'post'逻辑按逆序执行。
局部过滤器实战:路径重写与参数处理
局部过滤器仅对当前路由生效,Spring Cloud Gateway 内置了多种局部过滤器(如路径重写、请求头添加、参数添加等),以下是常用场景实战:
路径重写(RewritePath)
已在基础配置中演示,核心作用是将客户端请求路径改写为微服务的实际路径。例如:
- 客户端请求:
/api/user/1 → 改写为 /1 → 转发到 user-service 的 /1 接口。
- 配置格式:
RewritePath=原始路径正则,目标路径(使用 ${segment} 引用正则分组)。
为转发到微服务的请求添加固定请求头,例如添加 X-Gateway-Version 标识网关版本:
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- RewritePath=/api/user/(?<segment>.*), /${segment}
- AddRequestHeader=X-Gateway-Version, v1.0.0
添加请求参数(AddRequestParameter)
为请求添加固定参数,例如添加 source=gateway 标识请求来源:
filters:
- AddRequestParameter=source, gateway
自定义局部过滤器
实现一个局部过滤器,为特定路由的响应添加 X-Response-Time 头,记录请求处理耗时:
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Component
public class ResponseTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<ResponseTimeGatewayFilterFactory.Config> {
public static class Config {}
public ResponseTimeGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList();
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
long startTime = System.currentTimeMillis();
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long duration = System.currentTimeMillis() - startTime;
exchange.getResponse().getHeaders().add("X-Response-Time", duration + "ms");
}));
};
}
}
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- RewritePath=/api/user/(?<segment>.*), /${segment}
- ResponseTime
- 访问
http://localhost:8080/api/user/1,查看响应头,会新增 X-Response-Time: 20ms(耗时根据实际情况变化)。
全局过滤器实战:统一认证与日志收集
全局过滤器对所有路由生效,是实现统一功能的核心手段。以下是两个最常用的全局过滤器实战:
统一认证过滤器(JWT 令牌校验)
微服务架构中,客户端登录成功后获取 JWT 令牌,后续所有请求都需在请求头中携带 Authorization: Bearer {token},网关统一校验令牌有效性,无效则直接返回 401,避免每个微服务重复开发认证逻辑。
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
@Component
public class JwtUtil {
@Value("${jwt.secret:abcdefghijklmnopqrstuvwxyz1234567890}")
private String secret;
@Value("${jwt.expire:7200000}")
private long expire;
private SecretKey getSecretKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
public String generateToken(Long userId, String username) {
return Jwts.builder()
.setSubject(userId.toString())
.claim("username", username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expire))
.signWith(getSecretKey())
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(getSecretKey()).build().parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
public Claims parseClaims(String token) {
return Jwts.parserBuilder().setSigningKey(getSecretKey()).build().parseClaimsJws(token).getBody();
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Autowired
private JwtUtil jwtUtil;
private static final String[] WHITE_LIST = {
"/api/user/login",
"/api/user/register",
"/actuator/health"
};
@Override
public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String path = request.getPath().value();
for (String whitePath : WHITE_LIST) {
if (path.startsWith(whitePath)) {
return chain.filter(exchange);
}
}
String authHeader = request.getHeaders().getFirst("Authorization");
if (!StringUtils.hasText(authHeader) || !authHeader.startsWith("Bearer ")) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
String token = authHeader.substring(7);
if (!jwtUtil.validateToken(token)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
try {
String userId = jwtUtil.parseClaims(token).getSubject();
String username = jwtUtil.parseClaims(token).get("username", String.class);
ServerHttpRequest newRequest = request.mutate()
.header("X-User-Id", userId)
.header("X-User-Name", username)
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
} catch (Exception e) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
}
@Override
public int getOrder() {
return -100;
}
}
- 假设 user-service 提供登录接口
/api/user/login,传入用户名密码,返回令牌:
获取 JWT 令牌(模拟登录接口,实际开发中由 user-service 实现):
{"code":200,"msg":"登录成功","data":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
- 携带令牌访问受保护接口:
- 请求地址:
http://localhost:8080/api/user/1
- 请求头:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
- 预期结果:令牌有效,正常返回用户信息。
- 不携带令牌或令牌无效:
- 直接访问
http://localhost:8080/api/user/1 → 返回 401 Unauthorized。
全局日志过滤器
收集所有请求的日志(请求路径、方法、IP、耗时、响应状态等),便于问题排查与系统监控:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.UUID;
@Component
public class LogGlobalFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(LogGlobalFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) {
String requestId = UUID.randomUUID().toString();
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
long startTime = System.currentTimeMillis();
String method = request.getMethodValue();
String path = request.getPath().value();
String clientIp = request.getRemoteAddress().getAddress().getHostAddress();
logger.info("【请求开始】requestId: {}, 客户端 IP: {}, 方法:{}, 路径:{}, 请求头:{}", requestId, clientIp, method, path, request.getHeaders());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long duration = System.currentTimeMillis() - startTime;
int statusCode = response.getStatusCode().value();
logger.info("【请求结束】requestId: {}, 响应状态:{}, 耗时:{}ms", requestId, statusCode, duration);
}));
}
@Override
public int getOrder() {
return -90;
}
}
【请求开始】requestId: 550e8400-e29b-41d4-a716-446655440000, 客户端 IP: 127.0.0.1, 方法:GET, 路径:/api/user/1, 请求头:[Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", X-Request-Id:"123456"] 【请求结束】requestId: 550e8400-e29b-41d4-a716-446655440000, 响应状态:200, 耗时:35ms
跨域处理(CORS)
当客户端(如前端 Vue 项目)与网关不在同一域名时,会出现跨域请求问题。Spring Cloud Gateway 支持全局配置 CORS,无需每个微服务单独配置:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-methods: "*"
allowed-headers: "*"
allow-credentials: true
max-age: 3600
网关高级功能:动态路由、限流熔断与高可用
动态路由(基于 Nacos 配置)
基础路由配置写在 application.yml 中,修改后需重启网关才能生效,无法满足生产环境的动态调整需求。基于 Nacos 实现动态路由,可通过 Nacos 控制台修改路由配置,网关实时感知并生效,无需重启。
集成 Nacos Config
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.0.4.0</version>
</dependency>
② 创建 bootstrap.yml 文件(加载 Nacos 配置):
spring:
application:
name: gateway-service
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
namespace: public
group: DEFAULT_GROUP
profiles:
active: dev
- Data ID:
gateway-service-dev.yaml(格式:服务名 - 环境名。文件扩展名)。
- 配置格式:YAML。
- 配置内容(路由规则):
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- RewritePath=/api/user/(?<segment>.*), /${segment}
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- RewritePath=/api/order/(?<segment>.*), /${segment}
实现动态路由刷新
Spring Cloud Gateway 与 Nacos Config 整合后,默认支持配置动态刷新,但路由规则的刷新需要自定义逻辑(监听 Nacos 配置变更,重新加载路由):
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.Component;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
@Component
public class DynamicRouteConfig implements ApplicationEventPublisherAware {
@Value("${spring.cloud.nacos.config.server-addr}")
private String serverAddr;
@Value("${spring.cloud.nacos.config.namespace}")
private String namespace;
@Value("${spring.cloud.nacos.config.group}")
private String group;
@Value("${spring.application.name}")
private String serviceName;
@Value("${spring.profiles.active}")
private String activeProfile;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher applicationEventPublisher;
private ConfigService configService;
private String dataId;
@PostConstruct
public void init() throws NacosException {
dataId = serviceName + "-" + activeProfile + ".yaml";
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
properties.put("namespace", namespace);
configService = NacosFactory.createConfigService(properties);
loadRouteConfig();
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
loadRouteConfig();
}
@Override
public Executor getExecutor() {
return null;
}
});
}
private void loadRouteConfig() {
try {
String config = configService.getConfig(dataId, group, 5000);
if (org.springframework.util.StringUtils.hasText(config)) {
List<RouteDefinition> routeDefinitions = parseYamlToRouteDefinitions(config);
routeDefinitionWriter.delete(Mono.just("user-service-route")).subscribe();
routeDefinitionWriter.delete(Mono.just("order-service-route")).subscribe();
for (RouteDefinition routeDefinition : routeDefinitions) {
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
}
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
System.out.println("动态路由更新成功!");
}
} catch (Exception e) {
System.err.println("动态路由更新失败:" + e.getMessage());
}
}
private List<RouteDefinition> parseYamlToRouteDefinitions(String config) {
return null;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
⚠️ 注意:实际开发中,parseYamlToRouteDefinitions 方法需使用 SnakeYAML 或 Spring 的 YamlPropertiesFactoryBean 解析 Nacos 中的 YAML 配置,将其转换为 List<RouteDefinition> 对象,确保路由规则正确加载。
测试动态路由
- 在 Nacos 控制台修改
gateway-service-dev.yaml,新增一个商品服务路由:
- id: product-service-route
uri: lb://product-service
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/product/(?<segment>.*), /${segment}
- 点击'发布',网关控制台输出'动态路由更新成功!'。
- 访问
http://localhost:8080/api/product/1,网关会自动转发到 product-service,无需重启网关。
网关限流与熔断(整合 Sentinel)
网关作为流量入口,需要对进入系统的流量进行限流,避免后端微服务被压垮;同时,当后端微服务故障时,网关需要实现熔断,直接返回降级响应,避免故障扩散。Spring Cloud Gateway 与 Sentinel 整合,可快速实现限流与熔断功能。
集成 Sentinel 依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2021.0.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>2021.0.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.6</version>
</dependency>
配置 Sentinel
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8720
scg:
fallback:
mode: response
response-status: 429
response-body: '{"code":429,"msg":"当前请求过于频繁,请稍后再试!"}'
⚠️ 注意:Sentinel 控制台默认端口为 8080,与网关端口冲突,需修改 Sentinel 启动命令:
java -jar sentinel-dashboard-1.8.6.jar --server.port=8083
修改后 Sentinel 控制台地址为 http://localhost:8083,网关配置中的 spring.cloud.sentinel.transport.dashboard 需同步修改为 localhost:8083。
配置网关限流规则(Sentinel 控制台)
- 启动网关、Sentinel 控制台,访问网关接口(如
http://localhost:8080/api/user/1),触发 Sentinel 监控。
- 访问 Sentinel 控制台→网关限流→新增网关流控规则:
- 资源名称:
/api/user/**(路由路径)。
- 限流指标:QPS。
- 单机阈值:5(每秒最多允许 5 个请求)。
- 其他默认,点击'新增'。
测试限流效果
快速访问 http://localhost:8080/api/user/1(每秒超过 5 次),网关返回自定义降级响应:
{"code":429,"msg":"当前请求过于频繁,请稍后再试!"}
配置网关熔断规则
当后端微服务(如 user-service)故障时,网关熔断,避免频繁调用失败:
- 在 Sentinel 控制台→熔断规则→新增熔断规则:
- 资源名称:
lb://user-service(微服务名称)。
- 熔断策略:慢调用比例。
- 最大 RT:1000ms(超过 1 秒视为慢调用)。
- 慢调用比例阈值:0.5(慢调用占比超过 50%)。
- 熔断时长:10s。
- 最小请求数:5。
- 故意修改
user-service 的接口,添加 2 秒延迟:
@GetMapping("/user/{id}")
public User getUserById(@PathVariable Long id) throws InterruptedException {
Thread.sleep(2000);
}
- 快速访问
http://localhost:8080/api/user/1(超过 5 次),触发熔断,网关返回降级响应。
网关高可用部署
网关作为微服务架构的入口,一旦单点故障,整个系统将无法对外提供服务。因此,生产环境必须部署多个网关实例,配合负载均衡器(如 Nginx)实现高可用。
高可用架构图
客户端 → Nginx(负载均衡) → 网关实例 1(8080) → 微服务集群
→ 网关实例 2(8081) → 微服务集群
→ 网关实例 3(8082) → 微服务集群
部署步骤
- 启动多个网关实例:
- 实例 1:
java -jar gateway-service.jar --server.port=8080
- 实例 2:
java -jar gateway-service.jar --server.port=8081
- 实例 3:
java -jar gateway-service.jar --server.port=8082
- 所有实例注册到 Nacos,自动实现服务发现与负载均衡。
- 配置 Nginx 负载均衡:
编辑 Nginx 配置文件
nginx.conf:
http {
# 网关集群配置
upstream gateway_cluster {
server 127.0.0.1:8080 weight=1; # 权重 1
server 127.0.0.1:8081 weight=1; # 权重 1
server 127.0.0.1:8082 weight=1; # 权重 1
}
server {
listen 80; # Nginx 监听 80 端口(客户端统一访问 80 端口)
server_name localhost;
location / {
# 转发请求到网关集群
proxy_pass http://gateway_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
- 测试高可用:
- 客户端访问
http://localhost/api/user/1(Nginx 端口 80),Nginx 会将请求转发到任意一个网关实例。
- 关闭其中一个网关实例(如 8080),客户端请求仍能正常响应(Nginx 自动转发到其他实例)。
网关性能优化与故障排查
性能优化方案
基础优化
- 使用异步非阻塞模型:Spring Cloud Gateway 默认基于 Netty 异步非阻塞,无需额外配置,避免使用同步过滤器(如依赖 Spring MVC 的组件)。
- 调整 Netty 参数:
spring:
cloud:
gateway:
httpclient:
pool:
max-idle-time: 60000
max-connections: 1000
response-timeout: 5000
- 启用连接复用:开启 HTTP/2 协议,支持连接复用,提升并发性能:
server:
http2:
enabled: true
- 减少不必要的过滤器:仅保留核心功能过滤器(如认证、日志),避免过滤器链过长导致性能损耗。
缓存优化
- 对高频访问的静态资源(如图片、文档)或查询接口,在网关层添加缓存(如使用 Redis),减少对后端微服务的请求。
- 示例:使用
spring-cloud-gateway-cache 组件实现网关缓存(需额外引入依赖)。
限流优化
- 限流规则优先使用 Sentinel 的本地限流,避免分布式限流带来的网络开销。
- 对不同路由配置不同的限流阈值,根据微服务的处理能力动态调整。
故障排查方法
日志排查
- 开启 Gateway DEBUG 日志,打印请求转发细节:
logging:
level:
org.springframework.cloud.gateway: DEBUG
org.springframework.http.server.reactive: DEBUG
reactor.netty: DEBUG
- 关键日志包括:路由匹配过程、过滤器执行顺序、请求转发地址、响应状态等。
监控排查
- 集成 Spring Boot Actuator,暴露网关监控指标:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management:
endpoints:
web:
exposure:
include: gateway, health, info
endpoint:
gateway:
enabled: true
- 访问
http://localhost:8080/actuator/gateway/routes,查看当前路由配置;访问 http://localhost:8080/actuator/gateway/globalfilters,查看全局过滤器。
常见问题与解决方案
| 常见问题 | 解决方案 |
|---|
| 路由匹配失败(404) | 1. 检查路由 Path 断言是否正确; 2. 检查微服务是否已注册到 Nacos; 3. 开启 discovery.locator.enabled=true,测试服务名路由 |
| 跨域请求失败(CORS) | 检查网关全局 CORS 配置,确保 allowed-origins、allowed-methods 配置正确 |
| 认证失败(401) | 1. 检查 JWT 令牌是否有效; 2. 检查请求头 Authorization 格式是否正确(Bearer + 空格 + 令牌); 3. 检查白名单配置是否包含当前路径 |
| 限流熔断不生效 | 1. 检查 Sentinel 依赖是否完整; 2. 确认已访问网关接口触发 Sentinel 监控; 3. 检查 Sentinel 控制台规则配置是否正确 |
| 网关响应缓慢 | 1. 排查后端微服务响应时间; 2. 检查过滤器链是否过长; 3. 调整 Netty 连接池参数 |
本章总结
✅ 本章详细讲解了微服务网关的核心价值、Spring Cloud Gateway 的核心架构与实战应用,从基础路由配置到高级功能(动态路由、统一认证、限流熔断、高可用部署),完整覆盖了网关的设计与落地流程。
- 微服务网关的核心作用与 Spring Cloud Gateway 的三大核心组件(路由、断言、过滤器)。
- 局部过滤器与全局过滤器的开发,实现路径重写、统一认证、日志收集等功能。
- 基于 Nacos 的动态路由与基于 Sentinel 的限流熔断实现。
- 网关的高可用部署、性能优化与故障排查方法。
网关作为微服务架构的'入口',是系统安全防护、流量控制的关键环节。下一章将讲解微服务监控与运维体系,包括 Prometheus + Grafana 监控指标收集、ELK 日志分析、SkyWalking 链路追踪等内容,帮助读者构建完整的微服务运维体系。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online