Spring Cloud Alibaba教程:Sentinel 流控规则详解

一、流量控制( flow control)
简单来讲,就是监控应用服务流量的QPS或者并发线程数一些指标,当达到指定的阈值时对流量进行控制,以免被瞬时的流量高峰冲垮,从而达到应用的高可用。

1.1 概念解释:

  • resource 资源名称 :可以认为是接口的请求路径
  • grade 阈值类型:QPS、线程数
  • 是否集群: 单机还是集群
  • strategy : 流控制模式, 直接,关联、 链路
  • controlBehavior : 流控效果 ,有以下几种:

1 、快速失败 :当QPS超过了指定的阈值时,请求会立即拒绝,拒绝的方式是抛出FlowException。

2 、Warm Up (预热/冷启动方式):当系统长期的处于低水平的情况下,流量突然增加时,直接吧系统拉升 到高水位可能瞬间把系统压垮。通过“冷启动”,让请求的流量缓慢增加,在一定时间内增加到阈值的上限,给冷系统一个预热的时间。避免令系统被压垮。通常的设置为这个模式的系统请求的QPS曲线是这样的:
Spring Cloud Alibaba教程:Sentinel 流控规则详解_第1张图片
3 、匀速排队:请求是以均匀的速度通过,对应的是漏斗算法。类似于火车站检票一样,不管有多少人,都是匀速的通过安检:
Spring Cloud Alibaba教程:Sentinel 流控规则详解_第2张图片
1.2 源码解读
在Sentinel中,定义了一个Rule (规则) 的接口,FlowRule (流量规则) 实现了这个接口,这个接口只有一个方法,检查统计当前的指标是否超过设置的阈值,true表示不超过,我们可以看到这些接口当中就定义了以上的概念:

public interface Rule {
    boolean passCheck(Context context, DefaultNode node, int count, Object... args);
}

AbstractRule

public abstract class AbstractRule implements Rule {
   //资源名称,接口的调用路径
    private String resource;
    //调用来源,默认是default,不区分
    private String limitApp;
    
}

FlowRule 继承 AbstractRule 抽象类间接的实现了Rule的接口

public class FlowRule extends AbstractRule {
    //阈值类型,QPS  线程数
    private int grade = 1;
    //数量,限流的阈值
    private double count;
    //流控规则:0:直接, 1:关联 , 2:链路 
    private int strategy = 0;
    private String refResource;
    //流量控制效果 ,0:直接拒绝(默认)  1 预热/冷启动方() 2 速率限制 3 预热+速率限制
    private int controlBehavior = 0;
    
    private int warmUpPeriodSec = 10;
    //最大排队时间
    private int maxQueueingTimeMs = 500;
    
    private boolean clusterMode;
    //集群模式下的流量控制规则
    private ClusterFlowConfig clusterConfig;
    
    private TrafficShapingController controller;
    
....
}

二、基于QPS的流量控制
经过上面的分析,流量控制主要有两种类型,一种是统计并发线程数,另一种是统计QPS,由FlowRule 中的 grade 字段控制

在上一章 Spring Cloud Alibaba教程:Sentinel 实现限流 当中,我们通过Sentinel dashboard 页面配置了QPS的,在单机模式下,我们设置了流量的控制模式为直接,失败的效果是直接快速的失败。当然我们也可以设置通过页面设置其他的类型。
Spring Cloud Alibaba教程:Sentinel 流控规则详解_第3张图片
当然我们也可以用代码实现:

 
public class FlowRuleForQPS {

    private static void init() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //定义以QPS类型
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //定义资源名
        rule.setResource("hello");
        //定义每秒的请求QPS的阈值
        rule.setCount(5);
        //这里我们设置为匀速的模式
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }


    public static void main(String[] args) {
        init();
        for (int j = 0; j < 10; j++) {
            Entry entry = null;
            try {
                entry = SphU.entry("hello");
                System.out.println("操作成功!");
            } catch (BlockException ex) {
                System.out.println("当前访问人数过多,请刷新后重试!");
            } finally {
                if (entry != null) {
                    entry.exit();
                }
            }
        }
    }

基于QPS的限流,我们是可以设置不同的流量控制效果的。

二、基于并发线程数的流量控制
并发线程数限流主要是保护业务线程数不被耗尽。当应用依赖的下游应用由于某种原因不稳定、延迟增加。对于调用者来说,意味着吞吐量下降和更多的线程数占用。为了应对太多线程占用的情况,业内有使用的隔离方案,比如Hystrix通过线程池的方式隔离的。这种方式虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大。特别是对低延时的调用比较大的。Sentinel并发线程数限流不负责创建和管理线程池,而是简单的统计上下文的线程数,如果超出阈值,请求会被立即的拒绝,类似于信号量隔离。

我们通过代码实现以下:

public class FlowRuleForThreadNum {
    private static void init() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //定义以线程数控制
        rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
        //定义资源名
        rule.setResource("hello");
        //定义并发线程数阈值
        rule.setCount(5);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }

    public static void main(String[] args) {
        init();
        for (int i = 0; i < 10; i++) {
            new Thread() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        Entry entry = null;
                        try {
                            entry = SphU.entry("hello");
                            System.out.println("操作成功!");
                        } catch (BlockException ex) {
                            System.out.println("当前访问人数过多,请刷新后重试!");
                        } finally {
                            if (entry != null) {
                                entry.exit();
                            }
                        }
                    }
                }
            }.start();
        }
    }
}

这里的init()方法,在Sentinel dashboard页面中的配置如下:
Spring Cloud Alibaba教程:Sentinel 流控规则详解_第4张图片
我们运行代码,就可以知道,当请求的线程数达到了指定的阈值,就直接的拒绝了,在并发线程限流模式下,流量控制的效果只有一个,当查过设定的阈值,就会立即拒绝。
Spring Cloud Alibaba教程:Sentinel 流控规则详解_第5张图片

四、基于调用关系的流量控制
什么是调用关系?关系是包含了调用方和被调用方,一个方法有可能会调用其他方法,形成一个调用链路的层次关系。Sentinel通过 NodeSelectorSlot 建立不同资源的调用关系, 并且通过 ClusterNodeBuilderSlot 记录每个资源的实时统计信息。

4.1 根据调用方限流
在流控规则中,limitApp 字段用于根据调用方来源进行流量控制,当前的字段有三个选项:

  • default: 表示不区分调用者,来自任何调用者的请求都将进行限流统计。如果这个资源名的调用总和超过了这条规则定义的阈值,则触发限流。
  • {some_origin_name} : 表示针对特定的调用者,只有来自这个调用者的请求才会进行流量控制。例如 NodeA
    配置了一条针对调用者caller1的规则,那么当且仅当来自 caller1 对 NodeA 的请求才会触发流量控制。
  • other: 表示针对除 {some_origin_name}
    以外的其余调用方的流量进行流量控制。例如,资源NodeA配置了一条针对调用者 caller1 的限流规则,同时又配置了一条调用者为
    other 的规则,那么任意来自非 caller1 对 NodeA 的调用,都不能超过 other 这条规则定义的阈值。

同一个资源名可以配置多条规则,规则的生效顺序为:{some_origin_name} > other > default

我们可以通过 ContextUtil.enter(resourceName, origin) 方法中的 origin 参数标明了调用方身份。这些信息会在 ClusterBuilderSlot 中被统计,先看实例代码:

在使用基于调用关系的流量控制时候,我们对资源/hello接口,设置了QPS=2,受限制的应用为caller,设置了流控模式为直接拒绝。当我们调用该资源时,我们通过 ContextUtil.enter(“contextname”, “caller”) 方法声明了当前的调用者就是caller,如下代码:

public class FlowRuleForCaller {

    private static void init() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //定义以QPS类型
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //定义资源名
        rule.setResource("hello");
        //定义每秒的请求QPS的阈值
        rule.setCount(2);
       //设置受限制的应用名称
        rule.setLimitApp("caller");
        //这里我们设置直接拒绝模式
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        rules.add(rule);      
        
        //我们可以设置多个规则,针对不同的应用
        //针对除了应用名称为caller的其他应用
        FlowRule rule1 = new FlowRule();
        rule1.setResource("hello");
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("other");
        rule1.setCount(3);
        rules.add(rule1);
        
        //统一添加到规则管理器中
        FlowRuleManager.loadRules(rules);
    }


    public static void main(String[] args) {
        init();
        for (int i = 0; i < 5; i++) {
            //声明当前的调用方的应用名称, 通过origin参数
            ContextUtil.enter("contextname", "caller");
            Entry entry = null;
            try {
                entry = SphU.entry("hello");
                System.out.println("访问成功");
            } catch (BlockException e) {
                System.out.println("网络异常,请刷新!");
            } finally {
                if (entry != null) {
                    entry.exit();
                }
            }
        }
    }
}

当我们运行代码时,其效果就发挥了作用,如果我们将caller换成其他的调用方,那么规则就不一样了:
Spring Cloud Alibaba教程:Sentinel 流控规则详解_第6张图片
当然我们可以通过web页面设置:
Spring Cloud Alibaba教程:Sentinel 流控规则详解_第7张图片
4.2 根据调用链路入口限流:链路限流
在Sentinel中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root 的虚拟节点,调用链的入口都是这个虚节点的子节点。

一棵典型的调用树如下图所示:

machine-root
/
/
Entrance1 Entrance2
/
/
DefaultNode(nodeA) DefaultNode(nodeA)

ContextUtil.enter(name,origin) 有2个参数,第一个参数表示上下文名称,绑定了调用链入口,第二个表示调用方,我们可以通过设置上下文名称设置调用链路入口:

public class FlowRuleForEntrance {

    private static void init() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //设置阈值类型 qps
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
       //設置流控模式为 调用链路入口
        rule.setStrategy(RuleConstant.STRATEGY_CHAIN);
        //定义资源名
        rule.setResource("hello");
        //定义每秒的请求QPS的阈值
        rule.setCount(2);
        //设置入口
        rule.setRefResource("Entrance1");

        //这里我们设置直接拒绝模式
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        rules.add(rule);

        FlowRuleManager.loadRules(rules);
    }

    public static void main(String[] args) {
        init();
        for (int i = 0; i < 5; i++) {
            ContextUtil.enter("Entrance1");
            Entry entry = null;
            try {
                entry = SphU.entry("hello");
                System.out.println("访问成功");
            } catch (BlockException e) {
                System.out.println("网络异常,请刷新!");
            } finally {
                if (entry != null) {
                    entry.exit();
                }
            }

        }
    }
}

控制台的配置如下:
Spring Cloud Alibaba教程:Sentinel 流控规则详解_第8张图片
通过以上的配置同样可以实现限流作用。

4.3 、具有关系的资源流量控制:关联流量控制
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 FlowRule.strategy 为 RuleConstant.RELATE 同时设置 FlowRule.ref_identity 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。

4.4 其他
关于更多的问题我们可以参考: https://github.com/alibaba/Sentinel/wiki/FAQ

你可能感兴趣的:(Spring Cloud Alibaba教程:Sentinel 流控规则详解)