Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

規則持久化 - 拉模式

在Sentinel控制檯對某個微服務的接口資源配置了流控、降級等規則後,若重啓了該微服務,那麼配置的相關規則就會丟失,由於Sentinel默認將規則存放在內存中。每次重啓微服務都得從新配置規則顯然是不合理的,因此咱們須要將配置好的規則進行持久化存儲,而Sentinel提供了兩種規則持久化模式:html

  • 拉模式(pull)
  • 推模式(push)

本小節先介紹一下拉模式(pull),該模式的架構圖以下:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]java

  • Tips:規則在代碼中其實是一個對象,因此一般是轉換成json字符串後再存儲至本地文件

由於須要讀寫本地文件,因此實現拉模式須要編寫一些代碼,首先在項目中添加以下依賴:node

<!-- Sentinel Datasource -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-extension</artifactId>
</dependency>

因爲Sentinel有好幾種規則,因此須要寫的代碼也有點多,具體代碼以下示例:git

package com.zj.node.contentcenter.sentinel;

import com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler;
import com.alibaba.csp.sentinel.datasource.*;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

/**
 * 規則持久化 - 拉模式
 *
 * @author 01
 * @date 2019-08-02
 **/
@Slf4j
public class FileDataSourceInitial implements InitFunc {
    /**
     * 定義並實現各個規則對象的轉換器,用於將json格式的數據轉換爲相應的Java對象
     */
    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<FlowRule>>() {
            }
    );

    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<DegradeRule>>() {
            }
    );

    private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<SystemRule>>() {
            }
    );

    private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<AuthorityRule>>() {
            }
    );

    private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<ParamFlowRule>>() {
            }
    );

    @Override
    public void init() throws Exception {
        // 規則持久化文件所存放的路徑及文件名,能夠按需求自行修改
        String ruleDir = System.getProperty("user.home") + "/sentinel/rules";
        String flowRulePath = ruleDir + "/flow-rule.json";
        String degradeRulePath = ruleDir + "/degrade-rule.json";
        String systemRulePath = ruleDir + "/system-rule.json";
        String authorityRulePath = ruleDir + "/authority-rule.json";
        String paramFlowRulePath = ruleDir + "/param-flow-rule.json";

        // 目錄路徑及文件若不存在則建立
        this.mkdirIfNotExits(ruleDir);
        this.createFileIfNotExits(flowRulePath);
        this.createFileIfNotExits(degradeRulePath);
        this.createFileIfNotExits(systemRulePath);
        this.createFileIfNotExits(authorityRulePath);
        this.createFileIfNotExits(paramFlowRulePath);

        // 註冊各個規則的可讀寫數據源
        this.registerFlowRWDS(flowRulePath);
        this.registerDegradeRWDS(degradeRulePath);
        this.registerSystemRWDS(systemRulePath);
        this.registerAuthorityRWDS(authorityRulePath);
        this.registerParamRWDS(paramFlowRulePath);
    }

    /**
     * 註冊流控規則的可讀寫數據源
     */
    private void registerFlowRWDS(String flowRulePath) throws FileNotFoundException {
        // 構建可讀數據源,用於定時讀取本地的json文件
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
                flowRulePath,
                flowRuleListParser
        );
        // 將可讀數據源註冊至FlowRuleManager,當文件裏的規則內容發生變化時,就會更新到緩存裏
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());

        // 構建可寫數據源
        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
                flowRulePath,
                this::toJson
        );
        // 將可寫數據源註冊至transport模塊的WritableDataSourceRegistry中
        // 這樣收到控制檯推送的規則時,Sentinel會先更新到內存,而後將規則寫入到文件中
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
    }

    /**
     * 註冊降級規則的可讀寫數據源
     */
    private void registerDegradeRWDS(String degradeRulePath) throws FileNotFoundException {
        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
                degradeRulePath,
                degradeRuleListParser
        );
        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
                degradeRulePath,
                this::toJson
        );
        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
    }

    /**
     * 註冊系統規則的可讀寫數據源
     */
    private void registerSystemRWDS(String systemRulePath) throws FileNotFoundException {
        ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
                systemRulePath,
                systemRuleListParser
        );
        SystemRuleManager.register2Property(systemRuleRDS.getProperty());
        WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
                systemRulePath,
                this::toJson
        );
        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
    }

    /**
     * 註冊受權規則的可讀寫數據源
     */
    private void registerAuthorityRWDS(String authorityRulePath) throws FileNotFoundException {
        ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
                authorityRulePath,
                authorityRuleListParser
        );
        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
        WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
                authorityRulePath,
                this::toJson
        );
        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
    }

    /**
     * 註冊熱點參數規則的可讀寫數據源
     */
    private void registerParamRWDS(String paramFlowRulePath) throws FileNotFoundException {
        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
                paramFlowRulePath,
                paramFlowRuleListParser
        );
        ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
                paramFlowRulePath,
                this::toJson
        );
        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
    }

    private void mkdirIfNotExits(String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            boolean result = file.mkdirs();
            log.info("建立目錄: {} filePath: {}", result ? "成功" : "失敗", filePath);
        }
    }

    private void createFileIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            boolean result = file.createNewFile();
            log.info("建立文件: {} filePath: {}", result ? "成功" : "失敗", filePath);
        }
    }

    private <T> String toJson(T t) {
        return JSON.toJSONString(t);
    }
}

這裏有兩個重要的API:github

  • FileRefreshableDataSource 定時從指定文件中讀取規則JSON文件【上圖中的本地文件】,若是發現文件發生變化,就更新規則緩存
  • FileWritableDataSource 接收控制檯規則推送,並根據配置,修改規則JSON文件【上圖中的本地文件】

編寫完以上代碼後,還須要在項目的 resources/META-INF/services 目錄下建立一個文件,名爲 com.alibaba.csp.sentinel.init.InitFunc ,以下圖所示:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]web

而後編輯文件內容以下:spring

# 修改成上面FileDataSourceInitial的包名類名全路徑
com.zj.node.contentcenter.sentinel.FileDataSourceInitial

完成以上步驟後,重啓項目,此時就能夠自行測試一下規則是否能持久化存儲了。json

拉模式的優缺點:瀏覽器

  • 優勢:
    • 簡單易懂,沒有多餘依賴(如配置中心、緩存等依賴)
  • 缺點:
    • 因爲規則是用 FileRefreshableDataSource 定時更新的,因此規則更新會有延遲。若是FileRefreshableDataSource定時時間過大,可能長時間延遲;若是FileRefreshableDataSource太小,又會影響性能
    • 規則存儲在本地文件,若是有一天須要遷移微服務,那麼須要把規則文件一塊兒遷移,不然規則會丟失

若是有了解過規則持久化相關配置的小夥伴可能會有疑問,Spring Cloud Alibaba不是提供了以下配置了嗎?爲何要所有本身寫呢?緩存

spring.cloud.sentinel.datasource.ds1.file.file=classpath: degraderule.json
spring.cloud.sentinel.datasource.ds1.file.rule-type=flow

#spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json
#spring.cloud.sentinel.datasource.ds1.file.data-type=custom
#spring.cloud.sentinel.datasource.ds1.file.converter-class=com.alibaba.cloud.examples.JsonFlowRuleListConverter
#spring.cloud.sentinel.datasource.ds1.file.rule-type=flow

關於這個問題,能夠查看一下這個Issues

官方文檔:


規則持久化 - 推模式

在上一小節中,咱們瞭解了規則持久化中拉模式的原理及使用方式,本小節將介紹在生產環境中更爲經常使用的推模式(push)。

推模式的架構圖以下:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

  • Sentinel控制檯再也不是調用客戶端的API推送規則數據,而是將規則推送到Nacos或其餘遠程配置中心
  • Sentinel客戶端經過鏈接Nacos,來獲取規則配置;並監聽Nacos配置變化,如發生變化,就更新本地緩存(從而讓本地緩存老是和Nacos一致)
  • Sentinel控制檯也監聽Nacos配置變化,如發生變化就更新本地緩存(從而讓Sentinel控制檯的本地緩存老是和Nacos一致)

改造Sentinel控制檯

使用推模式進行規則的持久化仍是稍微有些麻煩的,由於須要改動Sentinel控制檯的源碼,對控制檯的改造主要是爲了實現:

  • DynamicRuleProvider:從Nacos上讀取配置
  • DynamicRulePublisher:將規則推送到Nacos上

這裏僅演示對流控規則的改造讓其支持推模式的規則持久化,由於其餘規則的改造過程也是相似的,稍微琢磨一下就能夠了。首先須要下載Sentinel的源碼包,我這裏使用的是1.6.3版本:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

下載並解壓完成後,使用IDE打開sentinel-dashboard這個項目,以下:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

第一步:修改該項目的pom.xml文件,找到以下依賴項:

<!-- for Nacos rule publisher sample -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <scope>test</scope>
</dependency>

&lt;scope&gt;test&lt;/scope&gt;一行註釋掉,即修改成以下:

<!-- for Nacos rule publisher sample -->
<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下,以下圖所示:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

拷貝完成後rule包結構以下圖:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

第三步:修改流控規則Controller,到com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2類的源碼中找到這一段:

@Autowired
@Qualifier("flowRuleDefaultProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleDefaultPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

修改@Qualifier註解的內容,將其修改成:

@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>&nbsp;&nbsp;流控規則 V1</a>-->
  <!--</li>-->

只須要把註釋解開,即改成:

<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>

到這步爲止,咱們就對sentinel-dashboard源碼改造完畢了,如今流控規則就能夠支持推模式的持久化了,接下來就是編譯、啓動以及測試。

打開idea的terminal,執行以下命令進行打包編譯:

mvn clean package -DskipTests

而後進入target目錄,使用以下命令執行jar包,啓動Sentinel控制檯:

java -jar sentinel-dashboard.jar

注:也能夠選擇直接在idea中點擊啓動按鈕來啓動Sentinel控制檯,效果是同樣的

啓動完成後,使用瀏覽器打開,能夠看到Sentinel的菜單欄中比以前多出了一項流控規則 V1
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

注:若沒有顯示該項,能夠嘗試清除瀏覽器緩存或換個瀏覽器打開


改造Sentinel客戶端( 微服務)

改造完控制檯後,接下來開始改造客戶端,首先在項目中添加以下依賴:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

而後添加各個規則配置:

spring:
  cloud:
    sentinel:
      datasource:
        # 名稱隨意
        flow:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-flow-rules
            groupId: SENTINEL_GROUP
            # 規則類型,取值見:
            # org.springframework.cloud.alibaba.sentinel.datasource.RuleType
            rule-type: flow
        degrade:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            rule-type: degrade
        system:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-system-rules
            groupId: SENTINEL_GROUP
            rule-type: system
        authority:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-authority-rules
            groupId: SENTINEL_GROUP
            rule-type: authority
        param-flow:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-param-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: param-flow

完成以上步驟後重啓項目,而後回到Sentinel控制檯裏的流控規則 V1中新增流控規則,之因此不在簇點鏈路中添加,是由於簇點鏈路中的按鈕依舊是調用以前的邏輯添加到內存中。新增流控規則以下:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

新增完成後,到Nacos Server的配置列表上,能夠看到該規則的配置數據,證實已經持久化存儲到Nacos了:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

若直接在Nacos上修改流控規則,而後刷新Sentinel控制檯,控制檯上的顯示也會被修改

此時重啓Sentinel控制檯和微服務,而後刷新控制檯,能夠發現該流控規則依舊存在:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

以上折騰了那麼多隻實現了流控規則的持久化,這仍是由於官方準備好了示例代碼。Sentinel有若干種規則,例如降級規則、系統規則、受權規則、熱點規則等,都須要使用相似的方式,修改 com.alibaba.csp.sentinel.dashboard.controller 包中對應的Controller,才能實現持久化,因此本小節僅是拋磚引玉,其餘規則能夠自行動手參考着實現。

推模式優缺點:

  • 優勢:
    • 一致性良好,性能優秀,貼近生產使用
  • 缺點:
    • 須要對控制檯的源碼進行改動比較麻煩,而且全部規則加起來的改動工做量較大
    • 引入額外的依賴(Nacos)

官方文檔:


將應用接入阿里雲 AHAS 服務

在生產環境使用Sentinel是必需要實現規則持久化的,而經過以上兩個小節的學習,咱們能夠得知無論使用哪一個模式都須要進行相應的改動,其中想要實現貼近生產使用的推模式須要改動的地方更多更麻煩。

若是不想作這些麻煩的改動,又但願在生產環境使用Sentinel的話,則須要考慮使用阿里雲提供的在線託管Sentinel控制檯(AHAS),該在線Sentinel控制檯實現了推模式的規則持久化並可用於生產:

接下來演示一下如何使用這個在線的Sentinel控制檯,根據開通說明文檔註冊了阿里雲帳戶後,進入開通頁面:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

根據提示開通完成後,進入管理控制檯,點擊接入應用流控:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

進入應用接入界面後能夠選擇不一樣的應用接入,這裏按照Spring Boot應用接入的說明完成相應步驟:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

須要說明一下的是,第二步中的HTTP接口埋點和普通接口埋點均可以省略掉,由於spring-cloud-starter-alibaba-sentinel依賴包裏已經實現了,但須要排除該依賴中的sentinel-transport-simple-http模塊,避免鏈接了本地的Sentinel控制檯,即修改依賴以下並添加AHAS Client依賴:

<!-- Sentinel -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <!-- 排除該模塊的目的是不與本地的Sentinel控制檯進行通訊,以避免形成沒必要要的干擾 -->
    <exclusions>
        <exclusion>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- ahas client -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>spring-boot-starter-ahas-sentinel-client</artifactId>
    <version>1.3.3</version>
</dependency>

除了修改依賴之外,還須要將配置文件中關於Sentinel控制檯的配置都註釋掉,由於此時咱們鏈接的是在線的Sentinel控制檯。以下:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

而後只須要添加AHAS的啓動參數:

ahas:
  license: xxxxxxxxx
  namespace: default
project:
  name: ${spring.application.name}

完成以上步驟後,重啓項目並訪問該項目的接口,應用正常接入的狀況下能夠在阿里雲的控制檯中看到該應用的展現,以下:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

點擊該應用就能夠進入到Sentinel控制檯,能夠看到這裏基本上和咱們本身搭建的Sentinel控制檯是差很少的,一樣支持實時監控、查看簇點鏈路及配置各類規則:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

例如流控規則的添加也是同樣的,只是界面稍微好看一點而已:
Spring Cloud Alibaba之服務容錯組件 - Sentinel [規則持久化篇]

至此就完成將應用接入AHAS了,因爲操做與Sentinel控制檯基本同樣這裏就不展開介紹了,能夠自行測試搗鼓一下,反正可視化的頁面使用起來仍是比較容易的。

相關文章
相關標籤/搜索