本篇主要介紹Sentinel如何實現(xiàn)Spring Cloud應(yīng)用的限流操作。
Sentinel接入Spring Cloud
- 創(chuàng)建一個基于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>
- 創(chuàng)建一個REST接口,并通過@SentinelResource配置限流保護(hù)資源
@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在默認(rèn)情況下會為所有HTTP服務(wù)提供限流埋點,所以如果只想對HTTP服務(wù)進(jìn)行限流,只需要添加依賴即可。
- 如果想要對特定的方法進(jìn)行限流或者降級,則需要通過@SentinelResource注解來實現(xiàn)限流資源的定義
- 可以通過SphU.entry()方法來配置資源。
- 手動配置流控規(guī)則,可以借助Sentinel的InitFunc SPI擴(kuò)展接口來實現(xiàn),只需要實現(xiàn)自己的InitFunc接口,并在init方法中編寫規(guī)則加載的邏輯即可。
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是擴(kuò)展點機(jī)制,如果需要被Sentinel加載,那么還要在resource目錄下創(chuàng)建META-INF/services/com.alibaba.csp.sentinel.init.InitFunc文件,文件內(nèi)容就是自定義擴(kuò)展點的全路徑
com.sentinel.springcloud.demo.FlowRuleInitFunc
按照上述配置好之后,在初次訪問任意資源的時候,Sentinel就會自動加載hello資源的流控規(guī)則。
- 啟動服務(wù)后,訪問http://localhost:8080/hello方法,當(dāng)訪問頻率超過設(shè)定閾值的時候,就會觸發(fā)限流。
上述配置過程是基于手動配置來加載流控規(guī)則的,還有一種方式就是通過Sentinel Dashboard來進(jìn)行配置。
基于Sentinel Dashboard來實現(xiàn)流控配置
基于Sentinel Dashboard來配置流控規(guī)則,可以實現(xiàn)流控規(guī)則的動態(tài)配置,執(zhí)行步驟如下:
-
啟動Sentinel Dashboard
在這里插入圖片描述 - 在application.yaml中增加如下配置:
spring:
application:
name: sentinel-spring-cloud-demo
cloud:
sentinel:
transport:
dashboard: localhost:7777
spring.cloud.sentinel.transport.dashboard指向的是Sentinel Dashboard的服務(wù)器地址,可以實現(xiàn)流控數(shù)據(jù)的監(jiān)控和流控規(guī)則的分發(fā)。
- 提供一個REST接口:
@RestController
public class SentinelDashboardController {
@GetMapping("/dash")
public String dashboard() {
return "Hello Sentinel Dashboard";
}
}
此處不需要添加任何資源埋點,在默認(rèn)情況下Sentinel Starter會對所有HTTP請求進(jìn)行限流。
- 啟動服務(wù)后,此時訪問http://localhost:8080/dash,不存在任何限流行為。
至此,Spring Cloud集成Sentinel的配置就完成了,接下來就可以進(jìn)入Sentinel Dashboard去實現(xiàn)限流規(guī)則的配置。
- 訪問local host:7777進(jìn)入Sentinel Dashboard。
-
進(jìn)入spring.application.name對應(yīng)的菜單,訪問“簇點鏈路”,如下圖所示,在該列表下可以看到/dash這個REST接口的資源名稱。
在這里插入圖片描述 -
針對/dash這個資源,點擊最右邊的操作欄中的“流控”按鈕設(shè)置流控規(guī)則,如下圖所示:
在這里插入圖片描述
新增規(guī)則中的所有配置信息,實際就是FlowRule中對應(yīng)的屬性配置。
-
新增完成后,再次訪問localhost:8080/dash,當(dāng)超過設(shè)置的閾值的時候,就可以看到限流的效果,并獲得如下輸出:
在這里插入圖片描述
自定義URL限流異常
在默認(rèn)情況下,URL觸發(fā)限流后會直接返回Blocked by Sentinel (flow limiting),但是在實際應(yīng)用中,大都采用JSON格式的數(shù)據(jù),所以如果希望修改觸發(fā)限流之后的返回結(jié)果形式,則可以通過自定義限流異常來處理,實現(xiàn)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\":\"訪問人數(shù)過多\"}";
httpServletResponse.getWriter().write(message);
}
}
還有一種場景是:當(dāng)觸發(fā)限流之后,我們希望直接跳轉(zhuǎn)到一個降級頁面,可以通過下面這個配置來實現(xiàn):
spring.cloud.sentinel.servlet.block-page={url}
URL資源清洗
Sentinel中HTTP服務(wù)的限流默認(rèn)由Sentinel-Web-Servlet包中的CommonFilter來實現(xiàn),從代碼中可以看到,這個Filter會把每個不同的URL都作為不同的資源來處理。
在下面這段代碼中,提供一個攜帶{id}參數(shù)的REST風(fēng)格的API,對于每一個不同的{id},URL也都不一樣,所以在默認(rèn)情況下Sentinel會把所有的URL當(dāng)作資源來進(jìn)行流控。
@RestController
public class UrlCleanController {
@GetMapping("/clean/{id}")
public String clean(@PathVariable("id") int id) {
return "Hello URL";
}
}
這會導(dǎo)致兩個問題:
- 限流統(tǒng)計不準(zhǔn)確,實際需求是控制clean方法總的QPS,結(jié)果統(tǒng)計的是每個URL的QPS。
- 導(dǎo)致Sentinel中資源數(shù)量過多,默認(rèn)資源數(shù)量的閾值是6000, 對于多出的資源規(guī)則將不會生效。
針對這個問題,可以通過UrlCleaner接口來實現(xiàn)資源清洗,也就是對/clean/{id}這個URL,我們可以統(tǒng)一歸集到/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;
}
}