本篇主要介紹Sentinel如何實現Spring Cloud應用的限流操作。
Sentinel接入Spring Cloud
- 創建一個基于Spring Boot的項目,并集成Greenwich.SR2版本的Spring Cloud依賴。
- 添加Sentinel依賴包
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
- 創建一個REST接口,并通過@SentinelResource配置限流保護資源
@RestController
public class SentinelDemoController {
@SentinelResource(value = "hello", blockHandler = "blockHandlerHello")
@GetMapping("/hello")
public String hello() {
return "Hello Sentinel";
}
public String blockHandlerHello(BlockException e) {
return "限流了";
}
}
在上面代碼中,配置限流資源有幾種情況:
- Sentinel starter在默認情況下會為所有HTTP服務提供限流埋點,所以如果只想對HTTP服務進行限流,只需要添加依賴即可。
- 如果想要對特定的方法進行限流或者降級,則需要通過@SentinelResource注解來實現限流資源的定義
- 可以通過SphU.entry()方法來配置資源。
- 手動配置流控規則,可以借助Sentinel的InitFunc SPI擴展接口來實現,只需要實現自己的InitFunc接口,并在init方法中編寫規則加載的邏輯即可。
public class FlowRuleInitFunc implements InitFunc {
@Override
public void init() throws Exception {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setCount(1);
rule.setResource("hello");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
SPI是擴展點機制,如果需要被Sentinel加載,那么還要在resource目錄下創建META-INF/services/com.alibaba.csp.sentinel.init.InitFunc文件,文件內容就是自定義擴展點的全路徑
com.sentinel.springcloud.demo.FlowRuleInitFunc
按照上述配置好之后,在初次訪問任意資源的時候,Sentinel就會自動加載hello資源的流控規則。
- 啟動服務后,訪問http://localhost:8080/hello方法,當訪問頻率超過設定閾值的時候,就會觸發限流。
上述配置過程是基于手動配置來加載流控規則的,還有一種方式就是通過Sentinel Dashboard來進行配置。
基于Sentinel Dashboard來實現流控配置
基于Sentinel Dashboard來配置流控規則,可以實現流控規則的動態配置,執行步驟如下:
-
啟動Sentinel Dashboard
在這里插入圖片描述 - 在application.yaml中增加如下配置:
spring:
application:
name: sentinel-spring-cloud-demo
cloud:
sentinel:
transport:
dashboard: localhost:7777
spring.cloud.sentinel.transport.dashboard指向的是Sentinel Dashboard的服務器地址,可以實現流控數據的監控和流控規則的分發。
- 提供一個REST接口:
@RestController
public class SentinelDashboardController {
@GetMapping("/dash")
public String dashboard() {
return "Hello Sentinel Dashboard";
}
}
此處不需要添加任何資源埋點,在默認情況下Sentinel Starter會對所有HTTP請求進行限流。
- 啟動服務后,此時訪問http://localhost:8080/dash,不存在任何限流行為。
至此,Spring Cloud集成Sentinel的配置就完成了,接下來就可以進入Sentinel Dashboard去實現限流規則的配置。
- 訪問local host:7777進入Sentinel Dashboard。
-
進入spring.application.name對應的菜單,訪問“簇點鏈路”,如下圖所示,在該列表下可以看到/dash這個REST接口的資源名稱。
在這里插入圖片描述 -
針對/dash這個資源,點擊最右邊的操作欄中的“流控”按鈕設置流控規則,如下圖所示:
在這里插入圖片描述
新增規則中的所有配置信息,實際就是FlowRule中對應的屬性配置。
-
新增完成后,再次訪問localhost:8080/dash,當超過設置的閾值的時候,就可以看到限流的效果,并獲得如下輸出:
在這里插入圖片描述
自定義URL限流異常
在默認情況下,URL觸發限流后會直接返回Blocked by Sentinel (flow limiting),但是在實際應用中,大都采用JSON格式的數據,所以如果希望修改觸發限流之后的返回結果形式,則可以通過自定義限流異常來處理,實現UrlBlockHandler并重寫blocked方法:
@Service
public class CustomUrlBlockHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
httpServletResponse.setHeader("Content-Type", "application/json;charset=UTF-8");
String message = "{\"code\":999,\"msg\":\"訪問人數過多\"}";
httpServletResponse.getWriter().write(message);
}
}
還有一種場景是:當觸發限流之后,我們希望直接跳轉到一個降級頁面,可以通過下面這個配置來實現:
spring.cloud.sentinel.servlet.block-page={url}
URL資源清洗
Sentinel中HTTP服務的限流默認由Sentinel-Web-Servlet包中的CommonFilter來實現,從代碼中可以看到,這個Filter會把每個不同的URL都作為不同的資源來處理。
在下面這段代碼中,提供一個攜帶{id}參數的REST風格的API,對于每一個不同的{id},URL也都不一樣,所以在默認情況下Sentinel會把所有的URL當作資源來進行流控。
@RestController
public class UrlCleanController {
@GetMapping("/clean/{id}")
public String clean(@PathVariable("id") int id) {
return "Hello URL";
}
}
這會導致兩個問題:
- 限流統計不準確,實際需求是控制clean方法總的QPS,結果統計的是每個URL的QPS。
- 導致Sentinel中資源數量過多,默認資源數量的閾值是6000, 對于多出的資源規則將不會生效。
針對這個問題,可以通過UrlCleaner接口來實現資源清洗,也就是對/clean/{id}這個URL,我們可以統一歸集到/clean/*資源下,具體配置代碼如下:
@Service
public class CustomUrlCleaner implements UrlCleaner {
@Override
public String clean(String s) {
if (StringUtils.isEmpty(s)) {
return s;
}
if (s.startsWith("/clean/")) {
return "/clean/*";
}
return s;
}
}