介紹
概覽圖

Sentinel API Gateway Adapter Common
**Sentinel 1.6.0 **引入了 Sentinel API Gateway Adapter Common 模塊,此模塊中包含網(wǎng)關(guān)限流的規(guī)則和自定義 API 的實體和管理邏輯:
-
GatewayFlowRule:網(wǎng)關(guān)限流規(guī)則,針對 API Gateway 的場景定制的限流規(guī)則,可以針對不同 route 或自定義的 API 分組進行限流,支持針對請求中的參數(shù)、Header、來源 IP 等進行定制化的限流。 -
ApiDefinition:用戶自定義的 API 定義分組,可以看做是一些 URL 匹配的組合。比如我們可以定義一個 API 叫my_api,請求 path 模式為/foo/**和/baz/**的都?xì)w到my_api這個 API 分組下面。限流的時候可以針對這個自定義的 API 分組維度進行限流。
GatewayFlowRule
其中網(wǎng)關(guān)限流規(guī)則 GatewayFlowRule 的字段解釋如下:
-
resource:資源名稱,可以是網(wǎng)關(guān)中的 route 名稱或者用戶自定義的 API 分組名稱。 -
resourceMode:規(guī)則是針對 API Gateway 的 route(RESOURCE_MODE_ROUTE_ID)還是用戶在 Sentinel 中定義的 API 分組(RESOURCE_MODE_CUSTOM_API_NAME),默認(rèn)是 route。 -
grade:限流指標(biāo)維度,同限流規(guī)則的grade字段。 -
count:限流閾值 -
intervalSec:統(tǒng)計時間窗口,單位是秒,默認(rèn)是 1 秒。 -
controlBehavior:流量整形的控制效果,同限流規(guī)則的controlBehavior字段,目前支持快速失敗和勻速排隊兩種模式,默認(rèn)是快速失敗。 -
burst:應(yīng)對突發(fā)請求時額外允許的請求數(shù)目。 -
maxQueueingTimeoutMs:勻速排隊模式下的最長排隊時間,單位是毫秒,僅在勻速排隊模式下生效。 -
paramItem:參數(shù)限流配置。若不提供,則代表不針對參數(shù)進行限流,該網(wǎng)關(guān)規(guī)則將會被轉(zhuǎn)換成普通流控規(guī)則;否則會轉(zhuǎn)換成熱點規(guī)則。其中的字段:-
parseStrategy:從請求中提取參數(shù)的策略,目前支持提取來源 IP(PARAM_PARSE_STRATEGY_CLIENT_IP)、Host(PARAM_PARSE_STRATEGY_HOST)、任意 Header(PARAM_PARSE_STRATEGY_HEADER)和任意 URL 參數(shù)(PARAM_PARSE_STRATEGY_URL_PARAM)四種模式。 -
fieldName:若提取策略選擇 Header 模式或 URL 參數(shù)模式,則需要指定對應(yīng)的 header 名稱或 URL 參數(shù)名稱。 -
pattern:參數(shù)值的匹配模式,只有匹配該模式的請求屬性值會納入統(tǒng)計和流控;若為空則統(tǒng)計該請求屬性的所有值。(1.6.2 版本開始支持) -
matchStrategy:參數(shù)值的匹配策略,目前支持精確匹配(PARAM_MATCH_STRATEGY_EXACT)、子串匹配(PARAM_MATCH_STRATEGY_CONTAINS)和正則匹配(PARAM_MATCH_STRATEGY_REGEX)。(1.6.2 版本開始支持)
-
網(wǎng)關(guān)規(guī)則加載
用戶可以通過 GatewayRuleManager.loadRules(rules) 手動加載網(wǎng)關(guān)規(guī)則,或通過 GatewayRuleManager.register2Property(property) 注冊動態(tài)規(guī)則源動態(tài)推送(推薦方式)。
Spring cloud gateway
從 1.6.0 版本開始,Sentinel 提供了 Spring Cloud Gateway 的適配模塊,可以提供兩種資源維度的限流:
- route 維度:即在 Spring 配置文件中配置的路由條目,資源名為對應(yīng)的 routeId
- 自定義 API 維度:用戶可以利用 Sentinel 提供的 API 來自定義一些 API 分組
pom依賴
使用時引入如下模塊
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>x.y.z</version>
</dependency>
注入實例到spring容器
使用時只需注入對應(yīng)的 SentinelGatewayFilter 實例以及 SentinelGatewayBlockExceptionHandler 實例即可。比如:
@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() {
// 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();
}
}
同時定義了一些Api分組
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("some_customized_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
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<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/ahas"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
Demo 示例:sentinel-demo-spring-cloud-gateway
比如我們在 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>.*), /$\{segment}
那么這里面的 route ID(如 product_route)和 API name(如 some_customized_api)都會被標(biāo)識為 Sentinel 的資源。比如訪問網(wǎng)關(guān)的 URL 為 http://localhost:8090/product/foo/22 的時候,對應(yīng)的統(tǒng)計會加到 product_route 和 some_customized_api 這兩個資源上面,而 http://localhost:8090/httpbin/json 只會對應(yīng)到 httpbin_route 資源上面。
實驗
請求http://localhost:8090/product/foo/22
wrk -c 10 -t 10 -d 100 'http://localhost:8090/product/foo/22'
控制臺輸出

當(dāng)前url的請求路徑是"http://localhost:8090/product/foo/22",可以命中的資源有aliyun_route 和 some_customized_api,aliyun_route的規(guī)則定義是按照client_ip限流qps是2,aliyun_route資源qps是10。 some_customized_api(/ahas,/product/**)定義的規(guī)則是url需要包含參數(shù)pn。因為沒有指定參數(shù)pn,所以some_customized_api沒有拒絕qps,全部都通過了。
請求http://localhost:8090/product/foo/22?pn=11
wrk -c 10 -t 10 -d 100 'http://localhost:8090/product/foo/22?pn=11'
控制臺輸出

分析基本和上面一致,因為url的param里加了pn=11, 所以導(dǎo)致some_customized_api的資源變成了通過qps=5.0
請求http://localhost:8090/httpbin/json?pa=1
wrk -c 10 -t 10 -d 100 'http://localhost:8090/httpbin/json?pa=1'
sentinel dashboard輸出

這個url請求命中的規(guī)則是,資源名:httpbin_route, url參數(shù)需要有pa,限流閥值是1。
請求http://localhost:8090/httpbin/json 添加header
wrk -c 10 -t 10 -d 100 -H 'X-Sentinel-Flag: aa' 'http://localhost:8090/httpbin/json'
sentinel dashboard輸出

這個url請求命中的規(guī)則是,資源名:httpbin_route, url的請求頭header中需要有X-Sentinel-Flag,限流閥值是1。
請求http://localhost:8090/httpbin/json?type=warn
wrk -c 10 -t 10 -d 100 'http://localhost:8090/httpbin/json?type=warn'
sentinel dashboard輸出

這個url請求命中的規(guī)則是,資源名:httpbin_route, url的參數(shù)需要是type=warn,限流閥值是2。代碼中setIntervalSec設(shè)置的是30,我改成了1,如果是30的話,通過qps展示的是0,側(cè)面說明了setIntervalSec值越低,采樣越精確。
請求http://localhost:8090/httpbin/json?type=warn 添加header
wrk -c 10 -t 10 -d 100 -H 'X-Sentinel-Flag: aa' 'http://localhost:8090/httpbin/json?type=warn'

可以看到qps閥值是2,其實這個請求同時滿足了Header中包含X-Sentinel-Flag和type=warn,在代碼中X-Sentinel-Flag的定義在前面(代碼中使用了HashSet,我改成了LinkedHashSet)可以看到后面定義的規(guī)則覆蓋了前面的規(guī)則。
限流回調(diào)
您可以在 GatewayCallbackManager 注冊回調(diào)進行定制:
-
setBlockHandler:注冊函數(shù)用于實現(xiàn)自定義的邏輯處理被限流的請求,對應(yīng)接口為BlockRequestHandler。默認(rèn)實現(xiàn)為DefaultBlockRequestHandler,當(dāng)被限流時會返回類似于下面的錯誤信息:Blocked by Sentinel: FlowException。
private void initCallback(){
GatewayCallbackManager.setBlockHandler(new DefaultBlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
t.printStackTrace();
return super.handleRequest(exchange,t);
}
});
}
瀏覽器限流的輸出

控制臺限流輸出

Zuul 1.x
Sentinel 提供了 Zuul 1.x 的適配模塊,可以為 Zuul Gateway 提供兩種資源維度的限流:
- route 維度:即在 Spring 配置文件中配置的路由條目,資源名為對應(yīng)的 route ID(對應(yīng)
RequestContext中的proxy字段) - 自定義 API 維度:用戶可以利用 Sentinel 提供的 API 來自定義一些 API 分組
使用時需引入以下模塊(以 Maven 為例):
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-zuul-adapter</artifactId>
<version>x.y.z</version>
</dependency>
若使用的是 Spring Cloud Netflix Zuul,我們可以直接在配置類中將三個 filter 注入到 Spring 環(huán)境中即可:
@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 生成的調(diào)用鏈路類似于下面,其中的資源名都是 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)
發(fā)生限流之后的處理流程 :
- 發(fā)生限流之后可自定義返回參數(shù),通過實現(xiàn)
SentinelFallbackProvider接口,默認(rèn)的實現(xiàn)是DefaultBlockFallbackProvider。 - 默認(rèn)的 fallback route 的規(guī)則是 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());
默認(rèn)情況下限流后會返回 429 狀態(tài)碼,返回結(jié)果為:
{
"code":429,
"message":"Sentinel block exception",
"route":"/"
}
Zuul 2.x
注:從 1.7.2 版本開始支持,需要 Java 8 及以上版本。
Sentinel 提供了 Zuul 2.x 的適配模塊,可以為 Zuul Gateway 提供兩種資源維度的限流:
- route 維度:對應(yīng) SessionContext 中的
routeVIP - 自定義 API 維度:用戶可以利用 Sentinel 提供的 API 來自定義一些 API 分組
使用時需引入以下模塊(以 Maven 為例):
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-zuul2-adapter</artifactId>
<version>x.y.z</version>
</dependency>
然后配置對應(yīng)的 filter 即可:
filterMultibinder.addBinding().toInstance(new SentinelZuulInboundFilter(500));
filterMultibinder.addBinding().toInstance(new SentinelZuulOutboundFilter(500));
filterMultibinder.addBinding().toInstance(new SentinelZuulEndpoint());
可以參考 sentinel-demo-zuul2-gateway 示例。
實現(xiàn)原理
當(dāng)通過 GatewayRuleManager 加載網(wǎng)關(guān)流控規(guī)則(GatewayFlowRule)時,無論是否針對請求屬性進行限流,Sentinel 底層都會將網(wǎng)關(guān)流控規(guī)則轉(zhuǎn)化為熱點參數(shù)規(guī)則(ParamFlowRule),存儲在 GatewayRuleManager 中,與正常的熱點參數(shù)規(guī)則相隔離。轉(zhuǎn)換時 Sentinel 會根據(jù)請求屬性配置,為網(wǎng)關(guān)流控規(guī)則設(shè)置參數(shù)索引(idx),并同步到生成的熱點參數(shù)規(guī)則中。
外部請求進入 API Gateway 時會經(jīng)過 Sentinel 實現(xiàn)的 filter,其中會依次進行 路由/API 分組匹配、請求屬性解析和參數(shù)組裝。Sentinel 會根據(jù)配置的網(wǎng)關(guān)流控規(guī)則來解析請求屬性,并依照參數(shù)索引順序組裝參數(shù)數(shù)組,最終傳入 SphU.entry(res, args) 中。Sentinel API Gateway Adapter Common 模塊向 Slot Chain 中添加了一個 GatewayFlowSlot,專門用來做網(wǎng)關(guān)規(guī)則的檢查。GatewayFlowSlot 會從 GatewayRuleManager 中提取生成的熱點參數(shù)規(guī)則,根據(jù)傳入的參數(shù)依次進行規(guī)則檢查。若某條規(guī)則不針對請求屬性,則會在參數(shù)最后一個位置置入預(yù)設(shè)的常量,達(dá)到普通流控的效果。

網(wǎng)關(guān)流控控制臺
在應(yīng)用啟動添加如下VM參數(shù)
-Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dcsp.sentinel.app.type=1
添加正確的啟動參數(shù)并有訪問量后,我們就可以在 Sentinel 上面看到對應(yīng)的 API Gateway 了。我們可以查看實時的 route 和自定義 API 分組的監(jiān)控和調(diào)用信息:

我們可以在控制臺配置自定義的 API 分組,將一些 URL 匹配模式歸為一個 API 分組:

然后我們可以在控制臺針對預(yù)設(shè)的 route ID 或自定義的 API 分組配置網(wǎng)關(guān)流控規(guī)則:

參考文檔
https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81