4月25號,Sentinel 1.6.0 正式發佈,帶來 Spring Cloud Gateway 支持、控制檯登陸功能、改進的熱點限流和註解 fallback 等多項新特性,該出手時就出手,緊跟時代潮流,昨天剛發佈,今天我就要給你們分享下如何使用!spring
Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模塊,此模塊中包含網關限流的規則和自定義 API 的實體和管理邏輯:json
GatewayFlowRule:網關限流規則,針對 API Gateway 的場景定製的限流規則,能夠針對不一樣 route 或自定義的 API 分組進行限流,支持針對請求中的參數、Header、來源 IP 等進行定製化的限流。後端
ApiDefinition:用戶自定義的 API 定義分組,能夠看作是一些 URL 匹配的組合。好比咱們能夠定義一個 API 叫 my_api,請求 path 模式爲 /foo/ 和 /baz/ 的都歸到 my_api 這個 API 分組下面。限流的時候能夠針對這個自定義的 API 分組維度進行限流。api
其中網關限流規則 GatewayFlowRule 的字段解釋以下:微信
用戶能夠經過 GatewayRuleManager.loadRules(rules) 手動加載網關規則,或經過 GatewayRuleManager.register2Property(property) 註冊動態規則源動態推送(推薦方式)。app
首先你的有一個Spring Cloud Gateway的項目,若是沒有,新建一個,增長Gateway和sentinel-spring-cloud-gateway-adapter的依賴,以下:ide
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> <version>1.6.0</version> </dependency>
新建一個application.yml配置文件,用來配置路由:學習
server: port: 2001 spring: application: name: spring-cloud-gateway cloud: gateway: routes: - id: path_route uri: http://cxytiandi.com predicates: - Path=/course
配置了Path路由,等會使用 http://localhost:2001/course 進行訪問便可。this
增長一個GatewayConfiguration 類,用於配置Gateway限流要用到的類,目前是手動配置的方式,後面確定是能夠經過註解啓用,配置文件中指定限流規則的方式來使用,固然這部分工做會交給Spring Cloud Alibaba來作,後面確定會發新版本的,你們耐心等待就好了。url
@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; } /** * 配置SentinelGatewayBlockExceptionHandler,限流後異常處理 * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } /** * 配置SentinelGatewayFilter * @return */ @Bean @Order(-1) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct public void doInit() { initGatewayRules(); } /** * 配置限流規則 */ private void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("path_route") .setCount(1) // 限流閾值 .setIntervalSec(1) // 統計時間窗口,單位是秒,默認是 1 秒 ); GatewayRuleManager.loadRules(rules); } }
咱們定義的資源名稱是path_route,也就是application.yml中的路由ID,一致就行。
在一秒鐘內屢次訪問http://localhost:2001/course就能夠看到限流啓做用了。
上面的配置是針對整個路由來限流的,若是咱們只想對某個路由的參數作限流,那麼可使用參數限流方式:
rules.add(new GatewayFlowRule("path_route") .setCount(1) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM).setFieldName("vipType") ) );
經過指定PARAM_PARSE_STRATEGY_URL_PARAM表示從url中獲取參數,setFieldName指定參數名稱
假設我有下面兩個路由,我想讓這兩個路由共用一個限流規則,那麼咱們能夠自定義進行組合:
- id: path2_route uri: http://cxytiandi.com predicates: - Path=/article - id: path3_route uri: http://cxytiandi.com predicates: - Path=/blog/**
自定義分組代碼:
private void initCustomizedApis() { Set<ApiDefinition> definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("customized_api") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ // article徹底匹配 add(new ApiPathPredicateItem().setPattern("/article")); // blog/開頭的 add(new ApiPathPredicateItem().setPattern("/blog/**") .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX)); }}); definitions.add(api1); GatewayApiDefinitionManager.loadApiDefinitions(definitions); }
而後咱們須要給customized_api這個資源進行配置:
rules.add(new GatewayFlowRule("customized_api") .setCount(1) .setIntervalSec(1) );
前面咱們有看到,當觸發限流後頁面顯示的是Blocked by Sentinel: FlowException,正常狀況下,就算給出提示也要跟後端服務的數據格式同樣,若是你後端都是JSON格式的數據,那麼異常的提示也要是JSON的格式,因此問題來了,咱們怎麼去自定義異常的輸出?
前面咱們有配置SentinelGatewayBlockExceptionHandler,個人註釋寫的限流後異常處理,咱們能夠進去看下源碼就知道是否是異常處理了。下面貼出核心的代碼:
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) { return response.writeTo(exchange, contextSupplier.get()); } @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { if (exchange.getResponse().isCommitted()) { return Mono.error(ex); } // This exception handler only handles rejection by Sentinel. if (!BlockException.isBlockException(ex)) { return Mono.error(ex); } return handleBlockedRequest(exchange, ex) .flatMap(response -> writeResponse(response, exchange)); }
重點在於writeResponse這個方法,咱們只要把這個方法改掉,返回本身想要返回的數據就好了,能夠自定義一個SentinelGatewayBlockExceptionHandler的類來實現。
好比:
public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler { // ........ }
而後複製SentinelGatewayBlockExceptionHandler中的代碼到JsonSentinelGatewayBlockExceptionHandler 中,只改動writeResponse一個方法便可。
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) { ServerHttpResponse serverHttpResponse = exchange.getResponse(); serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); byte[] datas = "{\"code\":403,\"msg\":\"限流了\"}".getBytes(StandardCharsets.UTF_8); DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas); return serverHttpResponse.writeWith(Mono.just(buffer)); }
最後將配置的SentinelGatewayBlockExceptionHandler改爲JsonSentinelGatewayBlockExceptionHandler 。
@Bean @Order(Ordered.HIGHEST_PRECEDENCE) public JsonSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); }