Sentinel-集羣限流

準備工做

基於sentine-1.4.2,在dashboard想要更好的查看集羣限流相關配置,須要一些小修改html

你也能夠直接從github上拉取個人代碼: git@github.com:spilledyear/Sentinel.git,對應的分支是 1.4.2java

  • 開啓集羣規則界面 修改:resources/app/views/flow_v1.html,將其中和集羣相關的按鈕打開,最終效果以下: git

  • 規則持久化 dashboard默認沒有對規則持久化,但在集羣規則界面添加的規則,實際上是能夠持久化到nacos的,只須要作一些簡單的修改。將dashboard模塊test目錄下的com.alibaba.csp.sentinel.dashboard.rule.nacos類拷貝到java目錄,以下: github

    而後修改com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2文件,將其中的ruleProvider和rulePublisher改爲剛剛新增的那兩個

  • 啓動nacos nacos的部署就不過多介紹,能夠看官方文檔 nacos手冊api

下面將從如下幾個方面簡單介紹集羣限流bash

啓動測試案例

以嵌入式模式爲例,在源碼的sentinel-demo模塊種,已經準備好了相關測試案例,啓動兩個實例:ClusterDemoApplication,啓動參數分別以下:app

  • 實例一
-Dproject.name=clusterapp -Dserver.port=8081 -Dcsp.sentinel.dashboard.server=localhost:8080
複製代碼
  • 實例二
-Dproject.name=clusterapp -Dserver.port=8082 -Dcsp.sentinel.dashboard.server=localhost:8080
複製代碼

爲了可以方便的修改規則信息,直觀的觀察效果,須要啓動控制檯curl

  • 啓動控制檯
-Dserver.port=8080
複製代碼

此時經過localhost:8080訪問控制檯,還沒法看到任何應用信息,由於此時尚未任何的服務調用,經過如下快捷方式訪問兩個服務實例socket

curl localhost:8081/hello/luo

curl localhost:8082/hello/luo
複製代碼

這時候查看機器列表菜單選項,發現已經有兩個實例了(端口區分): ide

但這時候尚未server和client的概念,須要簡單配置:點擊集羣限流菜單項,而後點擊右上角的"新增Toeken Server"

從中選取一臺server,另外一臺指定爲client,即:

此時,再查看集羣流控菜單項,發現已經有了server信息,經過鏈接詳情發現已有兩個鏈接,這是這是嵌入式,server端自己也是一個應用實例

規則的推送

新建規則

以上準備工做完成以後,下面能夠新建資源了。爲了觀察限流效果光差,新建的資源名與測試案例中的資源名一致:點擊流控規則菜單項,而後點擊右上角的回到集羣界面

爲何這裏要在集羣界面新建規則呢?上面已經說過了,針對集羣規則界面已經作了修改,規則能夠持久化到nacos配置中心

而後新建一個規則,有關於規則的使用這裏就不展開了

以上操做完成以後,會發現nacos中多了一條配置,具體內容就是規則的具體信息

查看限流效果

經過jmeter測試,讓兩個請求都分別請求不一樣的實例各20次:

發現每一個請求都經過了10次,加起來恰好20次,多出來的請求拋出了FlowException異常,執行了blockHandler對應的邏輯,初步符合集羣限流的效果
複製代碼

推送原理

在保存規則信息的時候,發現請求瞭如下接口:http://localhost:8080/v2/flow/rule/29 對應FlowControllerV2中的apiUpdateFlowRule,主要邏輯以下:

  • 將規則信息更新到dashboard的內存中,用於界面展現,這一部分主要和InMemoryRuleRepositoryAdapter的save方法相關;
  • 推送規則信息到nacos註冊中心,這一部分和FlowRuleNacosPublisher相關;

若是dashboard使用了nacos持久化規則,對應的,在嵌入式模式下應該也會在server和client端使用NacosDatasource做爲數據源,對應的源碼在sentinel-datasource-nacos模塊的NacosDataSource類中:

public NacosDataSource(final Properties properties, final String groupId, final String dataId,Converter<String, T> parser) {
    super(parser);
    this.configListener = new Listener() {
        @Override
        public Executor getExecutor() {
            return pool;
        }
        @Override
        public void receiveConfigInfo(final String configInfo) {
            RecordLog.info(String.format("[NacosDataSource] New property value received for (properties: %s) (dataId: %s, groupId: %s): %s",
                properties, dataId, groupId, configInfo));
            T newValue = NacosDataSource.this.parser.convert(configInfo);
            // Update the new value to the property.
            getProperty().updateValue(newValue);
        }
    };
    initNacosListener();
    loadInitialConfig();
}
複製代碼

從上能夠看出,當規則信息更新了的時候,會同步到sentinel的內存結構中。

這裏有一個小問題,若是沒有使用註冊中心,規則將怎麼進行推送? 答案其實在FlowRuleApiPublisher中,若是沒有使用註冊中心,將經過SentinelApiClient發送http請求,將規則推送到各個服務實例,服務實例收到規則信息以後再加載到sentinel相關的內存結構,核心代碼以下:

for (MachineInfo machine : set) {
    if (!MachineUtils.isMachineHealth(machine)) {
        continue;
    }
    // TODO: parse the results
    sentinelApiClient.setFlowRuleOfMachine(app, machine.getIp(), machine.getPort(), rules);
}
複製代碼

若是針對這個問題再次延申,還會有一些疑問,SentinelApiClient怎麼就知道要將規則信息發送到哪裏呢?哪一個端口?這一部分確定是sentine爲咱們隱藏起來了。

  • 第一個問題,哪一個端口?這個端口實際上是commandPort,即應用端暴露給 Sentinel 控制檯的端口,ip@commandPort,其實就是界面上看到的那兩個,分別爲8720和8721;

  • 第二個問題,隱藏了哪些細節?其實就是隱藏了暴露端口的這部分細節,都在sentinel-transport模塊中,提供了兩種實現方式。

  • 方式一,sentinel-transport-simple-http模塊中,經過ServerSocket方式暴露,對應的核心類爲SimpleHttpCommandCenter,核心代碼以下

@Override
public void run() {
    boolean success = false;
    ServerSocket serverSocket = getServerSocketFromBasePort(port);

    if (serverSocket != null) {
        CommandCenterLog.info("[CommandCenter] Begin listening at port " + serverSocket.getLocalPort());
        socketReference = serverSocket;
        executor.submit(new ServerThread(serverSocket));
        success = true;
        port = serverSocket.getLocalPort();
    } else {
        CommandCenterLog.info("[CommandCenter] chooses port fail, http command center will not work");
    }

    if (!success) {
        port = PORT_UNINITIALIZED;
    }

    TransportConfig.setRuntimePort(port);
    executor.shutdown();
}
複製代碼
  • 方式二,在sentinel-transport-netty-http模塊中,經過netty暴露,核心類是NettyHttpCommandCenter,核心代碼以下:
@Override
public void start() throws Exception {
    pool.submit(new Runnable() {
        @Override
        public void run() {
            try {
                server.start();
            } catch (Exception ex) {
                RecordLog.info("Start netty server error", ex);
                ex.printStackTrace();
                System.exit(-1);
            }
        }
    });
}
複製代碼

內部經過SPI機制加載,引用了哪一個模塊就會使用哪一種機制。

節點發現

dashboard是如何獲取節點信息並將其展現在界面上的?核心原理仍是在sentinel-transport模塊中,不論是在sentinel-transport-simple-http仍是sentinel-transport-netty-http中,都會向dashboard發送心跳上報當前節點信息,請求地址即:

dashboardIp:port/registry/machine,這裏表明 localhost:8080/registry/machine
複製代碼

dashboar收到請求後會將節點信息保存到內存中。

有關於這一部分,sentinel-transport-simple-http模塊中的核心類是SimpleHttpHeartbeatSender;sentinel-transport-netty-http模塊中的核心類是HttpHeartbeatSender;

dashboard相關的邏輯以下

public Result<?> receiveHeartBeat(String app, Long version, String v, String hostname, String ip, Integer port) {
    if (app == null) {
        app = MachineDiscovery.UNKNOWN_APP_NAME;
    }
    if (ip == null) {
        return Result.ofFail(-1, "ip can't be null");
    }
    if (port == null) {
        return Result.ofFail(-1, "port can't be null");
    }
    if (port == -1) {
        logger.info("Receive heartbeat from " + ip + " but port not set yet");
        return Result.ofFail(-1, "your port not set yet");
    }
    String sentinelVersion = StringUtil.isEmpty(v) ? "unknown" : v;
    long timestamp = version == null ? System.currentTimeMillis() : version;
    try {
        MachineInfo machineInfo = new MachineInfo();
        machineInfo.setApp(app);
        machineInfo.setHostname(hostname);
        machineInfo.setIp(ip);
        machineInfo.setPort(port);
        machineInfo.setTimestamp(new Date(timestamp));
        machineInfo.setVersion(sentinelVersion);
        appManagement.addMachine(machineInfo);
        return Result.ofSuccessMsg("success");
    } catch (Exception e) {
        logger.error("Receive heartbeat error", e);
        return Result.ofFail(-1, e.getMessage());
    }
}
複製代碼

因此,整個過程看起來是這樣子的:

配置項

  • NameSpace NameSpace主要是用於區分不一樣的應用,其實在嵌入式的模式下做用不大,嵌入式模式下通常是一種對等結構,這時候NameSpace通常就是一個,即:應用名。只有在獨立模式下才能體現它的做用:區分不一樣的應用。
// 若是不配置默認default
ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton("cluster-" + appId));
複製代碼
  • Supplier 主要做用就是就是根據NameSpace找到一個DynamicSentinelProperty,其實在嵌入式模式下,通常也就是寫死一個DynamicSentinelProperty,由於這時候的NameSpace也就只有一個
// 集羣限流規則配置,根據namespace動態生成Supplier,其實子
ClusterFlowRuleManager.setPropertySupplier(dataSource.getClusterFlowSupplier());
複製代碼
  • ServerTransportProperty 做用比較大,針對server端,會根據ServerTransportProperty中的信息在server端經過netty開啓一個端口,用於和client交互
// 配置ServerTransportConfig:port、idleSeconds
ClusterServerConfigManager.registerServerTransportProperty(dataSource.getServerTransportConfigProperty());
複製代碼
  • ClientConfigProperty client端的相關配置,其實只有一個屬性:請求server端的超時時間(requestTimeout)
// 爲client設置requestTimeout
ClusterClientConfigManager.registerClientConfigProperty(dataSource.getClusterClientConfigProperty());
複製代碼
  • ServerAssignProperty client端的相關配置,裏面保存的是server端的相關信息:server的host和port
// 爲client設置server的host和port,即serverHost、serverPort
ClusterClientConfigManager.registerServerAssignProperty(dataSource.getClusterClientAssignConfigProperty());
複製代碼
  • ClusterStateManager 在嵌入式模式下,能夠經過API來改變client和server的身份,大體邏輯就是:將server中的那個netty服務stop,而後根據新的配置在client開啓一個新的netty服務(注意,服務開啓成功以後,client就轉變成server了)
// 用於設置mode,設置0 表明client, 設置1表明 server
ClusterStateManager.registerProperty(dataSource.getClusterStateProperty());
複製代碼
相關文章
相關標籤/搜索