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 個請求