Spring Cloud Alibaba系列(六)sentinel的實際應用

Java旅途發表於2020-07-06

一、sentinel的持久化配置

上一章中我們通過Dashboard來為Sentinel客戶端設定各種各樣的規則,但是這些規則預設是存放在記憶體中,極不穩定,無法用於生成環境,所以需要將其持久化。

DataSource 擴充套件常見的實現方式有:

  • 拉模式:客戶端主動向某個規則管理中心定期輪詢拉取規則,這個規則中心可以是 RDBMS、檔案,甚至是 VCS 等。這樣做的方式是簡單,缺點是無法及時獲取變更;
  • 推模式:規則中心統一推送,客戶端通過註冊監聽器的方式時刻監聽變化,比如使用 Nacos、Zookeeper 等配置中心。這種方式有更好的實時性和一致性保證。

Sentinel 目前支援以下資料來源擴充套件:

生產環境中一般常用的就是推模式。這裡我們使用Nacos儲存規則。推送模式的正確做法應該是 配置中心控制檯/Sentinel 控制檯 → 配置中心 → Sentinel 資料來源 → Sentinel

1.1 sentinel同步nacos配置

  1. 增加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>
  1. 新增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
  1. 提供介面用於測試限流
@RestController
class test{

    @RequestMapping("/test")
    public String test(){
        return "Java旅途";
    }
}
  1. nacos中增加限流規則的配置

  • resource:資源名,即限流規則的作用物件
  • limitApp:流控針對的呼叫來源,若為 default 則不區分呼叫來源
  • grade:限流閾值型別(QPS 或併發執行緒數);0代表根據併發數量來限流,1代表根據QPS來進行流量控制
  • count:限流閾值
  • strategy:呼叫關係限流策略
  • controlBehavior:流量控制效果(直接拒絕、Warm Up、勻速排隊)
  • clusterMode:是否為叢集模式
  1. 測試,訪問test介面,發現sentinel-dashboard中出現了一條流控規則

1.2 sentinel-dashboard中修改規則同步到nacos

要想實現在sentinel-dashboard中修改規則並同步到nacos,我們就需要修改sentinel服務。首先我們去官網下載Sentinel。

  1. 修改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>
  1. 找到 sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos目錄,將整個目錄拷貝到 sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/
  2. 找到 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;
  1. 找到 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>&nbsp;&nbsp;流控規則 V1</a>-->
<!--</li>-->
  1. 重新編譯打包,執行打包後的sentinel-dashboard.jar。

  2. 測試,我們刪除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 分組
  1. 新增依賴
<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>
  1. 注入對應的 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();
    }
}
  1. 自定義異常處理
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));
    }
}
  1. 配置路由
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
  1. 新增啟動引數
-Dcsp.sentinel.app.type=1 -Dcsp.sentinel.dashboard.server=localhost:8081 -Dproject.name=alibaba-gateway

  1. 訪問介面,檢視效果

三、feign呼叫實現熔斷降級

降級:就是服務崩潰了,所以降級邏輯應該應用在消費者(呼叫者)那裡,加在服務提供者本身是毫無意義的,因為服務已經斷開了。

我們根據實際需求在sentinel-dashboard中配置降級規則,然後編寫程式碼。

  1. 定義介面
@RequestMapping("/test")
public String test(){
    return "Java旅途";
}
  1. 定義遠端服務呼叫介面
@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();
}
  1. 服務降級處理fallback
@Component
class RmoteTestFallback implements RemoteTest{
    @Override
    public String test() {
        return null;
    }
}
  1. 服務降級處理fallbackFactory
@Component
class RmoteTestFallbackFactory implements FallbackFactory<RemoteTest> {
    @Override
    public RemoteTest create(Throwable throwable) {
        return null;
    }
}

相關文章