Spring Cloud Gateway 实战:网关配置与 Sentinel 限流详解
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: gatewaycloud:gateway:routes:- id: provider_routeuri: http://localhost:8081predicates:- Path=/provider/**filters:- StripPrefix=1- id: consumer_routeuri: http://localhost:8181predicates:- 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: gatewaycloud: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: gatewaycloud:gateway:discovery:locator:enabled: trueroutes:- id: provider_routeuri: http://localhost:8081predicates:- 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);}@PostConstructpublic 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();}@PostConstructpublic 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: gatewaycloud: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);}@PostConstructpublic 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();}@PostConstructpublic 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);}@PostConstructprivate 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 个请求