Ali-Sentinel-节点与度量

归档

  • GitHub: Ali-Sentinel-节点与度量

作用

  • 保存资源的实时统计信息

节点

节点-类结构

  • com.alibaba.csp.sentinel.slots.statistic.metric.DebugSupport
/** 调试支持 */
public interface DebugSupport {
    void debug();   // 打印统计信息
}
  • com.alibaba.csp.sentinel.node.OccupySupport
/** 占用支持 */
public interface OccupySupport {
    ...
}
  • com.alibaba.csp.sentinel.node.Node
/** 节点 (用于统计) */
public interface Node extends OccupySupport, DebugSupport {
    long totalRequest();    // 获取每分钟传入的请求
    long totalPass();       // 获取每分钟的通过次数
    long totalSuccess();    // 每分钟完成的请求(Entry.exit())总数
    long blockRequest();    // 获取每分钟阻止的请求计数
    long totalException();  // 获取每分钟的异常计数
    double passQps();       // 获取每秒已通过请求的 QPS
    double blockQps();      // 获取每秒被阻止请求的 QPS
    double totalQps();      // 获取每秒总请求的 QPS
    double successQps();    // 获取每秒已完成请求的(Entry.exit()) QPS
    double maxSuccessQps(); // 获得最大已完成请求的 QPS
    double exceptionQps();  // 发生异常的 QPS
    double avgRt();         // 平均每秒响应时间 (Rt: response time)
    double minRt();
    int curThreadNum();
    double previousBlockQps();
    double previousPassQps();
    void addPassRequest(int count);
    void addRtAndSuccess(long rt, int success);
    void increaseBlockQps(int count);
    void increaseExceptionQps(int count);
    void increaseThreadNum();
    void decreaseThreadNum();
    void reset();
}
  • com.alibaba.csp.sentinel.node.StatisticNode
/** 统计节点 */
public class StatisticNode implements Node {
    // 保存最近 INTERVAL(1000) 毫秒的统计信息。
    // 节点的统计逻辑都委派给其处理,ref: sign_i_100 & sign_c_100
    private transient volatile Metric rollingCounterInSecond = new ArrayMetric(                 // sign_cm_101
        SampleCountProperty.SAMPLE_COUNT,   // def: 2
        IntervalProperty.INTERVAL           // def: 1000
    );
    // 保存最近 60 秒的统计信息。
    // 节点的统计逻辑都委派给其处理,ref: sign_i_100 & sign_c_100
    private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);    // sign_cm_102
    // -----------
    private LongAdder curThreadNum = new LongAdder();
    private long lastFetchTime = -1;
}
  • com.alibaba.csp.sentinel.node.DefaultNode
/** 默认节点 */
public class DefaultNode extends StatisticNode {
    private ResourceWrapper id;         // 关联的资源 (标识)
    private volatile Set<Node> childList = new HashSet<>(); // 子节点集
    private ClusterNode clusterNode;    // 关联的群集节点。
}
  • com.alibaba.csp.sentinel.node.EntranceNode
/** 入口节点 */
public class EntranceNode extends DefaultNode {
    ... // 只是根据所有的子节点进行计算统计结果
}
  • com.alibaba.csp.sentinel.node.ClusterNode
/** 集群节点 */
public class ClusterNode extends StatisticNode {
    private final String name;          // 名称
    private final int resourceType;     // 资源类型 (0: COMMON; 1: WEB; 2: RPC; 3: ApiGateway; 4: DB)
    private Map<String, StatisticNode> originCountMap = new HashMap<>(); // 保存不同来源的 StatisticNode
    private final ReentrantLock lock = new ReentrantLock(); // 操作 originCountMap 的 DCL 锁
}

节点-调用链

  • 基本委派给度量指标了

度量指标

  • 节点的统计都委派给 ArrayMetric

度量指标-类结构

  • com.alibaba.csp.sentinel.slots.statistic.metric.Metric
/** sign_i_100 度量接口 */
public interface Metric extends DebugSupport {
    long success();                 // 获取总成功数。
    long maxSuccess();              // 获取最大成功次数。
    long exception();               // 获取异常总数。
    long block();                   // 获取总阻塞次数。
    long pass();                    // 获取总通过数。 不包括 occupiedPass()
    long rt();                      // 获取总响应时间。
    long minRt();                   // 获得最小的 RT。
    List<MetricNode> details();     // 获取所有资源的聚合指标节点。
    MetricBucket[] windows();       // 获取原始窗口数组。
    void addException(int n);       // 添加当前异常计数。
    void addBlock(int n);           // 添加当前阻塞数。
    void addSuccess(int n);         // 添加当前完成的计数。
    ...
}
  • com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric
/** sign_c_100 度量实现类 */
public class ArrayMetric implements Metric {    // 实现 sign_i_100

    private final LeapArray<MetricBucket> data; // 统计依然向下委派处理,ref: sing_ac_110

    // sign_cm_101
    public ArrayMetric(int sampleCount, int intervalInMs) {
        this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);   // 秒统计的使用此,ref: sign_cm_110
    }

    // sign_cm_102
    public ArrayMetric(int sampleCount, int intervalInMs, boolean enableOccupy) {
        if (enableOccupy) {
            this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);
        } else {
            this.data = new BucketLeapArray(sampleCount, intervalInMs);         // 分统计的使用此,ref: sign_cm_110
        }
    }
}
  • com.alibaba.csp.sentinel.slots.statistic.base.LeapArray
/** sing_ac_110 统计指标的基本数据结构 */
public abstract class LeapArray<T> {

    protected int windowLengthInMs;     // 窗口时间跨度
    protected int sampleCount;
    protected int intervalInMs;
    private double intervalInSecond;

    protected final AtomicReferenceArray<WindowWrap<T>> array;      // T: MetricBucket, ref: sign_c_130 | sing_c_140

    // 更新锁,仅在当前 bucket 已弃用时使用。
    private final ReentrantLock updateLock = new ReentrantLock();

    // sign_cm_110
    public LeapArray(int sampleCount, int intervalInMs) {
        ... // 省略校验 (两参必须大于 0,且能整除)

        this.windowLengthInMs = intervalInMs / sampleCount;
        this.intervalInMs = intervalInMs;
        this.intervalInSecond = intervalInMs / 1000.0;
        this.sampleCount = sampleCount;

        this.array = new AtomicReferenceArray<>(sampleCount);
    }

}
  • com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap
/** sign_c_130 一段时间窗口的包装实体类 */
public class WindowWrap<T> {

    private final long windowLengthInMs;    // 单个窗口桶的时间长度(以毫秒为单位)。
    private long windowStart;               // 窗口的开始时间戳(以毫秒为单位)。
    private T value;                        // 统计数据。一般为: MetricBucket, ref: sing_c_140

    public WindowWrap(long windowLengthInMs, long windowStart, T value) {
        this.windowLengthInMs = windowLengthInMs;
        this.windowStart = windowStart;
        this.value = value;
    }

}
  • com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket
/** sing_c_140 度量桶 (一段时间内的度量数据) */
public class MetricBucket {

    private final LongAdder[] counters; // sign_f_110 各事件的计数器
    private volatile long minRt;        // 记录最小 RT 值。def: 5000

    public MetricBucket() {
        MetricEvent[] events = MetricEvent.values();        // ref: sign_ec_140
        this.counters = new LongAdder[events.length];
        for (MetricEvent event : events) {
            counters[event.ordinal()] = new LongAdder();    // 每个事件,一个计数器
        }
        initMinRt();    // 使用配置的最大 RT 值初始化
    }

}
  • com.alibaba.csp.sentinel.slots.statistic.MetricEvent
/** sign_ec_140 事件枚举 */
public enum MetricEvent {
    PASS,
    BLOCK,
    EXCEPTION,
    SUCCESS,
    RT,
    /**
     * 在未来配额中通过(自 1.5.0 起,预先占用)
     */
    OCCUPIED_PASS
}

度量指标-调用链

addPass()
  • 添加 PASS 计数

  • com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric

    @Override
    public void addPass(int count) {
        WindowWrap<MetricBucket> wrap = data.currentWindow();   // 获取当前时间戳的桶  ref: sign_m_201
        wrap.value().addPass(count);    // 添加 PASS 计数  ref: sign_m_210
    }
  • com.alibaba.csp.sentinel.slots.statistic.base.LeapArray
    // sign_m_201 获取当前时间戳的桶
    public WindowWrap<T> currentWindow() {
        return currentWindow(TimeUtil.currentTimeMillis()); // sign_m_202
    }

    // sign_m_202 在提供的时间戳处获取桶
    public WindowWrap<T> currentWindow(long timeMillis) {
        ... // 省略小于 0 的判断

        int idx = calculateTimeIdx(timeMillis);                 // sign_m_203 计算当前索引
        long windowStart = calculateWindowStart(timeMillis);    // sign_m_204 计算当前窗口的开始时间

        /**
         * 从数组中获取给定时间的存储桶。
         *
         * (1) Bucket 不存在,则只需创建一个新的 Bucket 并 CAS 更新为循环数组。
         * (2) Bucket 是最新的,那么只需返回 Bucket 即可。
         * (3) Bucket 已弃用,然后重置当前 Bucket。
         */
        while (true) {
            WindowWrap<T> old = array.get(idx);
            if (old == null) {  // 桶不存在,则创建
                /**
                 * newEmptyBucket(timeMillis) 为抽象方法,
                 * 两子类实现:直接返回 new MetricBucket()
                 */
                WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
                if (array.compareAndSet(idx, null, window)) {   // CAS 更新
                    return window;  // 更新成功,返回创建的 bucket
                } else {
                    Thread.yield(); // 争用失败,让出时间片等待可用的桶
                }
            } else if (windowStart == old.windowStart()) {  // 桶是最新的
                // 表明另一线程刚好调用上面或下面的逻辑(创建出桶并 CAS 更新完或重置完旧桶)
                return old;
            } else if (windowStart > old.windowStart()) {   // 桶是旧的
                /**
                 * 使用锁,保证重置和清理是原子操作
                 */
                if (updateLock.tryLock()) {
                    try {
                        return resetWindowTo(old, windowStart); // 重置桶(抽象方法),实现参考: sign_m_206
                    } finally {
                        updateLock.unlock();
                    }
                } else {
                    Thread.yield(); // 争用失败,让出时间片等待可用的桶
                }
            } else if (windowStart < old.windowStart()) {
                // 不应该到这里...
                return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            }
        }
    }

    // sign_m_203 计算当前索引,以便将时间戳映射到跳跃 (leap) 数组
    private int calculateTimeIdx(long timeMillis) {
        long timeId = timeMillis / windowLengthInMs;    // 除窗口时间跨度,得窗口主索引
        return (int)(timeId % array.length());          // 用主索引取模,映射到数组索引
    }

    // sign_m_204 计算窗口的开始时间 (向前取整)
    protected long calculateWindowStart(long timeMillis) {
        return timeMillis - timeMillis % windowLengthInMs;
    }
  • com.alibaba.csp.sentinel.slots.statistic.metric.BucketLeapArray
    // sign_m_206 重置相关统计
    @Override
    protected WindowWrap<MetricBucket> resetWindowTo(WindowWrap<MetricBucket> w, long startTime) {
        // Update the start time and reset value.
        w.resetTo(startTime);   // 重置 bucket 封装的开始时间戳 sign_m_207
        w.value().reset();      // 重置 bucket 各事件计数器 sign_m_208
        return w;
    }
  • com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap
    // sign_m_207 重置当前 bucket 的开始时间戳
    public WindowWrap<T> resetTo(long startTime) {
        this.windowStart = startTime;
        return this;
    }
  • com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket
    // sign_m_208 重置各事件计数器
    public MetricBucket reset() {
        for (MetricEvent event : MetricEvent.values()) {
            counters[event.ordinal()].reset();
        }
        initMinRt();
        return this;
    }
  • com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket
    // sign_m_210 添加 PASS 计数
    public void addPass(int n) {
        add(MetricEvent.PASS, n);   // sign_m_211
    }

    // sign_m_211 添加指定事件的计数
    public MetricBucket add(MetricEvent event, long n) {
        counters[event.ordinal()].add(n);   // 根据枚举的序数定位计数器,然后进行累加  ref: sign_f_110
        return this;
    }
pass()
  • 获取 PASS 计数

  • com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric

    @Override
    public long pass() {
        data.currentWindow();   // 设置当前时间对应的桶,ref: sign_m_201
        long pass = 0;
        List<MetricBucket> list = data.values();    // 获取当前“有效”桶的集合  ref: sign_m_220

        for (MetricBucket window : list) {
            pass += window.pass();  // 对每个度量桶的 PASS 进行累加  ref: sign_m_225
        }
        return pass;
    }
  • com.alibaba.csp.sentinel.slots.statistic.base.LeapArray
    // sign_m_220 获取当前“有效”桶的集合
    public List<T> values() {
        return values(TimeUtil.currentTimeMillis()); // sign_m_221
    }

    // sign_m_221 获取指定时间戳“有效”桶的集合
    public List<T> values(long timeMillis) {
        if (timeMillis < 0) {
            return new ArrayList<T>();
        }
        int size = array.length();
        List<T> result = new ArrayList<T>(size);

        for (int i = 0; i < size; i++) {
            WindowWrap<T> windowWrap = array.get(i);
            /**
             * 为空或当前时间大于桶的开始时间 (桶无效),则不添加
             * ref: sign_m_222
             */
            if (windowWrap == null || isWindowDeprecated(timeMillis, windowWrap)) {
                continue;
            }
            result.add(windowWrap.value()); // 否则添加返回
        }
        return result;
    }

    // sign_m_222 判断桶是否无效
    // 桶开始时间必须小于当前时间才算有效,否则算无效 (返回 true)
    public boolean isWindowDeprecated(long time, WindowWrap<T> windowWrap) {
        return time - windowWrap.windowStart() > intervalInMs;
    }
  • com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket
    // sign_m_225 返回 PASS 计数
    public long pass() {
        return get(MetricEvent.PASS);   // sign_m_226
    }

    // sign_m_226 返回指定事件的计数
    public long get(MetricEvent event) {
        return counters[event.ordinal()].sum();
    }

总结

  • 使用数组 + 时间戳实现滑动窗口,算法简单精妙

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/572330.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

react09 hooks(useState)

react-09 hooks&#xff08;useState&#xff09; hooks组件&#xff08;函数组件动态化&#xff09; 其本质就是函数组件&#xff0c;引用一些hooks方法&#xff0c;用来在函数组件中进行例如状态管理&#xff0c;模拟类组件的生命周期等&#xff0c;只能运用到函数组件中 ho…

基于MaxKB搭建一个知识库问答系统

什么是MaxKB MaxKB 是一款基于 LLM 大语言模型的知识库问答系统。MaxKB Max Knowledge Base&#xff0c;旨在成为企业的最强大脑。 开箱即用&#xff1a;支持直接上传文档、自动爬取在线文档&#xff0c;支持文本自动拆分、向量化&#xff0c;智能问答交互体验好&#xff1b…

DHCP Relay配置与抓包

前言&#xff1a;DHCP请求报文是以广播包方式发送的&#xff0c;当DHCP服务器与DHCP客户端不在同一网段时&#xff0c;就需要在三层网关设备配置DHCP中继功能 。 为能更好理解DHCP Relay功能&#xff0c;建议先看看DHCP Server的内容 https://blog.csdn.net/weixin_58574637…

【Java框架】SpringMVC(三)——异常处理,拦截器,文件上传,SSM整合

目录 异常处理解释局部异常处理全局异常 拦截器拦截器介绍作用:拦截器和过滤器之间的区别拦截器执行流程代码实现补充 文件上传依赖配置MultipartResolver编写文件上传表单页APIMultipartFileFile.separator必须对上传文件进行重命名代码示例 SpringMVC文件上传流程多文件上传 …

你知道吗?PCBA产品上市前还需要进行老化测试?

在PCBA加工过程中&#xff0c;电子工程师可能发现明明PCBA板出货时各项功能指标正常&#xff0c;但使用一段时间&#xff0c;就莫名其妙出现各种不良问题&#xff0c;最后返场维修&#xff0c;那么这是为什么&#xff1f; 首先&#xff0c;这些原因&#xff0c;十有八九可能是P…

谷歌开发者账号关联被封?解封申诉指南来了!

相信大部分在谷歌上架应用的开发者&#xff0c;已经被谷歌封号封麻了。且不少开发者普遍认为&#xff0c;一旦开发者账号被封禁&#xff0c;想要成功申诉回来的难度极大。 特别是那些因为涉及“高风险”滥用模式行为关联被封的账号&#xff0c;更是不可能申诉成功。但事实上&am…

再谈C语言——理解指针(五)(完结篇)

数组名的理解 在上⼀个章节我们在使⽤指针访问数组的内容时&#xff0c;有这样的代码&#xff1a; int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[0]; 这⾥我们使⽤ &arr[0] 的⽅式拿到了数组第⼀个元素的地址&#xff0c;但是其实数组名本来就是地址&#xf…

【产品经理修炼之道】- 将用户需求转化为研发需求

每当接到一个用户需求&#xff0c;产品经理头疼的事情之一&#xff0c;可能就是如何把用户需求转化成研发需求。怎么理解二者的区别&#xff0c;并成功地将需求转化呢&#xff1f;这篇文章里&#xff0c;作者做了分享与总结&#xff0c;一起来看一下。 产品经理与研发人员的博弈…

【图像分割】光流生成标签(matlab)

文章目录 1. 框架2. opticalFlow_label3. 光流 1. 框架 2. opticalFlow_label close all; clear; clc; % 使用光流进行标签的生成 %% 视频帧的读取 npy_data readNPY(train.npy);%% 提取标签的坐标 first_label squeeze(npy_data(2,1,:,:)); h fspecial("gaussian&quo…

C语言自定义类型【结构体】

结构体的概念 结构是一些值的集合&#xff0c;这些值被称为成员变量。结构的每个成员可以是不同类型的变量。 1.结构体的声明 1.1普通声明 我们假设要创建一本书的类型&#xff0c;那我们需要书名&#xff0c;作者&#xff0c;价格&#xff0c;书的ID 代码如下&#xff1a;…

第十五届蓝桥杯省赛第二场C/C++B组A题【进制】题解(AC)

解题思路 按照题意进行模拟&#xff0c;计算 x x x 的 b b b 进制过程中&#xff0c;若出现余数大于 9 9 9&#xff0c;则说明 x x x 的 b b b 进制一定要用字母进行表示。 #include <iostream> #include <cstring> #include <algorithm> #include &l…

vue3推荐算法

Vue 3 推荐算法主要指的是在 Vue 3 框架中实现的或者适用于 Vue 3 的算法库或组件库。Vue 3 由于其优秀的设计和性能&#xff0c;被广泛应用于构建各种类型的应用程序&#xff0c;包括需要复杂算法支持的项目。以下是一些在 Vue 3 中可能会用到的推荐算法资源&#xff1a; Vue-…

啊? 又要洗数据啦!! 还是两个key决定一个表! 二维Map学习,基于guava的HashBasedTable

一个洗数据的需求&#xff0c;表设计的外建不能判断某一个数据源&#xff0c;还要根据tyoe来进行判断才可以。 那此时呆逼的查发能实现但不够优雅&#xff0c;于是乎想到了二维数组&#xff0c;查了下资料有相关的实现给大家分享下&#xff01;&#xff01; 背景 表设计如下&a…

美易官方:AI热潮“熄火”了?Meta Q1财报较差

近期&#xff0c;随着Meta&#xff08;前Facebook&#xff09;发布了其2023年第一季度的财报&#xff0c;一场科技股的震荡在美股市场上演。曾经风光无限的AI热潮似乎出现了“熄火”的迹象&#xff0c;引发了市场的广泛关注和讨论。 Cresset Wealth Advisors首席投资官Jack Abl…

libVLC 专栏介绍

本专栏主要界面libVLC的使用&#xff0c;详细介绍了相关用法&#xff0c;使用Qt作为显示界面&#xff0c;不仅可以了解Qt的使用&#xff0c;QSS的美化&#xff0c;更能够熟悉libVLC核心接口的使用&#xff0c;最后打造一款属于自己的精美播放器。 每一节都有单独的源码供查看。…

CSS @media 媒体查询全解:打造极致跨平台页面的动态户体体验

随着互联网设备的多样化和用户浏览习惯的变化&#xff0c;现代网页设计越来越注重提供跨平台、跨设备的无缝用户体验。CSS媒体查询media在此背景下扮演着至关重要的角色&#xff0c;它赋予网页设计者精准控制网页样式的能力&#xff0c;使之能随设备环境变化而动态调整&#xf…

内存管理下及模板初阶

嗨喽&#xff0c;今天阿鑫给大家带来内存管理下以及模板初阶的博客&#xff0c;下面让我们开始今天的学习吧&#xff01; 内存管理下及模板初阶 new和delete的实现原理定位new表达式(placement-new)常见面试题泛型编程函数模板类模板 1. new和delete的实现原理 1.1 内置类型…

短链接推荐:一个可以监测用户行为的“营销神器”

客户对我的推广有兴趣吗&#xff1f;他喜欢我的产品吗&#xff1f;他打开了我的营销信息吗&#xff1f;这三个问题相信每一位推广者都遇到过。接下来&#xff0c;就将给大家介绍一位大聪明——它能帮你监测每一位用户的行为&#xff0c;让你分分秒秒掌握用户的心理&#xff01;…

consul服务注册与发现、服务配置与刷新

为什么要用服务注册&#xff1f;为什么要用consul不用eureka&#xff1f; 举个栗子&#xff1a; 微服务当中存在多个服务模块&#xff0c;每个服务模块的ip端口在每套环境是不一致的&#xff0c;开发切换环境部署时&#xff0c;如果漏了一个配置忘记改动&#xff0c;将是一个很…

黑龙江—等保测评三级安全设计思路

需求分析 6.1、 系统现状 6.2、 现有措施 目前&#xff0c;信息系统已经采取了下述的安全措施&#xff1a; 1、在物理层面上&#xff0c; 2、在网络层面上&#xff0c; 3、在系统层面上&#xff0c; 4、在应用层面上&#xff0c; 5、在管理层面上&#xff0c; 6.…
最新文章