Sentinel 支援對 Spring Cloud Gateway、Zuul 等主流的 API Gateway 進行限流。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iNmRjMkhDOxYDN2kjNjJWZxYDZ2UmMkNWMhNTMwQWMh9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 子產品,此子產品中包含網關限流的規則和自定義 API 的實體和管理邏輯:
GatewayFlowRule:網關限流規則,針對 API Gateway 的場景定制的限流規則,可以針對不同 route 或自定義的 API 分組進行限流,支援針對請求中的參數、Header、來源 IP 等進行定制化的限流。
ApiDefinition:使用者自定義的 API 定義分組,可以看做是一些 URL 比對的組合。比如我們可以定義一個 API 叫 my_api,請求 path 模式為 /foo/** 和 /baz/** 的都歸到 my_api 這個 API 分組下面。限流的時候可以針對這個自定義的 API 分組次元進行限流。
其中網關限流規則 GatewayFlowRule 的字段解釋如下:
resource:資源名稱,可以是網關中的 route 名稱或者使用者自定義的 API 分組名稱。
resourceMode:規則是針對 API Gateway 的 route(RESOURCE_MODE_ROUTE_ID)還是使用者在 Sentinel 中定義的 API 分組(RESOURCE_MODE_CUSTOM_API_NAME),預設是 route。
grade:限流名額次元,同限流規則的 grade 字段。
count:限流門檻值
intervalSec:統計時間視窗,機關是秒,預設是 1 秒。
controlBehavior:流量整形的控制效果,同限流規則的 controlBehavior 字段,目前支援快速失敗和勻速排隊兩種模式,預設是快速失敗。
burst:應對突發請求時額外允許的請求數目。
maxQueueingTimeoutMs:勻速排隊模式下的最長排隊時間,機關是毫秒,僅在勻速排隊模式下生效。
paramItem:參數限流配置。若不提供,則代表不針對參數進行限流,該網關規則将會被轉換成普通流控規則;否則會轉換成熱點規則。其中的字段:
parseStrategy:從請求中提取參數的政策,目前支援提取來源 IP(PARAM_PARSE_STRATEGY_CLIENT_IP)、Host(PARAM_PARSE_STRATEGY_HOST)、任意 Header(PARAM_PARSE_STRATEGY_HEADER)和任意 URL 參數(PARAM_PARSE_STRATEGY_URL_PARAM)四種模式。
fieldName:若提取政策選擇 Header 模式或 URL 參數模式,則需要指定對應的 header 名稱或 URL 參數名稱。
pattern:參數值的比對模式,隻有比對該模式的請求屬性值會納入統計和流控;若為空則統計該請求屬性的所有值。(1.6.2 版本開始支援)
matchStrategy:參數值的比對政策,目前支援精确比對(PARAM_MATCH_STRATEGY_EXACT)、子串比對(PARAM_MATCH_STRATEGY_CONTAINS)和正則比對(PARAM_MATCH_STRATEGY_REGEX)。(1.6.2 版本開始支援)
使用者可以通過 GatewayRuleManager.loadRules(rules) 手動加載網關規則,或通過 GatewayRuleManager.register2Property(property) 注冊動态規則源動态推送(推薦方式)。
Spring Cloud Gateway
從 1.6.0 版本開始,Sentinel 提供了 Spring Cloud Gateway 的适配子產品,可以提供兩種資源次元的限流:
route 次元:即在 Spring 配置檔案中配置的路由條目,資源名為對應的 routeId
自定義 API 次元:使用者可以利用 Sentinel 提供的 API 來自定義一些 API 分組
使用時需引入以下子產品(以 Maven 為例):
com.alibaba.csp
sentinel-spring-cloud-gateway-adapter
x.y.z
使用時隻需注入對應的 SentinelGatewayFilter 執行個體以及 SentinelGatewayBlockExceptionHandler 執行個體即可。比如:
@Configuration
public class GatewayConfiguration {
private final List viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
}
比如我們在 Spring Cloud Gateway 中配置了以下路由:
server:
port: 8090
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
enabled: true
discovery:
locator:
lower-case-service-id: true
routes:
# Add your routes here.
- id: product_route
uri: lb://product
predicates:
- Path=/product/**
- id: httpbin_route
uri: https://httpbin.org
predicates:
- Path=/httpbin/**
filters:
- RewritePath=/httpbin/(?.*), /$\{segment}
同時自定義了一些 API 分組:
private void initCustomizedApis() {
Set definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("some_customized_api")
.setPredicateItems(new HashSet() {{
add(new ApiPathPredicateItem().setPattern("/product/baz"));
add(new ApiPathPredicateItem().setPattern("/product/foo/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("another_customized_api")
.setPredicateItems(new HashSet() {{
add(new ApiPathPredicateItem().setPattern("/ahas"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
那麼這裡面的 route ID(如 product_route)和 API name(如 some_customized_api)都會被辨別為 Sentinel 的資源。比如通路網關的 URL 為 http://localhost:8090/product/foo/22 的時候,對應的統計會加到 product_route 和 some_customized_api 這兩個資源上面,而 http://localhost:8090/httpbin/json 隻會對應到 httpbin_route 資源上面。
您可以在 GatewayCallbackManager 注冊回調進行定制:
setBlockHandler:注冊函數用于實作自定義的邏輯處理被限流的請求,對應接口為 BlockRequestHandler。預設實作為 DefaultBlockRequestHandler,當被限流時會傳回類似于下面的錯誤資訊:Blocked by Sentinel: FlowException。
Zuul 1.x
Sentinel 提供了 Zuul 1.x 的适配子產品,可以為 Zuul Gateway 提供兩種資源次元的限流:
route 次元:即在 Spring 配置檔案中配置的路由條目,資源名為對應的 route ID(對應 RequestContext 中的 proxy 字段)
自定義 API 次元:使用者可以利用 Sentinel 提供的 API 來自定義一些 API 分組
使用時需引入以下子產品(以 Maven 為例):
com.alibaba.csp
sentinel-zuul-adapter
x.y.z
若使用的是 Spring Cloud Netflix Zuul,我們可以直接在配置類中将三個 filter 注入到 Spring 環境中即可:
@Configuration
public class ZuulConfig {
@Bean
public ZuulFilter sentinelZuulPreFilter() {
// We can also provider the filter order in the constructor.
return new SentinelZuulPreFilter();
}
@Bean
public ZuulFilter sentinelZuulPostFilter() {
return new SentinelZuulPostFilter();
}
@Bean
public ZuulFilter sentinelZuulErrorFilter() {
return new SentinelZuulErrorFilter();
}
}
Sentinel Zuul Adapter 生成的調用鍊路類似于下面,其中的資源名都是 route ID 或者自定義的 API 分組名稱:
-EntranceNode: sentinel_gateway_context$$route$$another-route-b(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:8 1mb:1 1mt:9)
--another-route-b(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:4 1mb:1 1mt:5)
--another_customized_api(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:4 1mb:0 1mt:4)
-EntranceNode: sentinel_gateway_context$$route$$my-route-1(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:6 1mb:0 1mt:6)
--my-route-1(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:2 1mb:0 1mt:2)
--some_customized_api(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:2 1mb:0 1mt:2)
發生限流之後的處理流程 :
發生限流之後可自定義傳回參數,通過實作 SentinelFallbackProvider 接口,預設的實作是 DefaultBlockFallbackProvider。
預設的 fallback route 的規則是 route ID 或自定義的 API 分組名稱。
比如:
// 自定義 FallbackProvider
public class MyBlockFallbackProvider implements ZuulBlockFallbackProvider {
private Logger logger = LoggerFactory.getLogger(DefaultBlockFallbackProvider.class);
// you can define route as service level
@Override
public String getRoute() {
return "/book/app";
}
@Override
public BlockResponse fallbackResponse(String route, Throwable cause) {
RecordLog.info(String.format("[Sentinel DefaultBlockFallbackProvider] Run fallback route: %s", route));
if (cause instanceof BlockException) {
return new BlockResponse(429, "Sentinel block exception", route);
} else {
return new BlockResponse(500, "System Error", route);
}
}
}
// 注冊 FallbackProvider
ZuulBlockFallbackManager.registerProvider(new MyBlockFallbackProvider());
預設情況下限流後會傳回 429 狀态碼,傳回結果為:
{
"code":429,
"message":"Sentinel block exception",
"route":"/"
}
網關流控實作原理
當通過 GatewayRuleManager 加載網關流控規則(GatewayFlowRule)時,無論是否針對請求屬性進行限流,Sentinel 底層都會将網關流控規則轉化為熱點參數規則(ParamFlowRule),存儲在 GatewayRuleManager 中,與正常的熱點參數規則相隔離。轉換時 Sentinel 會根據請求屬性配置,為網關流控規則設定參數索引(idx),并同步到生成的熱點參數規則中。
外部請求進入 API Gateway 時會經過 Sentinel 實作的 filter,其中會依次進行 路由/API 分組比對、請求屬性解析和參數組裝。Sentinel 會根據配置的網關流控規則來解析請求屬性,并依照參數索引順序組裝參數數組,最終傳入 SphU.entry(res, args) 中。Sentinel API Gateway Adapter Common 子產品向 Slot Chain 中添加了一個 GatewayFlowSlot,專門用來做網關規則的檢查。GatewayFlowSlot 會從 GatewayRuleManager 中提取生成的熱點參數規則,根據傳入的參數依次進行規則檢查。若某條規則不針對請求屬性,則會在參數最後一個位置置入預設的常量,達到普通流控的效果。
網關流控控制台
Sentinel 1.6.3 引入了網關流控控制台的支援,使用者可以直接在 Sentinel 控制台上檢視 API Gateway 實時的 route 和自定義 API 分組監控,管理網關規則和 API 分組配置。
在 API Gateway 端,使用者隻需要在原有啟動參數的基礎上添加如下啟動參數即可标記應用為 API Gateway 類型:
# 注:通過 Spring Cloud Alibaba Sentinel 自動接入的 API Gateway 整合則無需此參數
-Dcsp.sentinel.app.type=1
添加正确的啟動參數并有通路量後,我們就可以在 Sentinel 上面看到對應的 API Gateway 了。我們可以檢視實時的 route 和自定義 API 分組的監控和調用資訊,并針對其配置規則:
網關規則動态配置及網關叢集流控可以參考 AHAS Sentinel 網關流控。