Spring Cloud Gateway 实战:网关配置与 Sentinel 限流详解
在微服务架构中,网关扮演着统一入口、负载均衡、安全认证、限流等多种角色。Spring Cloud Gateway 是 Spring Cloud 官方推出的新一代网关组件,相比于第一代 Netflix Zuul,性能更强、功能更丰富,且基于 Netty 和 WebFlux 开发,完全非阻塞、响应式。
本文将详细介绍 Spring Cloud Gateway 的基础配置、与 Nacos 注册中心的整合,以及如何基于 Sentinel 进行网关限流(包括路由限流和 API 分组限流)。
什么是 Spring Cloud Gateway
Spring Cloud Gateway 是 Spring Cloud 官方网关组件,属于第二代网关解决方案,替代了 Zuul。
优点:
- 基于 Netty + WebFlux,性能更高
- 支持丰富的路由谓词和过滤器
- 与 Spring Cloud 生态无缝集成
注意事项:
- 不兼容 Servlet(例如 SpringMVC)
- 不支持 war 包部署,只能以 jar 形式运行
快速上手 Spring Cloud Gateway
引入依赖
一定不能引入 spring-boot-starter-web
,因为它基于 Servlet,会导致冲突。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
application.yml 配置
以下为最简单的静态路由配置(不使用注册中心):
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: provider_route
uri: http://localhost:8081
predicates:
- Path=/provider/**
filters:
- StripPrefix=1
- id: consumer_route
uri: http://localhost:8181
predicates:
- Path=/consumer/**
filters:
- StripPrefix=1
说明:
id
:路由标识uri
:转发地址predicates
:请求匹配条件(如路径)filters
:过滤器(如去前缀)
整合 Nacos 服务注册中心
通过 Nacos 让 Gateway 自动发现服务,无需手动配置 routes。
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
修改配置
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
开启后,Gateway 会自动根据 Nacos 上注册的服务自动创建路由。
Gateway 限流(基于 Sentinel)
在实际生产环境中,网关限流非常重要,可以防止后端服务被恶意或突发大流量冲击。
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
基于路由 ID 的限流
配置 routes
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: provider_route
uri: http://localhost:8081
predicates:
- Path=/provider/**
filters:
- StripPrefix=1
配置限流类
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("provider_route").setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = (exchange, throwable) -> {
Map<String, Object> map = new HashMap<>();
map.put("code", 0);
map.put("msg", "被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(map));
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
被限流后,返回自定义 JSON 响应 {"code":0,"msg":"被限流了"}
。
基于 API 分组的限流
除了基于路由 ID 的限流,还可以针对 URL 前缀或具体路径进行分组限流。
修改配置
只开启服务发现,不写 routes。
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
配置限流类
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("provider_api1").setCount(1).setIntervalSec(1));
rules.add(new GatewayFlowRule("provider_api2").setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = (exchange, throwable) -> {
Map<String, Object> map = new HashMap<>();
map.put("code", 0);
map.put("msg", "被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(map));
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("provider_api1")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/provider/api1/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("provider_api2")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/provider/api2/demo1"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
Controller 示例
@RestController
@RequestMapping("/provider")
public class DemoController {
@GetMapping("/api1/demo1")
public String demo1() {
return "demo1";
}
@GetMapping("/api1/demo2")
public String demo2() {
return "demo2";
}
@GetMapping("/api2/demo1")
public String demo3() {
return "demo3";
}
@GetMapping("/api2/demo2")
public String demo4() {
return "demo4";
}
}
限流效果:
- 对
/provider/api1/**
前缀限流,每秒允许 1 个请求 - 对
/provider/api2/demo1
单接口限流,每秒允许 1 个请求