一、sentinel的持久化配置
上一章中我們通過Dashboard來為Sentinel用戶端設定各種各樣的規則,但是這些規則預設是存放在記憶體中,極不穩定,無法用于生成環境,是以需要将其持久化。
DataSource
擴充常見的實作方式有:
- 拉模式:用戶端主動向某個規則管理中心定期輪詢拉取規則,這個規則中心可以是 RDBMS、檔案,甚至是 VCS 等。這樣做的方式是簡單,缺點是無法及時擷取變更;
- 推模式:規則中心統一推送,用戶端通過注冊監聽器的方式時刻監聽變化,比如使用 Nacos 、Zookeeper 等配置中心。這種方式有更好的實時性和一緻性保證。
Sentinel 目前支援以下資料源擴充:
生産環境中一般常用的就是
推模式
。這裡我們使用Nacos存儲規則。推送模式的正确做法應該是 配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 資料源 → Sentinel。

1.1 sentinel同步nacos配置
- 增加sentinel的依賴和nacos存儲擴充依賴
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 添加nacos相關配置
spring:
cloud:
sentinel:
datasource:
# 名稱随意
javatrip:
nacos:
server-addr: 127.0.0.1:8848
dataId: ${spring.application.name}-rules
groupId: SENTINEL_GROUP
# 規則類型,取值見:
# org.springframework.cloud.alibaba.sentinel.datasource.RuleType
rule-type: flow
- 提供接口用于測試限流
@RestController
class test{
@RequestMapping("/test")
public String test(){
return "Java旅途";
}
}
- nacos中增加限流規則的配置
- resource:資源名,即限流規則的作用對象
- limitApp:流控針對的調用來源,若為 default 則不區分調用來源
- grade:限流門檻值類型(QPS 或并發線程數); 代表根據并發數量來限流,
代表根據QPS來進行流量控制1
- count:限流門檻值
- strategy:調用關系限流政策
- controlBehavior:流量控制效果(直接拒絕、Warm Up、勻速排隊)
- clusterMode:是否為叢集模式
- 測試,通路test接口,發現sentinel-dashboard中出現了一條流控規則
1.2 sentinel-dashboard中修改規則同步到nacos
要想實作在sentinel-dashboard中修改規則并同步到nacos,我們就需要修改sentinel服務。首先我們去
官網下載下傳Sentinel。
- 修改pom檔案
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<scope>test</scope>
</dependency>
将test注釋掉,因為這個是作用與test目錄下的。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!--<scope>test</scope>-->
</dependency>
- 找到
目錄,将整個目錄拷貝到sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos
。sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/
-
,将預設動态規則修改為nacos動态規則。com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2
@Autowired
@Qualifier("flowRuleDefaultProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleDefaultPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
修改為:
@Autowired
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
-
sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
将以下内容注釋去掉
<!--<li ui-sref-active="active" ng-if="entry.appType==0">-->
<!--<a ui-sref="dashboard.flow({app: entry.app})">-->
<!--<i class="glyphicon glyphicon-filter"></i> 流控規則 V1</a>-->
<!--</li>-->
- 重新編譯打包,運作打包後的sentinel-dashboard.jar。
- 測試,我們删除nacos中的流量規則配置
- 在sentinel-dashboard——>流量規則V1中新增一個規則。
- 重新整理nacos,發現多了一個配置
- 在nacos中修改這個配置,将閥值改為1
- 重新整理sentinel-dashboard,流量閥值修改為1了。
- 重新開機服務,重新開機sentinel-dashboard,發現流控規則依然存在。
注意:以上隻是示範了流控規則的持久化,sentinel還支援其他規則,如果想實作哪種規則都可以采用相同的方式實作!
二、Gateway網關限流
限流:就是請求多了,對請求進行定制的快速響應處理,應用在服務提供者本身。
從 1.6.0 版本開始,Sentinel 提供了 Spring Cloud Gateway 的适配子產品,可以提供兩種資源次元的限流:
- route 次元:即在 Spring 配置檔案中配置的路由條目,資源名為對應的 routeId
- 自定義 API 次元:使用者可以利用 Sentinel 提供的 API 來自定義一些 API 分組
- 添加依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>x.y.z</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
- 注入對應的
執行個體以及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 MySentinelGatewayBlockExceptionHandler(viewResolvers,serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
}
- 自定義異常處理
public class MySentinelGatewayBlockExceptionHandler extends SentinelGatewayBlockExceptionHandler {
private List<ViewResolver> viewResolvers;
private List<HttpMessageWriter<?>> messageWriters;
public MySentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
super(viewResolvers,serverCodecConfigurer);
this.viewResolvers = viewResolvers;
this.messageWriters = serverCodecConfigurer.getWriters();
}
@Override
public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable throwable) {
if(serverWebExchange.getResponse().isCommitted()){
return Mono.error(throwable);
}
if(!BlockException.isBlockException(throwable)){
return Mono.error(throwable);
}
return handleBlockedRequest(serverWebExchange, throwable).flatMap(response -> writeResponse(response, serverWebExchange));
}
private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
}
private final Supplier<ServerResponse.Context> contextSupplier = () -> new ServerResponse.Context() {
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return MySentinelGatewayBlockExceptionHandler.this.messageWriters;
}
@Override
public List<ViewResolver> viewResolvers() {
return MySentinelGatewayBlockExceptionHandler.this.viewResolvers;
}
};
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
ServerHttpResponse resp = exchange.getResponse();
resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
String json = "{\"code\": -1, \"data\": null, \"msg\": \"通路量過大,請稍後再試\"}";
DataBuffer buffer = resp.bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8));
return resp.writeWith(Mono.just(buffer));
}
}
- 配置路由
server:
port: 7003
spring:
application:
name: alibaba-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
enabled: true
discovery:
locator:
enabled: true # 開啟從注冊中心動态建立路由的功能,利用微服務名稱進行路由
routes:
- id: sentinel-nacos # 路由id,建議配合服務名
uri: lb://sentinel-nacos #比對路由名
predicates:
- Path=/sentinel/** # 斷言,路徑相比對的進行路由
filters:
- StripPrefix=1
- 添加啟動參數
-Dcsp.sentinel.app.type=1 -Dcsp.sentinel.dashboard.server=localhost:8081 -Dproject.name=alibaba-gateway
- 通路接口,檢視效果
三、feign調用實作熔斷降級
降級:就是服務崩潰了,是以降級邏輯應該應用在消費者(調用者)那裡,加在服務提供者本身是毫無意義的,因為服務已經斷開了。
我們根據實際需求在sentinel-dashboard中配置降級規則,然後編寫代碼。
- 定義接口
@RequestMapping("/test")
public String test(){
return "Java旅途";
}
- 定義遠端服務調用接口
@FeignClient(name = "nacos-sentinel",fallback = RmoteTestFallback.class)
interface RemoteTest{
@RequestMapping("/test")
public String test();
}
為了簡寫fallback,我們更傾向于用fallbackFactory = RmoteTestFallbackFactory.class
@FeignClient(name = "nacos-sentinel",fallbackFactory = RmoteTestFallbackFactory.class)
interface RemoteTest{
@RequestMapping("/test")
public String test();
}
- 服務降級處理fallback
@Component
class RmoteTestFallback implements RemoteTest{
@Override
public String test() {
return null;
}
}
- 服務降級處理fallbackFactory
@Component
class RmoteTestFallbackFactory implements FallbackFactory<RemoteTest> {
@Override
public RemoteTest create(Throwable throwable) {
return null;
}
}