Spring Cloud Alibaba Sentinel 集群流量控制

为什么要进行集群流控

假设集群中有 10 台机器,我们给每台机器设置单机限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。

集群流控中共有两种身份

  • Token Client:集群流控客户端,用于向所属 Token Server 通信请求 token。集群限流服务端会返回给客户端结果,决定是否限流。
  • Token Server:即集群流控服务端,处理来自 Token Client 的请求,根据配置的集群规则判断是否应该发放 token(是否允许通过)。

Token Server 集群限流服务端

两种启动方式

  • 独立模式(Alone),即作为独立的 token server 进程启动,独立部署,隔离性好,但是需要额外的部署操作。独立模式适合作为 Global Rate Limiter 给集群提供流控服务。
    在这里插入图片描述
    在独立模式下,我们可以直接创建对应的 ClusterTokenServer 实例并在 main 函数中通过 start 方法启动 Token Server。

  • 嵌入模式(Embedded),即作为内置的 token server 与服务在同一进程中启动。在此模式下,集群中各个实例都是对等的,token server 和 client 可以随时进行转变,因此无需单独部署,灵活性比较好。但是隔离性不佳,需要限制 token server 的总 QPS,防止影响应用本身。嵌入模式适合某个应用集群内部的流控。
    在这里插入图片描述
    在 embedded 模式下通过API转换集群流控身份:

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

其中 mode 为 0 代表 client,1 代表 server,-1 代表关闭。注意应用端需要引入集群限流客户端或服务端的相应依赖。

独立模式示例

本文只介绍独立模式示例。我们的示例使用SPI方式初始化流控规则等。

  1. 添加 maven 依赖
<!-- Token Server 依赖 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-cluster-server-default</artifactId>
    <version>${sentinel.version}</version>
</dependency>

<!-- 支持Nacos数据源 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>${sentinel.version}</version>
</dependency>
  1. 添加SPI配置
    在这里插入图片描述
    com.alibaba.csp.sentinel.init.InitFunc 文件配置如下
com.yyoo.sentinel.server.func.MyClusterServerInitFunc

注:此处配置遵循Java的SPI规则即可,不做过多详解。

  1. MyClusterServerInitFunc 代码
package com.yyoo.sentinel.server.func;

import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.init.InitOrder;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.fasterxml.jackson.core.type.TypeReference;

import java.util.List;
import java.util.Properties;
import java.util.Set;

/**
 * 集群流控,服务端  规则 + Namespace + ServerTransportConfig 使用 Nacos 动态推送
 */
@InitOrder(1) // 用于定义执行顺序
public class MyClusterServerInitFunc implements InitFunc {

    /**
     * Nacos 地址
     */
    public static final String NACOS_ADDRS = "localhost:8848";

    /**
     * 对应 Nacos 的命名空间 ID
     *
     *  fd315d16-12ef-4d67-b8e2-8dfe2e6667b5
     *  855e91a2-60e7-48c3-aa3f-27aa2f8a0f73
     */
    public static final String NACOS_SENTINEL_NAMESPACE = "fd315d16-12ef-4d67-b8e2-8dfe2e6667b5";

    /**
     * Nacos group id
     */
    public static final String NACOS_SENTINEL_GROUPID = "Sentinel_Demo_Group";

    /**
     * 集群限流规则 dataID
     * 获取到的值为规则列表,如: List<FlowRule>
     */
    public static final String NACOS_SENTINEL_CLUSTER_RULES_DATAID = "Cluster-Flow-Rule";

    /**
     * 集群流控作用域(默认对应集群应用的 project.name 应用名)
     * 获取到的值为 Set<String> 类型,即为应用名列表
     */
    public static final String NACOS_SENTINEL_NAMESPACES_DATAID = "Cluster-Name-Space";

    /**
     * 集群流控 Server 端配置
     * 获取到的值为 ServerTransportConfig 类型
     */
    public static final String NACOS_SENTINEL_SERVER_TRANSPORT_CONFIG_DATAID = "Cluster-Server-Config";

    @Override
    public void init() throws Exception {

        Properties nacosPro = new Properties();
        nacosPro.put(PropertyKeyConst.SERVER_ADDR,NACOS_ADDRS);
        nacosPro.put(PropertyKeyConst.NAMESPACE,NACOS_SENTINEL_NAMESPACE);
        // 配置 Nacos 动态规则数据源 (此处只定义了限流规则,其他规则如:热点参数规则可按此示例配置即可)
        ClusterFlowRuleManager.setPropertySupplier(namespace -> {
            ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(nacosPro, NACOS_SENTINEL_GROUPID, NACOS_SENTINEL_CLUSTER_RULES_DATAID,
                    source -> JsonUtil.toGenericBean(source, new TypeReference<List<FlowRule>>(){}));
            return ds.getProperty();
        });

        // 配置从 Nacos 动态获取应用集群作用域NameSpace(即默认为集群的project.name)
        ReadableDataSource<String, Set<String>> nameSpaceDS = new NacosDataSource<>(nacosPro, NACOS_SENTINEL_GROUPID, NACOS_SENTINEL_NAMESPACES_DATAID,
                source -> JsonUtil.toGenericBean(source, new TypeReference<Set<String>>(){}));
        ClusterServerConfigManager.registerNamespaceSetProperty(nameSpaceDS.getProperty());


        // 配置从 Nacos 动态获取 Token Server 的配置
        ReadableDataSource<String, ServerTransportConfig> serverTransportConfigDS = new NacosDataSource<>(nacosPro, NACOS_SENTINEL_GROUPID, NACOS_SENTINEL_SERVER_TRANSPORT_CONFIG_DATAID,
                source -> JsonUtil.toGenericBean(source, new TypeReference<ServerTransportConfig>(){}));
        ClusterServerConfigManager.registerServerTransportProperty(serverTransportConfigDS.getProperty());

    }
}


服务端还可以通过 ClusterServerConfigManager.setMaxAllowedQps(); 设置 token server 最大允许的总 QPS(maxAllowedQps),来对 token server 的资源使用进行限制,防止在嵌入模式下影响应用本身

  1. 服务端启动类
package com.yyoo.sentinel.server;

import com.alibaba.csp.sentinel.cluster.server.ClusterTokenServer;
import com.alibaba.csp.sentinel.cluster.server.SentinelDefaultTokenServer;

public class Main {

    public static void main(String[] args) throws Exception {
        ClusterTokenServer tokenServer = new SentinelDefaultTokenServer();
        tokenServer.start();
    }

}

直接执行main函数启动即可

注:Server启动后,日志可以在~/logs/csp/ 文件夹下查看 sentinel-record.log。如需配置日志路径等,请查阅 Spring Cloud Alibaba Sentinel 控制台 一文中关于客户端配置文件部分。

客户端SPI配置类

package com.yyoo.sentinel.config;

import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.init.InitOrder;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.fasterxml.jackson.core.type.TypeReference;

import java.util.List;
import java.util.Properties;

/**
 * 集群流控,服务端  规则 + Namespace + ServerTransportConfig 使用 Nacos 动态推送
 */
@InitOrder(1) // 用于定义执行顺序
public class MyClusterServerInitFunc implements InitFunc {

    /**
     * Nacos 地址
     */
    public static final String NACOS_ADDRS = "localhost:8848";

    /**
     * 对应 Nacos 的命名空间 ID
     *
     *  fd315d16-12ef-4d67-b8e2-8dfe2e6667b5
     *  855e91a2-60e7-48c3-aa3f-27aa2f8a0f73
     */
    public static final String NACOS_SENTINEL_NAMESPACE = "fd315d16-12ef-4d67-b8e2-8dfe2e6667b5";

    /**
     * Nacos group id
     */
    public static final String NACOS_SENTINEL_GROUPID = "Sentinel_Demo_Group";

    /**
     * 集群限流规则 dataID
     * 获取到的值为规则列表,如: List<FlowRule>
     *     和 Server 端一致
     */
    public static final String NACOS_SENTINEL_CLUSTER_RULES_DATAID = "Cluster-Flow-Rule";


    /**
     * 集群流控 Client 端配置
     * 获取到的值为 ClusterClientConfig 类型
     */
    public static final String NACOS_SENTINEL_CLIENT_TRANSPORT_CONFIG_DATAID = "Cluster-Client-Config";

    @Override
    public void init() throws Exception {
        // Nacos 配置
        Properties nacosPro = new Properties();
        nacosPro.put(PropertyKeyConst.SERVER_ADDR,NACOS_ADDRS);
        nacosPro.put(PropertyKeyConst.NAMESPACE,NACOS_SENTINEL_NAMESPACE);

        // 集群客户端获取限流规则,此处和非集群模式一样
        ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(nacosPro, NACOS_SENTINEL_GROUPID, NACOS_SENTINEL_CLUSTER_RULES_DATAID,
                source -> JsonUtil.toGenericBean(source, new TypeReference<List<FlowRule>>(){}));
        FlowRuleManager.register2Property(ds.getProperty());

        // 获取集群客户端配置(客户端还有一个 ClusterClientConfig 配置需要请自行配置)
        /**
         * serverHost: token server host
         * serverPort: token server 端口
         * requestTimeout: 请求的超时时间(默认为 20 ms)
         */
        ReadableDataSource<String, ClusterClientAssignConfig> clientConfigDs = new NacosDataSource<>(nacosPro, NACOS_SENTINEL_GROUPID, NACOS_SENTINEL_CLIENT_TRANSPORT_CONFIG_DATAID,
                source -> JsonUtil.toGenericBean(source, new TypeReference<ClusterClientAssignConfig>(){}));
        ClusterClientConfigManager.registerServerAssignProperty(clientConfigDs.getProperty());

        // 设置当前应用为 Token Client。
        /**
         * 此处推荐使用 API http://<ip>:<port>/setClusterMode?mode=<xxx> 进行设置
         * 我们采用独立的 Token Server 模式,所以此处初始化直接写死的。
         * 0:表示 Token Client
         * 1:表示 Token Server
         * -:表示未设置,默认 -1
         */
        ClusterStateManager.applyState(0);
    }
}

客户端如果使用了Spring Cloud Alibaba ,可以使用 application.yml 配置动态数据源的

spring:
  application:
    name: mySentinelDemo # 此为客户端应用名(注意同集群流控的namespace 作用域相对应)
  cloud:
    sentinel:
      enabled: true 
      datasource: # 如果使用了 InitFunc 此可以不用配置
        myFlowRules:
          server-addr: ${my.nacos.server-addr}
          namespace: ${my.nacos.sentinel-namespace}
          group-id: Sentinel_Demo_Group
          data-id: Cluster-Flow-Rule
          data-type: json
          rule-type: flow

关于使用 application.yml 配置动态数据源请查阅我们 Spring Cloud Alibaba Sentinel 动态规则扩展 一章

我们此处还是使用的 SPI InitFunc 方式,因为使用 application.yml 配置方式只能配置动态规则,我们此处的其他配置还是得使用 InitFunc 的方式。

集群限流配置规则

FlowRule

集群流控的配置依然使用FlowRule

private boolean clusterMode; // 标识是否为集群限流配置
private ClusterFlowConfig clusterConfig; // 集群限流相关配置项
private double count; // 限流的阀值(注意:根据均摊模式或全局模式的不同,阀值表示的意义也不一样)
private String resource; // 资源名称(Spring Boot / Spring Cloud alibaba 下会自动定义资源,资源名称根据url生成,可通过控制台查看自动定义的资源)

以上 clusterMode、count、resource为必须配置的字段,否则集群流控规则不会生效

在集群Token Client 无法与 Token Server 正常通信时,如果设置了可以降级为本地流控,那么FlowRule的其他参数也可以进行相应配置,请参考我们 Spring Cloud Alibaba Sentinel 流量控制 一章

集群流控规则配置类 ClusterFlowConfig 字段说明


	/**
	 * 代表全局唯一的规则 ID,Sentinel 集群限流服务端通过此 ID 来区分各个规则,因此务必保持全局唯一。
     * 一般 flowId 由统一的管控端进行分配,或写入至 DB 时生成。
     * 此字段必须
	 */
	private Long flowId;

    /**
     * 0:单机均摊模式:配置的阈值等同于单机能够承受的限额. 
     * 		比如:集群应用名称为myCloud,有3个应用,单机均摊阀值为10,那么集群的阀值为30
     * 1:全局模式:配置的阈值等同于整个集群的总阈值
     *  默认 0
     */
    private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL;
    /**
     * 在 client 连接失败或通信失败时,是否退化到本地的限流模式
     * 默认为 true
     */
    private boolean fallbackToLocalWhenFail = true;

    /**
     * 0: normal.
     */
    private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL;

    private int sampleCount = ClusterRuleConstant.DEFAULT_CLUSTER_SAMPLE_COUNT;
    /**
     * 统计滑动窗口的时间间隔长度(毫秒)
     *  默认:1000ms
     */
    private int windowIntervalMs = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS;

    /**
     * 如果客户端保留令牌的时间超过resourceTimeout,则resourceTimeoutStrategy将起作用。
     */
    private long resourceTimeout = 2000;

    /**
     * 0:忽略
     * 1:释放token
     */
    private int resourceTimeoutStrategy = RuleConstant.DEFAULT_RESOURCE_TIMEOUT_STRATEGY;

    /**
	 * 如果请求(prioritized=true,优先级为true)被阻止,则acquireReuseStrategy将起作用。。
	 * 0:忽略并阻止。
	 * 1:再试一次。
	 * 2:努力直到成功。
     */
    private int acquireRefuseStrategy = RuleConstant.DEFAULT_BLOCK_STRATEGY;

    /**
     * 如果客户端脱机,服务器将删除客户端在clientOfflineTime之后持有的所有令牌。
     */
    private long clientOfflineTime = 2000;

Nacos 中的规则配置、namespace 规则作用域配置、Server和Client端的基础配置如下

注:以下配置都是根据我们的示例设置的 Nacos 的 namespace 、groupId、dataId

以上的配置总览如下

在这里插入图片描述

集群流控规则

在这里插入图片描述

[
    {
        "resource": "/test/flow1", 
        "limitApp": "default", 
        "grade": 1, 
        "count": 0, 
        "strategy": 0, 
        "refResource": null, 
        "controlBehavior": 0, 
        "warmUpPeriodSec": 10, 
        "maxQueueingTimeMs": 500, 
        "clusterMode": true, 
        "clusterConfig": {
            "flowId": 1, 
            "thresholdType": 0, 
            "fallbackToLocalWhenFail": true, 
            "strategy": 0, 
            "sampleCount": 10, 
            "windowIntervalMs": 1000, 
            "resourceTimeout": 2000, 
            "resourceTimeoutStrategy": 0, 
            "acquireRefuseStrategy": 0, 
            "clientOfflineTime": 2000
        }
    }
]

注意:此处我们设置的 count 为 0 。这是为了方便首次验证

集群流控作用域 namespace

在这里插入图片描述

Token Server 配置

在这里插入图片描述

Token Client 配置

在这里插入图片描述

示例验证

在客户端编写Controller url 为 /test/flow1 ,并访问即可。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值