一、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 或併發執行緒數);
0
代表根據併發數量來限流,1
代表根據QPS來進行流量控制 - 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>
將
<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/
。 - 找到
com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2
,將預設動態規則修改為nacos動態規則。
@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;
}
}