主頁 > 軟體設計 > Sentinel實作動態配置的集群流控

Sentinel實作動態配置的集群流控

2021-04-10 10:58:46 軟體設計

介紹

為什么要使用集群流控呢?

相對于單機流控而言,我們給每臺機器設定單機限流閾值,在理想情況下整個集群的限流閾值為機器數量??單機閾值,不過實際情況下流量到每臺機器可能會不均勻,會導致總量沒有到的情況下某些機器就開始限流,因此僅靠單機維度去限制的話會無法精確地限制總體流量,而集群流控可以精確地控制整個集群的呼叫總量,結合單機限流兜底,可以更好地發揮流量控制的效果,

基于單機流量不均的問題以及如何設定集群整體的QPS的問題,我們需要創建一種集群限流的模式,這時候我們很自然地就想到,可以找一個 server 來專門統計總的呼叫量,其它的實體都與這臺 server 通信來判斷是否可以呼叫,這就是最基礎的集群流控的方式,

原理

集群限流的原理很簡單,和單機限流一樣,都需要對 qps 等資料進行統計,區別就在于單機版是在每個實體中進行統計,而集群版是有一個專門的實體進行統計,

這個專門的用來統計資料的稱為 Sentinel 的 token server,其他的實體作為 Sentinel 的 token client 會向 token server 去請求 token,如果能獲取到 token,則說明當前的 qps 還未達到總的閾值,否則就說明已經達到集群的總閾值,當前實體需要被 block,如下圖所示:

在這里插入圖片描述

和單機流控相比,集群流控中共有兩種身份:

  • Token Client:集群流控客戶端,用于向所屬 Token Server 通信請求 token,集群限流服務端會回傳給客戶端結果,決定是否限流,
  • Token Server:即集群流控服務端,處理來自 Token Client 的請求,根據配置的集群規則判斷是否應該發放 token(是否允許通過),

而單機流控中只有一種身份,每個 sentinel 都是一個 token server,

注意,集群限流中的 token server 是單點的,一旦 token server 掛掉,那么集群限流就會退化成單機限流的模式,

Sentinel 集群流控支持限流規則和熱點規則兩種規則,并支持兩種形式的閾值計算方式:

  • 集群總體模式:即限制整個集群內的某個資源的總體 qps 不超過此閾值,
  • 單機均攤模式:單機均攤模式下配置的閾值等同于單機能夠承受的限額,token server 會根據連接數來計算總的閾值(比如獨立模式下有 3 個 client 連接到了 token server,然后配的單機均攤閾值為 10,則計算出的集群總量就為 30),按照計算出的總的閾值來進行限制,這種方式根據當前的連接數實時計算總的閾值,對于機器經常進行變更的環境非常適合,

部署方式

token server 有兩種部署方式:

  • 一種是獨立部署,就是單獨啟動一個 token server 服務來處理 token client 的請求,如下圖所示:

在這里插入圖片描述

如果獨立部署的 token server 服務掛掉的話,那其他的 token client 就會退化成本地流控的模式,也就是單機版的流控,所以這種方式的集群限流需要保證 token server 的高可用性,

  • 一種是嵌入部署,即作為內置的 token server 與服務在同一行程中啟動,在此模式下,集群中各個實體都是對等的,token server 和 client 可以隨時進行轉變,如下圖所示:

在這里插入圖片描述

嵌入式部署的模式中,如果 token server 服務掛掉的話,我們可以將另外一個 token client 升級為token server來,當然啦如果我們不想使用當前的 token server 的話,也可以選擇另外一個 token client 來承擔這個責任,并且將當前 token server 切換為 token client,Sentinel 為我們提供了一個 api 來進行 token server 與 token client 的切換:

http://<ip>:<port>/setClusterMode?mode=<xxx>

其中 mode 為 0 代表 client,1 代表 server,-1 代表關閉,

PS:注意應用端需要引入集群限流客戶端或服務端的相應依賴,

集群限流控制臺

sentinel為用戶提供集群限流控制臺功能,能夠通過控制臺配置集群的限流規則以及配置集群的Server與Client,

集群限流客戶端

要想使用集群限流功能,必須引入集群限流 client 相關依賴:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-cluster-client-default</artifactId>
    <version>1.8.0</version>
</dependency>

集群限流服務端

要想使用集群限流服務端,必須引入集群限流 server 相關依賴:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-cluster-server-default</artifactId>
    <version>1.8.0</version>
</dependency>

我們結合server和client實作一個嵌入式模式,在pom中同時引入上面的兩個依賴,并配置sentinel控制臺地址,實作一個查詢訂單的介面,

pom

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-cluster-server-default</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-cluster-client-default</artifactId>
</dependency>

application.yml

server:
  port: 9091

spring:
  application:
    name: cloudalibaba-sentinel-clusterServer
  cloud:
    sentinel:
      transport:
        #配置sentinel dashboard地址
        dashboard: localhost:8080
        port: 8719 #默認8719埠

OrderController

@RestController
public class OrderController {
    /**
     * 查詢訂單
     * @return
     */
    @GetMapping("/order/{id}")
    public CommonResult<Order> getOrder(@PathVariable("id") Long id){

        Order order = new Order(id, "212121");
        return CommonResult.success(order.toString());
    }
}

代碼示例如cloudalibaba-sentinel-cluster-embedded9091

修改VM options配置,啟動三個不同埠的實體,即可,

-Dserver.port=9091 -Dproject.name=cloudalibaba-sentinel-clusterServer -Dcsp.sentinel.log.use.pid=true 
-Dserver.port=9092 -Dproject.name=cloudalibaba-sentinel-clusterServer -Dcsp.sentinel.log.use.pid=true 
-Dserver.port=9093 -Dproject.name=cloudalibaba-sentinel-clusterServer -Dcsp.sentinel.log.use.pid=true 

控制臺配置

登錄sentinel的控制臺,并有訪問量后,我們就可以在 Sentinel上面看到集群流控,如下圖所示:

點擊添加Token Server,

在這里插入圖片描述

從實體串列中選擇一個作為Server端,其他作為Client端,并選中到右側Client串列,配置token sever端的最大允許的QPS,用于對 Token Server 的資源使用進行限制,防止在嵌入模式下影回應用本身,

在這里插入圖片描述

配置完成之后的Token Server串列,如下圖所示

在這里插入圖片描述

使用控制臺配置token Server、token Client以及限流規則,有很多的缺點:

1、限流規則,不能持久化,應用重啟之后,規則丟失,

2、token Server 、token Client配置也會丟失,

官方推薦給集群限流服務端注冊動態配置源來動態地進行配置,我們使用nacos作為配置中心,動態配置客戶端與服務端屬性以及限流規則,實作動態集群限流,

sentinel結合nacos實作集群限流

我們使用Nacos對cloudalibaba-sentinel-cluster-embedded9091進行改造,實作動態配置源來動態進行配置,

配置源注冊的相關邏輯可以置于 InitFunc 實作類中,并通過 SPI 注冊,在 Sentinel 初始化時即可自動進行配置源加載監聽,

嵌入模式部署

添加ClusterInitFunc類

public class ClusterInitFunc implements InitFunc {

    //應用名稱
    private static final String APP_NAME = AppNameUtil.getAppName();

    //nacos集群地址
    private final String remoteAddress = "localhost:8848";

    //nacos配置的分組名稱
    private final String groupId = "SENTINEL_GROUP";

    //配置的dataId
    private final String flowDataId = APP_NAME + Constants.FLOW_POSTFIX;
    private final String paramDataId = APP_NAME + Constants.PARAM_FLOW_POSTFIX;
    private final String configDataId = APP_NAME + Constants.CLIENT_CONFIG_POSTFIX;
    private final String clusterMapDataId = APP_NAME + Constants.CLUSTER_MAP_POSTFIX;

    private static final String SEPARATOR = "@";


    @Override
    public void init() {
        // Register client dynamic rule data source.
        //動態資料源的方式配置sentinel的流量控制和熱點引數限流的規則,
        initDynamicRuleProperty();

        // Register token client related data source.
        // Token client common config
        // 集群限流客戶端的配置屬性
        initClientConfigProperty();
        // Token client assign config (e.g. target token server) retrieved from assign map:
        //初始化Token客戶端
        initClientServerAssignProperty();

        // Register token server related data source.
        // Register dynamic rule data source supplier for token server:
        //集群的流控規則,比如限制整個集群的流控閥值,啟動的時候需要添加-Dproject.name=專案名
        registerClusterRuleSupplier();
        // Token server transport config extracted from assign map:
        //初始化server的埠配置
        initServerTransportConfigProperty();

        // Init cluster state property for extracting mode from cluster map data source.
        //初始化集群中服務是客戶端還是服務端
        initStateProperty();
    }

    private void initDynamicRuleProperty() {

        //流量控制的DataId分別是APP_NAME + Constants.FLOW_POSTFIX;熱點引數限流規則的DataId是APP_NAME + Constants.PARAM_FLOW_POSTFIX;

        ReadableDataSource<String, List<FlowRule>> ruleSource = new NacosDataSource<>(remoteAddress, groupId,
            flowDataId, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        FlowRuleManager.register2Property(ruleSource.getProperty());

        ReadableDataSource<String, List<ParamFlowRule>> paramRuleSource = new NacosDataSource<>(remoteAddress, groupId,
            paramDataId, source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {}));
        ParamFlowRuleManager.register2Property(paramRuleSource.getProperty());
    }

    private void initClientConfigProperty() {
        ReadableDataSource<String, ClusterClientConfig> clientConfigDs = new NacosDataSource<>(remoteAddress, groupId,
            configDataId, source -> JSON.parseObject(source, new TypeReference<ClusterClientConfig>() {}));
        ClusterClientConfigManager.registerClientConfigProperty(clientConfigDs.getProperty());
    }

    private void initServerTransportConfigProperty() {
        ReadableDataSource<String, ServerTransportConfig> serverTransportDs = new NacosDataSource<>(remoteAddress, groupId,
            clusterMapDataId, source -> {
            List<ClusterGroupEntity> groupList = new Gson().fromJson(source, new TypeToken<List<ClusterGroupEntity>>(){}.getType());
            return Optional.ofNullable(groupList)
                .flatMap(this::extractServerTransportConfig)
                .orElse(null);
        });
        ClusterServerConfigManager.registerServerTransportProperty(serverTransportDs.getProperty());
    }

    private void registerClusterRuleSupplier() {
        // Register cluster flow rule property supplier which creates data source by namespace.
        // Flow rule dataId format: ${namespace}-flow-rules
        ClusterFlowRuleManager.setPropertySupplier(namespace -> {
            ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(remoteAddress, groupId,
                namespace + Constants.FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
            return ds.getProperty();
        });
        // Register cluster parameter flow rule property supplier which creates data source by namespace.
        ClusterParamFlowRuleManager.setPropertySupplier(namespace -> {
            ReadableDataSource<String, List<ParamFlowRule>> ds = new NacosDataSource<>(remoteAddress, groupId,
                namespace + Constants.PARAM_FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {}));
            return ds.getProperty();
        });
    }

    private void initClientServerAssignProperty() {
        // Cluster map format:
        // [{"clientSet":["112.12.88.66@8729","112.12.88.67@8727"],"ip":"112.12.88.68","serverId":"112.12.88.68@8728","port":11111}]
        // serverId: <ip@commandPort>, commandPort for port exposed to Sentinel dashboard (transport module)
        ReadableDataSource<String, ClusterClientAssignConfig> clientAssignDs = new NacosDataSource<>(remoteAddress, groupId,
            clusterMapDataId, source -> {
            List<ClusterGroupEntity> groupList = new Gson().fromJson(source, new TypeToken<List<ClusterGroupEntity>>(){}.getType());
            return Optional.ofNullable(groupList)
                .flatMap(this::extractClientAssignment)
                .orElse(null);
        });
        ClusterClientConfigManager.registerServerAssignProperty(clientAssignDs.getProperty());
    }

    private void initStateProperty() {
        // Cluster map format:
        // [{"clientSet":["112.12.88.66@8729","112.12.88.67@8727"],"ip":"112.12.88.68","serverId":"112.12.88.68@8728","port":11111}]
        // serverId: <ip@commandPort>, commandPort for port exposed to Sentinel dashboard (transport module)
        ReadableDataSource<String, Integer> clusterModeDs = new NacosDataSource<>(remoteAddress, groupId,
            clusterMapDataId, source -> {
            List<ClusterGroupEntity> groupList = new Gson().fromJson(source, new TypeToken<List<ClusterGroupEntity>>(){}.getType());
            return Optional.ofNullable(groupList)
                .map(this::extractMode)
                .orElse(ClusterStateManager.CLUSTER_NOT_STARTED);
        });
        ClusterStateManager.registerProperty(clusterModeDs.getProperty());
    }

    private int extractMode(List<ClusterGroupEntity> groupList) {
        // If any server group serverId matches current, then it's token server.
        if (groupList.stream().anyMatch(this::machineEqual)) {
            return ClusterStateManager.CLUSTER_SERVER;
        }
        // If current machine belongs to any of the token server group, then it's token client.
        // Otherwise it's unassigned, should be set to NOT_STARTED.
        boolean canBeClient = groupList.stream()
            .flatMap(e -> e.getClientSet().stream())
            .filter(Objects::nonNull)
            .anyMatch(e -> e.equals(getCurrentMachineId()));
        return canBeClient ? ClusterStateManager.CLUSTER_CLIENT : ClusterStateManager.CLUSTER_NOT_STARTED;
    }

    private Optional<ServerTransportConfig> extractServerTransportConfig(List<ClusterGroupEntity> groupList) {
        return groupList.stream()
            .filter(this::machineEqual)
            .findAny()
            .map(e -> new ServerTransportConfig().setPort(e.getPort()).setIdleSeconds(600));
    }

    private Optional<ClusterClientAssignConfig> extractClientAssignment(List<ClusterGroupEntity> groupList) {
        if (groupList.stream().anyMatch(this::machineEqual)) {
            return Optional.empty();
        }
        // Build client assign config from the client set of target server group.
        for (ClusterGroupEntity group : groupList) {
            if (group.getClientSet().contains(getCurrentMachineId())) {
                String ip = group.getIp();
                Integer port = group.getPort();
                return Optional.of(new ClusterClientAssignConfig(ip, port));
            }
        }
        return Optional.empty();
    }

    private boolean machineEqual(/*@Valid*/ ClusterGroupEntity group) {
        return getCurrentMachineId().equals(group.getServerId());
    }

    private String getCurrentMachineId() {
        // Note: this may not work well for container-based env.
        return HostNameUtil.getIp() + SEPARATOR + TransportConfig.getRuntimePort();
    }
}

在resources檔案夾下創建META-INF/service,,然后創建一個叫做com.alibaba.csp.sentinel.init.InitFunc的檔案,在檔案中指名實作InitFunc介面的類全路徑,內容如下:

com.liang.springcloud.alibaba.init.ClusterInitFunc

添加配置的決議類:

public class ClusterGroupEntity implements Serializable {

    private String serverId;
    private String ip;
    private Integer port;
    private Set<String> clientSet;

    public String getServerId() {
        return serverId;
    }

    public void setServerId(String serverId) {
        this.serverId = serverId;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public Set<String> getClientSet() {
        return clientSet;
    }

    public void setClientSet(Set<String> clientSet) {
        this.clientSet = clientSet;
    }

    @Override
    public String toString() {
        return "ClusterGroupEntity{" +
                "serverId='" + serverId + '\'' +
                ", ip='" + ip + '\'' +
                ", port=" + port +
                ", clientSet=" + clientSet +
                '}';
    }
}

在Nacos中添加動態規則配置,以及token server與token client的配置:

DataId:cloudalibaba-sentinel-clusterServer-flow-rules Group:SENTINEL_GROUP 配置內容(json格式):

[
    {
        "resource" : "/order/{id}",     // 限流的資源名稱
        "grade" : 1,                         // 限流模式為:qps,執行緒數限流0,qps限流1
        "count" : 20,                        // 閾值為:20
        "clusterMode" :  true,               // 是否是集群模式,集群模式為:true
        "clusterConfig" : {
            "flowId" : 111,                  // 全域唯一id
            "thresholdType" : 1,             // 閾值模式為:全域閾值,0是單機均攤,1是全域閥值
            "fallbackToLocalWhenFail" : true // 在 client 連接失敗或通信失敗時,是否退化到本地的限流模式
        }
    }
]

DataId:cloudalibaba-sentinel-clusterServer-cluster-client-config Group:SENTINEL_GROUP 配置內容(json格式):

{
    "requestTimeout": 20
}

DataId:cloudalibaba-sentinel-clusterServer-cluster-map Group:SENTINEL_GROUP 配置內容(json格式):

[{
	"clientSet": ["10.133.40.30@8721", "10.133.40.30@8722"],
	"ip": "10.133.40.30",
	"serverId": "10.133.40.30@8720",
	"port": 18730   //這個埠是token server通信的埠
}]

重新啟動服務,并訪問介面,我們可以看到流控規則與集群流控都自動配置完成,我們需要測驗,我們集群流控是否已經生效,

不斷執行以下命令:

ab -n 100 -c 50 http://localhost:9091/order/1
ab -n 100 -c 50 http://localhost:9092/order/3
ab -n 100 -c 50 http://localhost:9093/order/1

測驗效果圖:

我們從實時監控圖上可以看出,資源名為/order/{id},整個集群的QPS為20,跟我們的配置是一樣的,當作為token server的機器掛掉后,集群限流會退化到 local 模式的限流,即在本地按照單機閾值執行限流檢查,

在這里插入圖片描述

Token Server 分配配置:

在這里插入圖片描述

上面這張圖可以很好幫忙我們解釋嵌入模式的具體實作,通過配置資訊決議,管理我們的token server與token client,

適用范圍:

嵌入模式適合某個應用集群內部的流控,由于隔離性不佳,token server會影回應用本身,需要限制 token server 的總QPS,

獨立模式部署

獨立模式相對于嵌入模式而言就是將token server與應用隔離,進行獨立部署,將嵌入模式中token server和token client分離,分別進行配置,我們只需要將 InitFunc 實作類進行拆分,

token server的nacos配置

server的名稱空間配置,(集群的namespace或客戶端專案名)如下:

DataId:cluster-server-namespace-set Group:SENTINEL_ALONE_GROUP 配置內容(json格式):

[
    "cloudalibaba-sentinel-cluster-client-alone"
]

server的通信埠配置,如下:

DataId:cluster-server-transport-config Group:SENTINEL_ALONE_GROUP 配置內容(json格式):

{
 "idleSecods":600,
 "port": 18730
}

Token sever的流控限制配置,如下:

DataId:cluster-server-flow-config Group:SENTINEL_ALONE_GROUP 配置內容(json格式):

{
    "exceedCount":1.0,
    "maxAllowedQps":20000,
    "namespace":"cloudalibaba-sentinel-cluster-client-alone"
}

token server的host地址與埠號配置,如下:

DataId: cluster-server-config Group:SENTINEL_ALONE_GROUP 配置內容(json格式):

{
    "serverHost": "10.133.40.30",
    "serverPort": 18730
}

token server的InitFunc類

/**
 * @PROJECT_NAME: SpringCloud-Learning
 * @USER: yuliang
 * @DESCRIPTION:
 * @DATE: 2021-04-01 10:01
 */
public class ClusterServerInitFunc implements InitFunc {

    //nacos集群地址
    private final String remoteAddress = "localhost:8848";
    //配置的分組名稱
    private final String groupId = "SENTINEL_ALONE_GROUP";

    //配置的dataId
    private final String namespaceSetDataId = "cluster-server-namespace-set";
    private final String serverTransportDataId = "cluster-server-transport-config";
    private final String serverFlowDataId = "cluster-server-flow-config";

    @Override
    public void init() {

        //監聽特定namespace(集群的namespace或客戶端專案名)下的集群限流規則
        initPropertySupplier();
        // 設定tokenServer管轄的作用域(即管理哪些應用)
        initTokenServerNameSpaces();

        // Server transport configuration data source.
        //Server端配置
        initServerTransportConfig();

        // 初始化最大qps
        initServerFlowConfig();

        //初始化服務器狀態
        initStateProperty();

    }

    private  void initPropertySupplier(){

        // Register cluster flow rule property supplier which creates data source by namespace.
        // Flow rule dataId format: ${namespace}-flow-rules
        ClusterFlowRuleManager.setPropertySupplier(namespace -> {
            ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(remoteAddress, groupId,
                    namespace + Constants.FLOW_POSTFIX,
                    source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
            return ds.getProperty();
        });
        // Register cluster parameter flow rule property supplier which creates data source by namespace.
        ClusterParamFlowRuleManager.setPropertySupplier(namespace -> {
            ReadableDataSource<String, List<ParamFlowRule>> ds = new NacosDataSource<>(remoteAddress, groupId,
                    namespace + Constants.PARAM_FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {}));
            return ds.getProperty();
        });

    }


    private void initTokenServerNameSpaces(){
        // Server namespace set (scope) data source.
        ReadableDataSource<String, Set<String>> namespaceDs = new NacosDataSource<>(remoteAddress, groupId,
                namespaceSetDataId, source -> JSON.parseObject(source, new TypeReference<Set<String>>() {}));
        ClusterServerConfigManager.registerNamespaceSetProperty(namespaceDs.getProperty());
    }

    private void initServerTransportConfig(){
        // Server transport configuration data source.
        ReadableDataSource<String, ServerTransportConfig> transportConfigDs = new NacosDataSource<>(remoteAddress,
                groupId, serverTransportDataId,
                source -> JSON.parseObject(source, new TypeReference<ServerTransportConfig>() {}));
        ClusterServerConfigManager.registerServerTransportProperty(transportConfigDs.getProperty());
    }


    private void initServerFlowConfig(){

        // Server namespace set (scope) data source.
        ReadableDataSource<String, ServerFlowConfig> serverFlowConfig = new NacosDataSource<>(remoteAddress, groupId,
                serverFlowDataId, source -> JSON.parseObject(source, new TypeReference<ServerFlowConfig>() {}));

        ClusterServerConfigManager.registerGlobalServerFlowProperty(serverFlowConfig.getProperty());
    }

    private void initStateProperty() {
        ClusterStateManager.applyState(ClusterStateManager.CLUSTER_SERVER);

    }
}

token client的nacos配置

客戶端請求超時配置,如下:

DataId:cluster-client-config Group:SENTINEL_ALONE_GROUP 配置內容(json格式):

{
    "requestTimeout": 20
}

流控限流配置,如下:

DataId: cloudalibaba-sentinel-cluster-client-alone-flow-rules Group:SENTINEL_ALONE_GROUP 配置內容(json格式):

[
    {
        "resource" : "/order/{id}",     // 限流的資源名稱
        "grade" : 1,                         // 限流模式為:qps
        "count" : 30,                        // 閾值為:30
        "clusterMode" :  true,               // 集群模式為:true
        "clusterConfig" : {
            "flowId" : 111,                  // 全域唯一id
            "thresholdType" : 1,             // 閾值模式為:全域閾值
            "fallbackToLocalWhenFail" : true // 在 client 連接失敗或通信失敗時,是否退化到本地的限流模式
        }
    }
]

熱點限流配置,如下:

DataId:cloudalibaba-sentinel-cluster-client-alone-param-rules Group:SENTINEL_ALONE_GROUP 配置內容(json格式):

[
    {
        "resource" : "order",          // 限流的資源名稱
        "paramIdx" : 1,                      //引數索引
        "grade" : 1,                         // 限流模式為:qps
        "count" : 10,                        // 閾值為:10
        "clusterMode" :  true,               // 集群模式為:true
        "clusterConfig" : {
            "flowId" : 121,                  // 全域唯一id
            "thresholdType" : 1,             // 閾值模式為:全域閾值
            "fallbackToLocalWhenFail" : true // 在 client 連接失敗或通信失敗時,是否退化到本地的限流模式
        },
        "paramFlowItemList":[      //索引為1的引數值為hot時,介面閾值為50,其他值均為10
            {
                object: "hot",
                count: 50,
                classType: "java.lang.String"
            }
        ]
    }
]

Token client的InitFunc類

/**
 * @PROJECT_NAME: SpringCloud-Learning
 * @USER: yuliang
 * @DESCRIPTION:
 * @DATE: 2021-04-01 17:47
 */
public class ClusterClientInitFunc implements InitFunc {

    //專案名稱
    private static final String APP_NAME = AppNameUtil.getAppName();
    //nacos集群地址
    private final String remoteAddress = "localhost:8848";
    //nacos配置的分組名稱
    private final String groupId = "SENTINEL_ALONE_GROUP";

    //專案名稱 + Constants的配置名稱,組成配置的dataID
    private final String flowDataId = APP_NAME + Constants.FLOW_POSTFIX;
    private final String paramDataId = APP_NAME + Constants.PARAM_FLOW_POSTFIX;
    private final String configDataId = "cluster-client-config";
    private final String serverDataId =  "cluster-server-config";


    @Override
    public void init() throws Exception {

        // Register client dynamic rule data source.
        //客戶端,動態資料源的方式配置sentinel的流量控制和熱點引數限流的規則,
        initDynamicRuleProperty();

        // Register token client related data source.
        // Token client common config
        // 集群限流客戶端的配置屬性
        initClientConfigProperty();
        // Token client assign config (e.g. target token server) retrieved from assign map:
        //初始化Token客戶端
        initClientServerAssignProperty();

        //初始化客戶端狀態
        initStateProperty();
    }

    private void initDynamicRuleProperty() {

        //流量控制的DataId分別是APP_NAME + Constants.FLOW_POSTFIX;熱點引數限流規則的DataId是APP_NAME + Constants.PARAM_FLOW_POSTFIX;

        ReadableDataSource<String, List<FlowRule>> ruleSource = new NacosDataSource<>(remoteAddress, groupId,
                flowDataId, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        FlowRuleManager.register2Property(ruleSource.getProperty());

        ReadableDataSource<String, List<ParamFlowRule>> paramRuleSource = new NacosDataSource<>(remoteAddress, groupId,
                paramDataId, source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {}));
        ParamFlowRuleManager.register2Property(paramRuleSource.getProperty());
    }

    private void initClientConfigProperty() {
        ReadableDataSource<String, ClusterClientConfig> clientConfigDs = new NacosDataSource<>(remoteAddress, groupId,
                configDataId, source -> JSON.parseObject(source, new TypeReference<ClusterClientConfig>() {}));
        ClusterClientConfigManager.registerClientConfigProperty(clientConfigDs.getProperty());
    }

    private void initClientServerAssignProperty() {
        ReadableDataSource<String, ClusterClientAssignConfig> clientAssignDs = new NacosDataSource<>(remoteAddress, groupId,
                serverDataId, source -> JSON.parseObject(source, new TypeReference<ClusterClientAssignConfig>() {}));
        ClusterClientConfigManager.registerServerAssignProperty(clientAssignDs.getProperty());
    }

    private void initStateProperty() {
        ClusterStateManager.applyState(ClusterStateManager.CLUSTER_CLIENT);

    }
}

核心的代碼與配置,如上所示,其他代碼,可以訪問:

<module>cloudalibaba-sentinel-cluster-server-alone9092</module>
<module>cloudalibaba-sentinel-cluster-client-alone9093</module>

測驗:

啟動cloudalibaba-sentinel-cluster-server-alone9092,我們啟動兩個實體,模擬集群(可以啟動多個):

-Dserver.port=9092 -Dcsp.sentinel.log.use.pid=true
-Dserver.port=9094 -Dcsp.sentinel.log.use.pid=true

啟動cloudalibaba-sentinel-cluster-client-alone9093,我們啟動1個實體,模擬server(實作master選舉之后,可以啟動多個):

-Dserver.port=9093 -Dcsp.sentinel.log.use.pid=true

不斷執行以下命令,進行介面訪問測驗:

ab -n 100 -c 50 http://localhost:9092/order/1
ab -n 100 -c 50 http://localhost:9094/order/3

我們從實時監控圖上可以看出,資源名為/order/{id},整個集群的QPS為30,跟我們的配置是一樣的,當作為token server的機器掛掉后,集群限流會退化到 local 模式的限流,即在本地按照單機閾值執行限流檢查,

在這里插入圖片描述

熱點限流已經為大家實作了,大家可以自行測驗,比較簡單,不再累述,

ab -n 100 -c 50  http://localhost:9092/hot_order/1/hot
ab -n 100 -c 50  http://localhost:9094/hot_order/1/hot

ab -n 100 -c 50  http://localhost:9092/hot_order/1/nothot
ab -n 100 -c 50  http://localhost:9094/hot_order/1/nothot

其它

若在生產環境使用集群限流,管控端還需要關注以下的問題:

  • Token Server 自動管理、調度(分配/選舉 Token Server)
  • Token Server 高可用,在某個 server 不可用時自動 failover 到其它機器

總結

集群流控,有兩種模式,嵌入模式和獨立模式,個人不建議在業務系統使用集群流控,集群流控可以在網關層做,業務層的話可以使用單機流控,相對來說簡單好上手,token server目前存在單點問題,需要個人實作master選舉,并修改 cluster-server-config的IP即可,

代碼示例

本文示例讀者可以通過查看下面倉庫中的專案,如下所示:

<module>cloudalibaba-sentinel-cluster</module>
  • Github:https://github.com/jiuqiyuliang/SpringCloud-Learning

博主寫作不易,加個關注唄

求關注、求點贊,加個關注不迷路,感謝

點贊是對我最大的鼓勵
↓↓↓↓↓↓

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/274444.html

標籤:其他

上一篇:【JVM進階之路】九:性能監控工具-可視化工具篇

下一篇:node.js開發自己的腳手架工具以及利用yeoman開發自己的腳手架工具

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more