隨着微服務的流行,服務和服務之間的穩定性變得越來越重要。Sentinel 以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
Sentinel 具有以下特征:
-
豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的范圍)、消息削峰填谷、集群流量控制、實時熔斷下游不可用應用等。
-
完備的實時監控:Sentinel 同時提供實時的監控功能。您可以在控制台中看到接入應用的單台機器秒級數據,甚至 500 台以下規模的集群的匯總運行情況。
-
廣泛的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應的依賴並進行簡單的配置即可快速地接入 Sentinel。
-
完善的 SPI 擴展點:Sentinel 提供簡單易用、完善的 SPI 擴展接口。您可以通過實現擴展接口來快速地定制邏輯。例如定制規則管理、適配動態數據源等。
一、初識Sentinel
首先,我們需要引入Sentinel的依賴。
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.7.2</version> </dependency>
Sentinel 支持以下幾種規則:流量控制規則、熔斷降級規則、系統保護規則、來源訪問控制規則 和 熱點參數規則。
在這里,我們來展示一個流量控制和熔斷降級的示例。
1、流量控制
流量控制,其原理是監控應用流量的 QPS 或並發線程數等指標,當達到指定的閾值時對流量進行控制,以避免被瞬時的流量高峰沖垮,從而保障應用的高可用性。
我們以 QPS 為例,先來定義它的規則,相關屬性含義見注釋。
/** * 加載限流規則 * @param resource */ public static void loadFlowRules(String resource){ FlowRule rule = new FlowRule(); //資源名稱,可以是任意字符串 rule.setResource(resource); //限流閾值 rule.setCount(5); //限流閾值類型,設置為QPS。即每秒QPS大於5時,觸發限流 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //針對的調用來源 rule.setLimitApp("default"); //調用關系限流策略,默認按照資源本身 rule.setStrategy(RuleConstant.STRATEGY_DIRECT); //限流效果,默認直接拒絕 rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); //是否集群限流 rule.setClusterMode(false); FlowRuleManager.loadRules(Collections.singletonList(rule)); }
如上代碼,當每秒的請求數達到 5 之后,就會直接拒絕當前時間窗口的后續請求。
接下來,我們把需要控制流量的代碼用 Sentinel API SphU.entry("resource") 和 entry.exit()
包圍起來即可。
public static void main(String[] args) throws InterruptedException { loadFlowRules("orderService"); while (!stop){ count.incrementAndGet(); Entry entry = null; try { entry = SphU.entry(resource); logger.info("業務操作...{}",count.get()); } catch (BlockException e) { logger.error("請求被限流...{}",count.get()); Thread.sleep(1000); } finally { if (entry != null) { entry.exit(); } if (count.get()>=20){ stop = true; } } } }
如上代碼,我們先通過loadFlowRules()
方法加載限流規則。然后將業務操作用Sentinel API包圍起來。
我們定義的限流閾值是5,這里一共有20個請求。觸發限流之后,我們的線程停頓1秒,以便度過當前的時間窗口,所以會有3個請求被限流。
運行代碼,我們可以得到以下結果:
14:38:00.463 - 業務操作...1 14:38:00.465 - 業務操作...2 14:38:00.465 - 業務操作...3 14:38:00.465 - 業務操作...4 14:38:00.465 - 業務操作...5 14:38:00.494 - 請求被限流...6 14:38:01.494 - 業務操作...7 14:38:01.494 - 業務操作...8 14:38:01.495 - 業務操作...9 14:38:01.495 - 業務操作...10 14:38:01.495 - 業務操作...11 14:38:01.496 - 請求被限流...12 14:38:02.497 - 業務操作...13 14:38:02.497 - 業務操作...14 14:38:02.497 - 業務操作...15 14:38:02.497 - 業務操作...16 14:38:02.497 - 業務操作...17 14:38:02.497 - 請求被限流...18 14:38:03.498 - 業務操作...19 14:38:03.498 - 業務操作...20
2、熔斷
除了流量控制以外,對調用鏈路中不穩定的資源進行熔斷降級也是保障高可用的重要措施之一。
Sentinel 熔斷降級會在調用鏈路中某個資源出現不穩定狀態時(例如調用超時或異常比例升高),對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯錯誤。
那怎么來衡量資源是否穩定呢?
Sentinel提供了三種方式,平均響應時間、異常比例和異常數。
我們拿平均響應時間為例,先來定義它的規則。
/** * 1秒內的5個請求,平均響應時間大於10ms,接下來的3秒內都會自動熔斷。 * @param resourceName */ public static void loadDegradeRule(String resourceName){ List<DegradeRule> rules = new ArrayList<>(); DegradeRule rule = new DegradeRule(); //資源名稱 rule.setResource(resourceName); //閾值 - 10ms rule.setCount(10); //熔斷策略 - RT模式 rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); //時間窗口 - 3s rule.setTimeWindow(3); //RT模式下,1秒內連續多少個請求的平均RT超出閾值,才可以觸發熔斷 rule.setRtSlowRequestAmount(5); rules.add(rule); DegradeRuleManager.loadRules(rules); }
如上代碼,我們定義了熔斷的規則,屬性的含義見注釋內容,然后來看測試用例。
public static void main(String[] args)throws InterruptedException { loadDegradeRule(resource); while (!stop){ count.incrementAndGet(); Entry entry = null; try { entry = SphU.entry(resource); logger.info("業務操作...{}",count.get()); Thread.sleep(15); } catch (BlockException e) { if (e instanceof DegradeException){ logger.error("觸發熔斷機制...{}",count.get()); Thread.sleep(500); } } finally { if (entry != null) { entry.exit(); } if (count.get()>=20){ stop = true; } } } logger.info("----------------------------"); }
在上面的代碼中,我們一共有20個請求。我們讓線程停頓15ms使平均RT超過閾值,也就是超過10ms。
我們定義的規則里面是1秒內連續5個請求的平均RT超出閾值,就可以觸發熔斷
,所以當第6個請求到達時,就會觸發熔斷。
熔斷多久呢?就在3秒的時間窗口。
上面的測試代碼中,在觸發熔斷之后,我們又手動讓線程停頓了 1000ms ,所以每次熔斷的請求會有3個。
是不是這樣,我們運行代碼,看下結果:
10:56:20.022 [main] INFO orderService - 業務操作...1 10:56:20.040 [main] INFO orderService - 業務操作...2 10:56:20.056 [main] INFO orderService - 業務操作...3 10:56:20.072 [main] INFO orderService - 業務操作...4 10:56:20.088 [main] INFO orderService - 業務操作...5 10:56:20.127 [main] ERROR orderService - 觸發熔斷機制...6 10:56:21.128 [main] ERROR orderService - 觸發熔斷機制...7 10:56:22.128 [main] ERROR orderService - 觸發熔斷機制...8 10:56:23.129 [main] INFO orderService - 業務操作...9 10:56:23.145 [main] INFO orderService - 業務操作...10 10:56:23.160 [main] INFO orderService - 業務操作...11 10:56:23.176 [main] INFO orderService - 業務操作...12 10:56:23.192 [main] INFO orderService - 業務操作...13 10:56:23.207 [main] ERROR orderService - 觸發熔斷機制...14 10:56:24.208 [main] ERROR orderService - 觸發熔斷機制...15 10:56:25.208 [main] ERROR orderService - 觸發熔斷機制...16 10:56:26.209 [main] INFO orderService - 業務操作...17 10:56:26.224 [main] INFO orderService - 業務操作...18 10:56:26.240 [main] INFO orderService - 業務操作...19 10:56:26.255 [main] INFO orderService - 業務操作...20 10:56:26.271 [main] INFO orderService - ----------------------------
至此,我們就可以說,Sentinel 能夠正常工作了。
二、系統集成
上面只是一個很簡單的Demo示例,如果我們希望在我們的SpringBoot項目中使用Sentinel,還需要一些工作。
1、Sentinel 控制台
Sentinel 提供一個輕量級的開源控制台,它是使用SpringBoot開發的。
它提供機器發現以及健康情況管理、監控(單機和集群),規則管理和推送的功能。
所以,我們先把這個控制台運行起來。
第一步,需要在https://github.com/alibaba/Sentinel/releases
這個地址,下載最新版本的控制台 jar 包。
第二步,使用命令啟動控制台程序,其中 -Dserver.port=9080
用於指定 Sentinel 控制台端口。
java -Dserver.port=9080 -Dcsp.sentinel.dashboard.server=localhost:9080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
第三步,我們的業務系統引入 Transport 模塊來與 Sentinel 控制台進行通信。
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.7.2</version> </dependency>
第四步,在我們的業務系統中,設置JVM啟動參數,用來指明Sentinel控制台的地址。
-Dcsp.sentinel.dashboard.server=127.0.0.1:9080
最后,啟動我們的業務系統,然后打開Sentinel控制台,如果可以看到機器列表就可以了。

2、定義規則
在定義規則之前,我們需要規划好資源范圍。
什么意思呢?比如我們拿一個訂單業務來說,是不是所有的訂單操作都算一個資源?還是拆分開來看,創建訂單算一個資源,訂單查詢算另外一個資源。
所以,我們可以先把希望流控的資源名稱定義出來。
public final class ResourceConstants { public static final String ORDER_SERVICE = OrderService.class.getName(); public static final String ORDER_SERVICE_ORDERS = ORDER_SERVICE+".orders"; public static final String ORDER_SERVICE_CREATE = ORDER_SERVICE+".create"; }
由於是一個SpringBoot項目,我們可以在系統啟動的時候,來加載流控規則。
@Component public class ApplicationStartup implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { initFlowRule(ResourceConstants.ORDER_SERVICE,5); initFlowRule(ResourceConstants.ORDER_SERVICE_ORDERS,5); } public void initFlowRule(String resourceName,int count) { FlowRule flowRule = new FlowRule(resourceName) .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS); List<FlowRule> list = new ArrayList<>(); list.add(flowRule); FlowRuleManager.loadRules(list); } }
然后,我們在Controller加入Sentinel的代碼,來達到流控的效果。
@RequestMapping("/getOrders") public ResponseEntity getOrders(){ Entry entry = null; try { entry = SphU.entry(ResourceConstants.ORDER_SERVICE_ORDERS); return ResponseEntity.ok(orderService.orders()); } catch (BlockException e) { logger.error("請求被限流...{}",e.getRule().getResource()); return ResponseEntity.badRequest().body(e.getRule()); } finally { if (entry != null) { entry.exit(); } } }
現在,我們拿JMeter來測試一下,啟動10個線程來請求這個接口。只會通過5個請求,拒絕5個請求。

- 在每個需要流控的地方,通過API硬編碼,侵入性太強而且也不方便;
- 流控規則只保留在內存中,系統重啟就沒了,沒有持久化規則數據。
接下來,我們來解決上述的兩個問題。
三、框架適配
得益於廣泛的開源生態,Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊。我們只需要引入相應的依賴並進行簡單的配置即可快速地接入 Sentinel。
我們希望可以對 Web 請求進行流量控制,那么需要引入Sentinel 提供與 Servlet 的整合。
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-web-servlet</artifactId> <version>1.7.2</version> </dependency>
1、Filter配置
因為是SpringBoot應用,我們通過Configuration進行配置。
@Configuration public class SentinelFilterConfig { @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns("/*"); registration.setName("sentinelFilter"); registration.setOrder(1); return registration; } }
在我們自己的業務代碼中,就可以免去Sentinel API部分了。
@RequestMapping("/getOrders") public ResponseEntity getOrders(){ return ResponseEntity.ok(orderService.orders()); }
在流控規則不變的情況下,我們拿JMeter啟動10個線程來請求這個接口。同樣的只會通過5個請求,拒絕5個請求。
2、UrlBlockHandler
默認情況下,當請求被限流時會返回默認的提示頁面。
我們可以在代碼中調用WebServletConfig.setBlockPage(blockPage)
方法設定自定義的跳轉 URL,當請求被限流時會自動跳轉至設定好的 URL。
如果不打算讓它跳轉頁面,我們也可以實現 UrlBlockHandler 接口並編寫定制化的限流處理邏輯。
比如像下面這樣,限流或熔斷之后,會向客戶端返回一個異常的HTTP狀態碼和提示信息。
public class SentinelUrlBlockHandler implements UrlBlockHandler { public static final String flowMsg = "觸發流控機制~"; public static final String degradeMsg = "觸發熔斷機制~"; Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex){ logger.error("熔斷限流...{}",ex.getRule()); response.setCharacterEncoding("UTF-8"); response.setStatus(HttpStatus.BAD_REQUEST.value()); PrintWriter out = response.getWriter(); if (ex instanceof FlowException){ out.print(flowMsg); }else if (ex instanceof DegradeException){ out.print(degradeMsg); } out.flush(); out.close(); } }
然后將其注冊至 WebCallbackManager 中。
WebCallbackManager.setUrlBlockHandler(new SentinelUrlBlockHandler());
3、UrlCleaner
Sentinel Web Filter 會將每個到來的不同的 URL 都作為不同的資源處理。
比如訂單業務中的,創建訂單、訂單查詢、訂單刪除等等,因為URL的不同,都會被當作不同的資源。
如果我們希望將這些操作都歸到訂單資源下/order/*
,就需要實現 UrlCleaner 接口清洗一下資源。
比如像下面這樣,將資源歸類。比如/order/getOrders和/order/createOrder
,都會變成/order/*。
public class SentinelUrlClean implements UrlCleaner { @Override public String clean(String originUrl) { if (originUrl == null || originUrl.isEmpty()) { return originUrl; } int lastSlashIndex = originUrl.lastIndexOf("/"); if (lastSlashIndex >= 0) { originUrl = originUrl.substring(0, lastSlashIndex) + "/*"; } return originUrl; } }
然后將其注冊至 WebCallbackManager 中。
WebCallbackManager.setUrlCleaner(new SentinelUrlClean());
當時,更絕對一些,如果整個系統都采用一個資源,那么這里只返回一個固定的url也可以。
四、最佳實踐
上面我們說到,現在的Sentinel規則數據都只保留在內存中,沒辦法做到集中管理和推送規則,不具備生產環境可用性。
規則管理及推送,一般有三種方式。
- 原始模式
將規則推送至客戶端並直接更新到內存中。重啟即消失,不建議在生產環境中使用。
- Pull 模式
客戶端主動向某個規則管理中心定期輪詢拉取規則,這個規則中心可以是 RDBMS、文件 等。不保證實時性,拉取過於頻繁可能會導致性能問題。
- Push 模式
規則中心統一推送,客戶端通過注冊監聽器的方式時刻監聽變化,比如使用 Nacos、Zookeeper 等配置中心,有更好的實時性和一致性。生產環境下一般采用 push 模式的數據源。
生產環境下一般更常用的是 push 模式的數據源。對於 push 模式的數據源,如遠程配置中心(ZooKeeper, Nacos, Apollo等等),推送的操作不應由 Sentinel 客戶端進行,而應該經控制台統一進行管理,直接進行推送,數據源僅負責獲取配置中心推送的配置並更新到本地。因此推送規則正確做法應該是 ** 配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 數據源 → Sentinel **,而不是經 Sentinel 數據源推送至配置中心。
接下來我們來實現由Nacos配置中心統一管理數據。
1、啟動Nacos
關於Nacos本文不再多說,下載一個啟動就好了。
2、引入依賴
NacosDataSource,官方已經提供了,我們引入相關依賴即可。
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-extension</artifactId> <version>1.7.2</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.7.2</version> </dependency>
3、從數據源中讀取規則數據
在初始化NacosDataSource的時候,我們要指定Nacos的服務地址,groupId和dataId。
然后根據這些信息連接Nacos,去讀取里面的數據。並且注冊監聽器,在Nacos配置中心的規則數據發生變化后,通知到客戶端。
說起來可能比較復雜,但是作為客戶端使用的話,其實比較簡單。我們搞一個類,去連接它就可以了。
@Component public class DataSourceRuleManager { private static final String remoteAddress = "localhost:8848"; private static final String groupId = "sentinel.group"; private static final String flowDataId = "flow.rule"; @PostConstruct public void loadFlowRules() { FlowConverter converter = new FlowConverter(); //連接Nacos,讀取配置信息並通過converter將內容轉換為對象 ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress,groupId,flowDataId,converter); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); } //轉換器 從Nacos配置中心讀取到的數據轉換為對象 public class FlowConverter implements Converter { @Override public Object convert(Object source) { return JSON.parseArray(source.toString(),FlowRule.class); } } }
配置完之后,我們就可以啟動業務系統了。
4、從Nacos配置中心添加規則數據
現在就可以通過Nacos控制台,向配置中心添加規則數據了。
有一點需要注意的是,由於我們的轉換器是通過JSON解析FlowRule類型的數組對象,所以配置內容里面的格式和屬性名稱要對應起來,否則解析會失敗。

還有一種方式是直接通過 Sentinel 控制台 → 配置中心,這樣的話需要修改dashboard的實現,過程雖然不難但比較復雜,由於篇幅有限,本文就不再贅述。感興趣的朋友可以留言交流~
總結
本文簡單介紹了分布式系統熔斷、限流組件Sentinel的使用。為了達到生產環境的基本可用,包含了 Sentinel 與 Servlet 的整合和規則中心統一推送和持久化。