随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的轻量级流量控制框架,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助您保护服务的稳定性。

流量控制有以下几个角度:

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
  • 运行指标,例如 QPS、线程池、系统负载等;
  • 控制的效果,例如直接限流、冷启动、排队等。

Sentinel 的设计理念是让用户可以自由选择控制的角度,并进行灵活组合,从而达到想要的效果。

一, Sentinel 的使用可以分为两个部分:

  • 核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 7 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard):Dashboard主要负责管理推送规则;监控;管理机器信息等。基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

二, 引入核心库和客户端与dashboard控制台通信的依赖

<!-- sentinel核心库依赖 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.4.0</version>
</dependency>

<!-- sentinel客户端与dashboard通信依赖 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.4.0</version>
</dependency>

三, 定义资源

资源 是 Sentinel 中的核心概念之一。最常用的资源是我们代码中的 Java 方法, 它可以是 Java 应用程序中的任何内容, 或是应用程序提供某一项服务的业务代码, 或是应用程序调用其他应用提供的服务接口, 或者直接就是一段代码, 由此可知sentinel中的资源可以是应用程序中具体的某一行代码, 流控的粒度非常细.

只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

例如: 使用Sentinel Api将被访问的资源包围起来.

Entry entry = null;
// 务必保证finally会被执行
try {
  // 资源名可使用任意有业务语义的字符串
  entry = SphU.entry("自定义资源名");
  // 被保护的业务逻辑, 即被保护的资源
  // do something...
} catch (BlockException e1) {
  // 资源访问阻止,被限流或被降级
  // 进行相应的处理操作
} finally {
  if (entry != null) {
    entry.exit();
  }
}


四, 定义访问该资源的规则

资源定义完成后, 就需要对该资源定义一些访问规则, 如针对上面的资源, 基于QPS的流行控制形式, 采用默认的RuleConstant.CONTROL_BEHAVIOR_DEFAULT;配置每秒最多允许通过20次请求.

private static void initFlowRules(){
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule();
    rule.setResource("HelloWorld");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    // Set limit QPS to 20.
    rule.setCount(20);
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

五, 启动sentinel dashboard控制台

下载sentinel-dashboard.jar

使用下面命令启动dashboard:

java -Dserver.port=8080 -jar sentinel-dashboard.jar

-Dserver.port=8080为sentinel控制台的启动端口, 如下就代表着启动成功.

访问控制台: 由于现在还没有任何客户端接入控制台, 所以看不到应用.

六, 客户端接入dashboard控制台

这里以sentinel自带的sentinel-demo-basic自带的demo为例, 运行FlowQpsDemo类, 并配置上控制台地址.

-Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -- sentinel 控制台ip:port
-Dcsp.sentinel.api.port=8719 -- 客户端用于接收控制台流控规则端口号
-Dproject.name=FlowQpsDemo -- 名称

FlowQpsDemo类,

1 首先定义资源, 成功访问资源时pass+1, 阻止访问资源时会抛出BlockException则block+1.

2 接着定义访问资源的规则:

rule1.setResource(KEY);资源名,即限流规则的作用对象

rule1.setCount(20);设置允许通过的最大请求数20; 限流阈值

rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);设置限流阈值类型, QPS 或线程数模式, 默认是QPS. 

rule1.setLimitApp("default");流控针对的调用来源,若为 default 则不区分调用来源

这里没有在代码显示的配置流控效果, 而当流量超过阈值时, 默认采用的流控效果就是直接拒绝即快速失败, 或者我们也可以直接配置上:

rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); 

下面配上流控规则定义的一些重要属性:

Field说明默认值
resource资源名,资源名是限流规则的作用对象 
count限流阈值 
grade限流阈值类型,QPS 或线程数模式QPS 模式
limitApp流控针对的调用来源default,代表不区分调用来源
strategy判断的根据是资源自身,还是根据其它关联资源 (refResource),还是根据链路入口根据资源本身
controlBehavior流控效果(直接拒绝 / 排队等待 / 慢启动模式)直接拒绝

同一个资源可以同时有多个限流规则。

public class FlowQpsDemo {

    private static final String     KEY         = "abc";

    // 统计通过数
    private static AtomicInteger    pass        = new AtomicInteger();
    // 统计阻止数
    private static AtomicInteger    block       = new AtomicInteger();
    // 统计总数
    private static AtomicInteger    total       = new AtomicInteger();

    private static volatile boolean stop        = false;

    private static final int        threadCount = 32;

    // 运行100s
    private static int              seconds     = 60 + 40;

    public static void main(String[] args) throws Exception {
        // 初始化流控规则
        initFlowQpsRule();
        // 1s计时统计, pass, block, total
        tick();
        // 模拟开启32个线程并发访问资源
        simulateTraffic();
        System.out.println("===== begin to do flow control");
        System.out.println("only 20 requests per second can pass");

    }

    private static void initFlowQpsRule() {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(KEY);
        // set limit qps to 20
        rule1.setCount(20);
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
    }

    private static void simulateTraffic() {
        for (int i = 0; i < threadCount; i++) {
            Thread t = new Thread(new RunTask());
            t.setName("simulate-traffic-Task");
            t.start();
        }
    }

    static class RunTask implements Runnable {

        @Override
        public void run() {
            while (!stop) {
                Entry entry = null;

                try {
                    entry = SphU.entry(KEY);
                    // token acquired, means pass
                    pass.addAndGet(1);
                } catch (BlockException e1) {
                    block.incrementAndGet();
                } catch (Exception e2) {
                    // biz exception
                } finally {
                    total.incrementAndGet();
                    if (entry != null) {
                        entry.exit();
                    }
                }

                Random random2 = new Random();
                try {
                    TimeUnit.MILLISECONDS.sleep(random2.nextInt(50));
                } catch (InterruptedException e) {
                    // ignore
                }
            }
        }
    }

    private static void tick() {
        Thread timer = new Thread(new TimerTask());
        timer.setName("sentinel-timer-task");
        timer.start();
    }

    static class TimerTask implements Runnable {

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            System.out.println("begin to statistic!!!");

            long oldTotal = 0;
            long oldPass = 0;
            long oldBlock = 0;
            while (!stop) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                long globalTotal = total.get();
                long oneSecondTotal = globalTotal - oldTotal;
                oldTotal = globalTotal;

                long globalPass = pass.get();
                long oneSecondPass = globalPass - oldPass;
                oldPass = globalPass;

                long globalBlock = block.get();
                long oneSecondBlock = globalBlock - oldBlock;
                oldBlock = globalBlock;

                System.out.println(seconds + " send qps is: " + oneSecondTotal);
                System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:"
                                   + oneSecondPass + ", block:" + oneSecondBlock);

                if (seconds-- <= 0) {
                    stop = true;
                }
            }

            long cost = System.currentTimeMillis() - start;
            System.out.println("time cost: " + cost + " ms");
            System.out.println("total:" + total.get() + ", pass:" + pass.get() + ", block:" + block.get());
            System.exit(0);
        }
    }
}

后台运行效果, 以及控制台展示:

实时监控, 统计的p_qps=20.

簇点链路展示, 资源名称abc, 通过的QPS, 拒绝的QPS, 线程数, 平均RT, 分钟通过数, 分钟拒绝数

同时, 还可以通过dashboard控制台修改针对该资源的流控规则, 推送给客户端实时应用该规则, 如下图, 修改流控规则QPS为10

则后台console端展示的就是修改后, 最大允许通过QPS为10的情况.

对应的实时监控, 通过的p_qps统计也是10.

而流控规则, 此时对应展示的单机阈值10, 注意这里的流控效果: 快速失败(RuleConstant.CONTROL_BEHAVIOR_DEFAULT则是默认处理超过阈值流量的策略), 指的是, 当QPS超过任意规则的阈值后, 新的请求会被直接拒绝, 拒绝方式为抛出FlowException, FlowException 是 BlockException 的子类, 如下图:

七, 总结

主要介绍了sentinel的基本使用, 定义资源, 定义访问该资源的规则, 如何接入dashboard监控台实时修改流控规则. 但是, 在这里也得注意一下, 监控台修改的规则推送给客户端, 修改客户端的流控规则都是基于内存的, 一旦客户端应用重启, 那么刚刚上面配置的流控规则就会失效, 这时的流控规则就是客户端启动时的最原始配置的流控规则, 所以将dashboard监控台应用于生产环境时, 还需要将流控规则持久化, 当然了sentinel也提供了开放的接口,可以通过实现 DataSource接口的方式,来自定义规则的存储数据源。整合动态配置系统,如 ZooKeeper、Nacos 等,动态地实时刷新配置规则; 结合 RDBMS、NoSQL、VCS 等来实现该规则.

详细请参考:

https://github.com/alibaba/Sentinel/wiki/%E5%9C%A8%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Sentinel

到这里, 我们结合sentinel的dashboard, 实现了基于QPS的流量控制, 采用默认快速失败/直接拒绝策略控制超过阈值的流量, 从而对资源进行保护.

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐