你要如何衡量你的人生

坚持,努力,让好事发生

Sentinel整体架构如下
Sentinel架构
图里从下往上可以看到,核心的部分包含 规则(rules)处理插槽(slot)调用链路(invocation tree)集群节点(cluster node)滑动窗口(slading winodw) 5部分。

Sentinel代码使用 tag 1.8.6 ,对应github链接 https://github.com/alibaba/Sentinel/tree/1.8.6

(1) 核心流程源码解读

核心源码包含以下几个部分
1.规则(rules)-限流规则/熔断规则
2.构建功能插槽(solt)责任链
3.调用链路(invocation tree)
4.集群节点(cluster node)
5.滑动窗口(slading winodw)

public class SimpleDemo {

    public static void main(String[] args) {
        // 配置规则.
        initFlowRules();

        //while (true) {
            // 1.5.0 版本开始可以直接利用 try-with-resources 特性
            try (Entry entry = SphU.entry("HelloWorld")) {
                // 被保护的逻辑
                System.out.println("hello world");
            } catch (BlockException ex) {
                // 处理被流控的逻辑
                System.out.println("blocked!");
            }
        //}
    }

    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 2.
        rule.setCount(2);
        rules.add(rule);
        // 
        FlowRuleManager.loadRules(rules);
    }

}

(2) 加载限流规则

资源对应限流规则

List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("HelloWorld");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// Set limit QPS to 2.
rule.setCount(2);
rules.add(rule);
// 
FlowRuleManager.loadRules(rules);

(3) 构建功能插槽(ProcessSolt)责任链

这块对应的代码是 SlotChainProvider.newSlotChain();
在构建功能插槽的时候使用责任链设计模式和使用SPI提高扩展性

构建完的责任链类似这种
功能插槽责任链

整体上的代码如下

package com.alibaba.csp.sentinel.slotchain;

/**
 * A provider for creating slot chains via resolved slot chain builder SPI.
 */
public final class SlotChainProvider {

    private static volatile SlotChainBuilder slotChainBuilder = null;

    /**
     * The load and pick process is not thread-safe, but it's okay since the method should be only invoked
     * via {@code lookProcessChain} in {@link com.alibaba.csp.sentinel.CtSph} under lock.
     *
     * @return new created slot chain
     */
    public static ProcessorSlotChain newSlotChain() {
        if (slotChainBuilder != null) {
            return slotChainBuilder.build();
        }

        // 使用SPI构建插槽实例 
        // 这块通过SPI读取配置文件加载类  
        // 读取的配置文件 META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder
        // slotChainBuilder=com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder
        // Resolve the slot chain builder SPI.
        slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();

        if (slotChainBuilder == null) {
            // 不应该走到这儿  走到这儿肯定有问题
            RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
            slotChainBuilder = new DefaultSlotChainBuilder();
        } else {
            RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}",
                slotChainBuilder.getClass().getCanonicalName());
        }

        // 构建功能插槽责任链
        return slotChainBuilder.build();
    }
}

这儿比较重要的方法有两个,一个是 SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault(),另一个是 slotChainBuilder.build()

(3.1) 构造默认的slotChainBuilder

package com.alibaba.csp.sentinel.spi;

public final class SpiLoader<S> {

    /**
     * Load the first-found Provider instance,if not found, return default Provider instance
     *
     * @return Provider instance
     */
    public S loadFirstInstanceOrDefault() {
        // 使用SPI读取配置文件获取全限定类目,并通过ClassLoader加载class
        load();

        for (Class<? extends S> clazz : classList) {
            if (defaultClass == null || clazz != defaultClass) {
                return createInstance(clazz);
            }
        }

        // 实例化  newInstance 
        return loadDefaultInstance();
    }

}

(3.2) 构造ProcessorSlot责任链

package com.alibaba.csp.sentinel.slots;

/**
 * 默认 ProcessorSlotChain 的构建器  
 *  
 * 
 * Builder for a default {@link ProcessorSlotChain}.
 *
 */
@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build() {
        // 创建默认的 DefaultProcessorSlotChain 
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();

        // 使用SPI读取配置,并通过ClassLoader加载class 
        // 读取的配置文件 META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot
        // 这儿会读取到8个 ProcessorSlot
        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
        for (ProcessorSlot slot : sortedSlotList) {
            if (!(slot instanceof AbstractLinkedProcessorSlot)) {
                RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
                continue;
            }
            
            // 按照字典序设置ProcessorSlot责任链
            chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }

        return chain;
    }
}

META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot文件内容如下

# Sentinel default ProcessorSlots
com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot
com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot
com.alibaba.csp.sentinel.slots.logger.LogSlot
com.alibaba.csp.sentinel.slots.statistic.StatisticSlot
com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot
com.alibaba.csp.sentinel.slots.system.SystemSlot
com.alibaba.csp.sentinel.slots.block.flow.FlowSlot
com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot

可以仔细看一下,文件里的内容已经按照字典序排序了

构建完成的功能插槽责任链类似下面这样:
功能插槽责任链抽象

对应的代码Debug截图:
构建完成的功能插槽责任链

(3.2.1) 默认功能插槽责任链-DefaultProcessorSlotChain

package com.alibaba.csp.sentinel.slotchain;

public class DefaultProcessorSlotChain extends ProcessorSlotChain {

    // 头结点 first
    AbstractLinkedProcessorSlot<?> first = new AbstractLinkedProcessorSlot<Object>() {
        // 省略部分代码
    };

    // 尾结点 end
    AbstractLinkedProcessorSlot<?> end = first;

    /** 添加头节点 */
    @Override
    public void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor) {
        protocolProcessor.setNext(first.getNext());
        first.setNext(protocolProcessor);
        if (end == first) {
            end = protocolProcessor;
        }
    }

    /** 添加尾结点 */
    @Override
    public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {
        end.setNext(protocolProcessor);
        end = protocolProcessor;
    }


    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
        throws Throwable {
        first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        first.exit(context, resourceWrapper, count, args);
    }

}

(3.3) 功能插槽责任链-ProcessorSlot

执行各个功能插槽的入口是chain.entry()
这里的chain是DefaultProcessorSlotChainchain.entry()会不断调用对应的entry()方法

先来了解一下 有哪些功能插槽 及 主要作用

(3.3.1) Sentinel里的功能插槽

处理插槽 作用 备注 全限定类名
NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来 用于根据调用路径来限流降级 com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot
ClusterBuilderSlot 负责维护资源运行统计信息(响应时间、qps、线程数、异常),以及调用者列表 这些信息将用作为多维度限流,降级的依据 com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot
LogSlot 记录(BlockException)异常日志 (限流、熔断) com.alibaba.csp.sentinel.slots.logger.LogSlot
StatisticSlot 用于记录、统计不同纬度的 runtime 指标监控信息; com.alibaba.csp.sentinel.slots.statistic.StatisticSlot
AuthoritySlot 根据配置的黑白名单和调用来源信息,来做黑白名单控制; com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot
SystemSlot 通过系统的状态,例如 load1 等,来控制总的入口流量; com.alibaba.csp.sentinel.slots.system.SystemSlot
FlowSlot 用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制; com.alibaba.csp.sentinel.slots.block.flow.FlowSlot
DegradeSlot 通过统计信息以及预设的规则,来做熔断降级; com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot

(3.3.2) 功能插槽-ProcessorSlot

源码 https://github.com/alibaba/Sentinel/blob/1.8.6/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/ProcessorSlot.java

package com.alibaba.csp.sentinel.slotchain;

/**
 * 一些处理的容器 和 处理完成时的通知方式。 
 */
public interface ProcessorSlot<T> {

    /**
     * 插槽入口
     *
     * @param context         当前上下文
     * @param resourceWrapper 当前资源
     * @param param           泛型参数 {@link com.alibaba.csp.sentinel.node.Node}
     * @param count           需要的令牌个数
     * @param prioritized     entry优先级
     * @param args            原始调用的参数
     * @throws Throwable blocked exception or unexpected error
     */
    void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, boolean prioritized,
               Object... args) throws Throwable;

    /**
     * 表示entry()方法结束
     *
     * @param context         当前上下文
     * @param resourceWrapper 当前资源
     * @param obj             相关对象 (e.g. Node)
     * @param count           需要的令牌个数
     * @param prioritized     entry优先级
     * @param args            原始调用的参数
     * @throws Throwable blocked exception or unexpected error
     */
    void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized,
                   Object... args) throws Throwable;

    /**
     * 退出插槽
     *
     * @param context         当前上下文
     * @param resourceWrapper 当前资源
     * @param count           需要的令牌个数
     * @param args            原始调用的参数
     */
    void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);

    /**
     * 表示exit结束
     *
     * @param context         当前上下文
     * @param resourceWrapper 当前资源
     * @param count           需要的令牌个数
     * @param args            原始调用的参数
     */
    void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
}

调用功能插槽时都是从entry方法进入

(3.4) 默认的8个功能插槽

(3.4.1) 调用链路(资源路径)插槽-NodeSelectorSlot

NodeSelectorSlot主要作用是负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;

源码:https://github.com/alibaba/Sentinel/blob/1.8.6/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java#L136

package com.alibaba.csp.sentinel.slots.nodeselector;

/**
 * </p>
 * This class will try to build the calling traces via
 * <ol>
 * <li>adding a new {@link DefaultNode} if needed as the last child in the context.
 * The context's last node is the current node or the parent node of the context. </li>
 * <li>setting itself to the context current node.</li>
 * </ol>
 * </p>
 *
 * <p>It works as follow:</p>
 * <pre>
 * ContextUtil.enter("entrance1", "appA");
 * Entry nodeA = SphU.entry("nodeA");
 * if (nodeA != null) {
 *     nodeA.exit();
 * }
 * ContextUtil.exit();
 * </pre>
 *
 * Above code will generate the following invocation structure in memory:
 *
 * <pre>
 *
 *              machine-root
 *                  /
 *                 /
 *           EntranceNode1
 *               /
 *              /
 *        DefaultNode(nodeA)- - - - - -> ClusterNode(nodeA);
 * </pre>
 *
 * <p>
 * Here the {@link EntranceNode} represents "entrance1" given by
 * {@code ContextUtil.enter("entrance1", "appA")}.
 * </p>
 * <p>
 * Both DefaultNode(nodeA) and ClusterNode(nodeA) holds statistics of "nodeA", which is given
 * by {@code SphU.entry("nodeA")}
 * </p>
 * <p>
 * The {@link ClusterNode} is uniquely identified by the ResourceId; the {@link DefaultNode}
 * is identified by both the resource id and {@link Context}. In other words, one resource
 * id will generate multiple {@link DefaultNode} for each distinct context, but only one
 * {@link ClusterNode}.
 * </p>
 * <p>
 * the following code shows one resource id in two different context:
 * </p>
 *
 * <pre>
 *    ContextUtil.enter("entrance1", "appA");
 *    Entry nodeA = SphU.entry("nodeA");
 *    if (nodeA != null) {
 *        nodeA.exit();
 *    }
 *    ContextUtil.exit();
 *
 *    ContextUtil.enter("entrance2", "appA");
 *    nodeA = SphU.entry("nodeA");
 *    if (nodeA != null) {
 *        nodeA.exit();
 *    }
 *    ContextUtil.exit();
 * </pre>
 *
 * Above code will generate the following invocation structure in memory:
 *
 * <pre>
 *
 *                  machine-root
 *                  /         \
 *                 /           \
 *         EntranceNode1   EntranceNode2
 *               /               \
 *              /                 \
 *      DefaultNode(nodeA)   DefaultNode(nodeA)
 *             |                    |
 *             +- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);
 * </pre>
 *
 * <p>
 * As we can see, two {@link DefaultNode} are created for "nodeA" in two context, but only one
 * {@link ClusterNode} is created.
 * </p>
 *
 * <p>
 * We can also check this structure by calling: <br/>
 * {@code curl http://localhost:8719/tree?type=root}
 * </p>
 *
 * @author jialiang.linjl
 * @see EntranceNode
 * @see ContextUtil
 */
@Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT)
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {

    /**
     * {@link DefaultNode}s of the same resource in different context.
     */
    private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
        throws Throwable {
        /*
         * It's interesting that we use context name rather resource name as the map key.
         *
         * Remember that same resource({@link ResourceWrapper#equals(Object)}) will share
         * the same {@link ProcessorSlotChain} globally, no matter in which context. So if
         * code goes into {@link #entry(Context, ResourceWrapper, DefaultNode, int, Object...)},
         * the resource name must be same but context name may not.
         *
         * If we use {@link com.alibaba.csp.sentinel.SphU#entry(String resource)} to
         * enter same resource in different context, using context name as map key can
         * distinguish the same resource. In this case, multiple {@link DefaultNode}s will be created
         * of the same resource name, for every distinct context (different context name) each.
         *
         * Consider another question. One resource may have multiple {@link DefaultNode},
         * so what is the fastest way to get total statistics of the same resource?
         * The answer is all {@link DefaultNode}s with same resource name share one
         * {@link ClusterNode}. See {@link ClusterBuilderSlot} for detail.
         */
        // 根据上下文名称获取节点 
        DefaultNode node = map.get(context.getName());
        // 单例模式-DCL
        if (node == null) {
            synchronized (this) {
                node = map.get(context.getName());
                if (node == null) {
                    node = new DefaultNode(resourceWrapper, null);
                    HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
                    cacheMap.putAll(map);
                    cacheMap.put(context.getName(), node);
                    map = cacheMap;
                    // 构建调用树
                    ((DefaultNode) context.getLastNode()).addChild(node);
                }

            }
        }

        context.setCurNode(node);
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

}    
package com.alibaba.csp.sentinel.node;


public class DefaultNode extends StatisticNode {

    /**
     * 孩子节点集合
     */
    private volatile Set<Node> childList = new HashSet<>();

    /**
     * 添加孩子节点
     *
     * @param node valid child node
     */
    public void addChild(Node node) {
        if (node == null) {
            RecordLog.warn("Trying to add null child to node <{}>, ignored", id.getName());
            return;
        }
        // DCL
        if (!childList.contains(node)) {
            synchronized (this) {
                if (!childList.contains(node)) {
                    // 添加孩子节点
                    Set<Node> newSet = new HashSet<>(childList.size() + 1);
                    newSet.addAll(childList);
                    newSet.add(node);
                    childList = newSet;
                }
            }
            RecordLog.info("Add child <{}> to node <{}>", ((DefaultNode)node).id.getName(), id.getName());
        }
    }

}
/**
 * <pre>
 *
 *                  machine-root
 *                  /         \
 *                 /           \
 *         EntranceNode1   EntranceNode2
 *               /               \
 *              /                 \
 *      DefaultNode(nodeA)   DefaultNode(nodeA)
 *             |                    |
 *             +- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);
 * </pre>
 */

(3.4.2) 节点统计信息插槽-ClusterBuilderSlot

ClusterBuilderSlot负责维护资源运行统计信息(响应时间、qps、线程数、异常),以及调用者列表

https://github.com/alibaba/Sentinel/blob/1.8.6/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java#L77

package com.alibaba.csp.sentinel.slots.clusterbuilder;
 
/**
 * 该槽维护资源运行统计信息(响应时间、qps、线程数、异常),以及调用者列表,由 ContextUtil#enter(String origin)标记 
 * 一个资源只有一个集群节点,而一个资源可以有多个默认节点。
 */
@Spi(isSingleton = false, order = Constants.ORDER_CLUSTER_BUILDER_SLOT)
public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    /**
     * 相同的资源 ResourceWrapper#equals(Object) 将在全局范围内共享相同的ProcessorSlotChain,无论在哪个上下文中。
     * 因此,如果代码进入 entry(),资源名称必须相同,但上下文名称可能不同。
     * 
     * 为了获取同一资源在不同上下文中的总统计信息,同一资源在全局共享相同的ClusterNode。所有ClusterNode都缓存在此映射中。
     * 
     * 应用程序运行的时间越长,这个映射就会变得越稳定。所以我们不是并发映射而是锁。因为这个锁只发生在最开始,而并发映射将一直持有锁。
     * 
     */
    private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>();

    private static final Object lock = new Object();

    private volatile ClusterNode clusterNode = null;

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args)
        throws Throwable {
        // 单例-DCL
        if (clusterNode == null) {
            synchronized (lock) {
                if (clusterNode == null) {
                    // 创建集群节点​​。
                    clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
                    HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
                    newMap.putAll(clusterNodeMap);
                    // 添加到缓存里
                    newMap.put(node.getId(), clusterNode);
                    // 更新缓存
                    clusterNodeMap = newMap;
                }
            }
        }
        node.setClusterNode(clusterNode);

        /*
         * 如果设置了原始上下文,我们应该获取 或 创建原始直接的Node。
         */
        if (!"".equals(context.getOrigin())) {
            Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
            context.getCurEntry().setOriginNode(originNode);
        }

        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

}

(3.4.3) 日志插槽-LogSlot

LogSlot主要负责记录限流异常的响应,以提供用于故障排除的具体日志。
记录的日志存储在 sentinel-block.log 文件里

源码: https://github.com/alibaba/Sentinel/blob/1.8.6/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/logger/LogSlot.java#L35

package com.alibaba.csp.sentinel.slots.logger;

 
/**
 * 一个处理插槽,它是对记录限流异常的响应,以提供用于故障排除的具体日志。
 */
@Spi(order = Constants.ORDER_LOG_SLOT)
public class LogSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode obj, int count, boolean prioritized, Object... args)
        throws Throwable {
        try {
            fireEntry(context, resourceWrapper, obj, count, prioritized, args);
        } catch (BlockException e) {
            // 记录异常日志(包括限流和降级) 
            // 日志会记录在 sentinel-block.log 文件里
            EagleEyeLogUtil.log(resourceWrapper.getName(), e.getClass().getSimpleName(), e.getRuleLimitApp(),
                context.getOrigin(), e.getRule().getId(), count);
            throw e;
        } catch (Throwable e) {
            RecordLog.warn("Unexpected entry exception", e);
        }

    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        try {
            fireExit(context, resourceWrapper, count, args);
        } catch (Throwable e) {
            RecordLog.warn("Unexpected entry exit exception", e);
        }
    }
}

sentinel-block.log 文件内容如下:

2020-10-30 14:39:29|1|HelloWorld,FlowException,default,|46944,0
2020-10-30 14:39:30|1|HelloWorld,FlowException,default,|138183,0
2020-10-30 14:39:31|1|HelloWorld,FlowException,default,|188067,0
2020-11-25 20:12:24|1|sentinelTest,FlowException,default,|400,0
2020-11-25 20:12:25|1|sentinelTest,FlowException,default,|540,0
2020-11-25 21:00:57|1|sentinelTest,FlowException,default,|9,0
2020-11-25 21:00:58|1|sentinelTest,FlowException,default,|1,0
2020-11-25 21:00:59|1|sentinelTest,FlowException,default,|62,0
2020-11-25 21:01:00|1|sentinelTest,FlowException,default,|838,0
2020-12-10 10:39:24|1|SentinelResourceMethod1,FlowException,app1,app1|1,0
2020-12-10 10:39:25|1|SentinelResourceMethod1,FlowException,app1,app1|1,0

(3.4.4) 实时统计插槽-StatisticSlot

源码 https://github.com/alibaba/Sentinel/blob/1.8.6/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java#L55

package com.alibaba.csp.sentinel.slots.statistic;

/**
 * 专用于实时统计的处理器插槽。
 * 在进入这个槽的时候,我们需要单独统计以下信息:
 *   ClusterNode:资源ID的集群节点的总统计。
 *   源节点:来自不同调用者/源的集群节点的统计信息。 
 *   DefaultNode:特定上下文中特定资源名称的统计信息。
 *   最后是所有入口的总和统计。
 */
@Spi(order = Constants.ORDER_STATISTIC_SLOT)
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        try {
            // Do some checking.
            fireEntry(context, resourceWrapper, node, count, prioritized, args);

            // Request passed, add thread count and pass count.
            node.increaseThreadNum();
            node.addPassRequest(count);

            if (context.getCurEntry().getOriginNode() != null) {
                // Add count for origin node.
                context.getCurEntry().getOriginNode().increaseThreadNum();
                context.getCurEntry().getOriginNode().addPassRequest(count);
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseThreadNum();
                Constants.ENTRY_NODE.addPassRequest(count);
            }

            // Handle pass event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }
        } catch (PriorityWaitException ex) {
            node.increaseThreadNum();
            if (context.getCurEntry().getOriginNode() != null) {
                // Add count for origin node.
                context.getCurEntry().getOriginNode().increaseThreadNum();
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseThreadNum();
            }
            // Handle pass event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }
        } catch (BlockException e) {
            // Blocked, set block exception to current entry.
            context.getCurEntry().setBlockError(e);

            // Add block count.
            node.increaseBlockQps(count);
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().increaseBlockQps(count);
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseBlockQps(count);
            }

            // Handle block event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onBlocked(e, context, resourceWrapper, node, count, args);
            }

            throw e;
        } catch (Throwable e) {
            // Unexpected internal error, set error to current entry.
            context.getCurEntry().setError(e);

            throw e;
        }
    }

}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
    Node node = context.getCurNode();

    if (context.getCurEntry().getBlockError() == null) {
        // Calculate response time (use completeStatTime as the time of completion).
        long completeStatTime = TimeUtil.currentTimeMillis();
        context.getCurEntry().setCompleteTimestamp(completeStatTime);
        long rt = completeStatTime - context.getCurEntry().getCreateTimestamp();

        Throwable error = context.getCurEntry().getError();

        // Record response time and success count.
        recordCompleteFor(node, count, rt, error);
        recordCompleteFor(context.getCurEntry().getOriginNode(), count, rt, error);
        if (resourceWrapper.getEntryType() == EntryType.IN) {
            recordCompleteFor(Constants.ENTRY_NODE, count, rt, error);
        }
    }

    // Handle exit event with registered exit callback handlers.
    Collection<ProcessorSlotExitCallback> exitCallbacks = StatisticSlotCallbackRegistry.getExitCallbacks();
    for (ProcessorSlotExitCallback handler : exitCallbacks) {
        handler.onExit(context, resourceWrapper, count, args);
    }

    // fix bug https://github.com/alibaba/Sentinel/issues/2374
    fireExit(context, resourceWrapper, count, args);
}

(3.4.5) 权限规则检查插槽-AuthoritySlot

AuthoritySlot 的主要作用是 权限规则检查。

源码 https://github.com/alibaba/Sentinel/blob/1.8.6/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java

package com.alibaba.csp.sentinel.slots.block.authority;

/**
 * 致力于权限规则检查的一个处理插槽。
 */
@Spi(order = Constants.ORDER_AUTHORITY_SLOT)
public class AuthoritySlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
        throws Throwable {
        // 权限校验  
        checkBlackWhiteAuthority(resourceWrapper, context);
        // 
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }


    /**  */
    void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
        // 获取所有资源的权限规则
        Map<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules();

        if (authorityRules == null) {
            return;
        }

        // 获取当前资源的权限规则  
        Set<AuthorityRule> rules = authorityRules.get(resource.getName());
        if (rules == null) {
            return;
        }

        for (AuthorityRule rule : rules) {
            // 检查权限
            if (!AuthorityRuleChecker.passCheck(rule, context)) {
                // 权限校验不通过抛AuthorityException异常
                throw new AuthorityException(context.getOrigin(), rule);
            }
        }
    }

}    

(3.4.6) 系统规则检查插槽-SystemSlot

SystemSlot主要作用是系统规则检查。

源码: https://github.com/alibaba/Sentinel/blob/1.8.6/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemSlot.java#L36

package com.alibaba.csp.sentinel.slots.system;

/**
 * 致力于系统规则检查的一个处理插槽。
 */
@Spi(order = Constants.ORDER_SYSTEM_SLOT)
public class SystemSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        // 系统规则检查                
        SystemRuleManager.checkSystem(resourceWrapper, count);
        // 
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        fireExit(context, resourceWrapper, count, args);
    }

}
package com.alibaba.csp.sentinel.slots.system;

/**
 * Sentinel System Rule 使入站流量和容量满足。 它考虑了传入请求的平均 rt、qps、线程数。 
 * 它还提供了系统负载的度量,但仅在 Linux 上可用。
 *
 * rt、qps、线程数很好理解。 如果传入请求的 rt、qps、线程数超过其阈值,请求将被拒绝。
 * 但是,我们使用不同的方法来计算负载。
 *
 * 将系统视为管道,约束之间的转换导致三个不同区域(交通限制、容量限制和危险区域)具有不同性质的行为。 
 * 当运行中没有足够的请求来填充管道时,RTprop 会确定行为; 否则,系统容量占主导地位。 
 * 约束线在飞行中相交 = Capacity × RTprop。 
 * 由于管道已满,超过这一点,机载容量过剩产生了一个队列,导致RTT对机载流量的线性依赖和系统负载的增加。
 * 在危险区域,系统将停止响应。 
 * 
 * 参考 BBR 算法了解更多。
 * 
 * 注意,SystemRule仅对入站请求有效,出站流量不受SystemRule限制 
 */
public final class SystemRuleManager {

    /**
     * 将系统规则应用于资源。 只会检查入站流量。
     *
     * @param 资源
     * @throws BlockException 当超过任何系统规则的阈值时。
     */
    public static void checkSystem(ResourceWrapper resourceWrapper, int count) throws BlockException {
        if (resourceWrapper == null) {
            return;
        }
        // 确保检查开关打开。
        if (!checkSystemStatus.get()) {
            return;
        }

        // 仅用于入站流量 
        if (resourceWrapper.getEntryType() != EntryType.IN) {
            return;
        }

        // Constants.ENTRY_NODE 是 入站流量的全局统计节点。

        // total qps
        double currentQps = Constants.ENTRY_NODE.passQps();
        if (currentQps + count > qps) {
            throw new SystemBlockException(resourceWrapper.getName(), "qps");
        }

        // total thread
        int currentThread = Constants.ENTRY_NODE.curThreadNum();
        if (currentThread > maxThread) {
            throw new SystemBlockException(resourceWrapper.getName(), "thread");
        }

        // 延时
        double rt = Constants.ENTRY_NODE.avgRt();
        if (rt > maxRt) {
            throw new SystemBlockException(resourceWrapper.getName(), "rt");
        }

        // 负载  BBR algorithm.
        if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
            if (!checkBbr(currentThread)) {
                throw new SystemBlockException(resourceWrapper.getName(), "load");
            }
        }

        // cpu usage
        if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
            throw new SystemBlockException(resourceWrapper.getName(), "cpu");
        }
    }

}

(3.4.7) 插槽-FlowSlot

package com.alibaba.csp.sentinel.slots.block.flow;

/**
 * 结合从前面的插槽(NodeSelectorSlot、ClusterNodeBuilderSlot 和 StatisticSlot)收集的
 * 运行时统计信息,FlowSlot 将使用预设规则来决定是否应阻止传入请求。
 *
 * 如果触发任何规则,SphU.entry(resourceName) 将抛出 FlowException。 
 * 用户可以通过捕获 FlowException 来自定义自己的逻辑。
 *
 * 一个资源可以有多个流规则。 FlowSlot 遍历这些规则,直到其中一条被触发或者所有规则都被遍历。
 *
 * 每个FlowRule主要由这些因素组成:等级、策略、路径。 我们可以结合这些因素来达到不同的效果。
 * 
 * 等级由 FlowRule 中的 grade 字段定义。 
 * 此处,0 用于线程隔离,1 用于请求计数整形 (QPS)。 
 * 线程计数和请求计数都是在真实运行时收集的,
 * 我们可以通过以下命令查看这些统计信息:
 * <pre>
 * curl http://localhost:8719/tree
 *
 * idx id    thread pass  blocked   success total aRt   1m-pass   1m-block   1m-all   exception
 * 2   abc647 0      460    46          46   1    27      630       276        897      0
 * </pre>
 *
 * thread 当前正在处理资源的线程数 
 * pass 一秒内传入请求数 
 * blocked 一秒内被阻塞的请求数 
 * success 一秒内被Sentinel成功处理的请求数 
 * RT 请求在一秒内的平均响应时间 
 * total 一秒内传入请求和阻塞请求的总和 
 * 1m-pass 为一分钟内传入请求数 
 * 1m-block 为一分钟内被阻塞的请求数 
 * 1m-all 是一分钟内传入和阻止的请求总数 
 * exception 为一秒内业务(自定义)异常的计数 
 * 
 * 这个阶段通常用于保护资源不被占用。 (达到限流阈值,服务快撑不住了)
 * 如果资源需要很长时间才能完成,线程将开始占用。 响应时间越长,占用的线程越多。
 * 
 * 除了计数器之外,线程池或信号量也可以用来实现这一点。
 * - 线程池:分配一个线程池来处理这些资源。 当池中不再有空闲线程时,拒绝请求而不影响其他资源。
 * - 信号量:使用信号量来控制该资源中线程的并发数。
 * 
 * 使用线程池的好处是,超时可以优雅的走开。 但它也给我们带来了上下文切换和额外线程的成本。 
 * 如果传入请求已经在单独的线程中提供服务,例如 Servlet HTTP 请求,那么如果使用线程池,线程数几乎会翻倍。
 * 
 * 
 * 流量整形 
 * 当QPS超过阈值时,Sentinel会采取动作控制传入的请求,由流规则中的 controlBehavior 字段配置。
 *   1.立即拒绝(Immediately reject):默认行为。 超出的请求立即被拒绝 并抛出 FlowException
 *   2.热启动(Warmup) : 如果一段时间以来系统的负载很低,大量的请求来了,系统可能无法一次处理所有这些请求。 
 *        然而,如果我们稳定地增加传入请求,系统可以预热并最终能够处理所有请求。 
 *        可以通过在流规则中设置字段 warmUpPeriodSec 来配置此预热期。
 *   3.统一速率限制  此策略严格控制请求之间的间隔。换句话说,它允许请求以稳定、统一的速率传递。
 *        https://raw.githubusercontent.com/wiki/alibaba/Sentinel/image/uniform-speed-queue.png 
 *        该策略是漏桶算法的实现  https://en.wikipedia.org/wiki/Leaky_bucket  
 *        它用于以稳定的速率处理请求,通常用于突发业务(例如消息处理)。
 *        当大量超出系统容量的请求同时到达时,使用此策略的系统将处理请求及其固定速率,直到所有请求都已处理或超时。
 */
@Spi(order = Constants.ORDER_FLOW_SLOT)
public class FlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    private final FlowRuleChecker checker;

    public FlowSlot() {
        this(new FlowRuleChecker());
    }

    /**
     * Package-private for test.
     *
     * @param checker flow rule checker
     * @since 1.6.1
     */
    FlowSlot(FlowRuleChecker checker) {
        AssertUtil.notNull(checker, "flow checker should not be null");
        this.checker = checker;
    }

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        // 校验
        checkFlow(resourceWrapper, context, node, count, prioritized);

        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
        throws BlockException {
        //   
        checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        fireExit(context, resourceWrapper, count, args);
    }

    private final Function<String, Collection<FlowRule>> ruleProvider = new Function<String, Collection<FlowRule>>() {
        @Override
        public Collection<FlowRule> apply(String resource) {
            // Flow rule map should not be null.
            Map<String, List<FlowRule>> flowRules = FlowRuleManager.getFlowRuleMap();
            return flowRules.get(resource);
        }
    };
}

(3.4.8) 熔断降级插槽-DegradeSlot

package com.alibaba.csp.sentinel.slots.block.degrade;

/**
 * 专门用于熔断/电路断路 的处理插槽
 */
@Spi(order = Constants.ORDER_DEGRADE_SLOT)
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        //                 
        performChecking(context, resourceWrapper);

        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

}    
/**  */
void performChecking(Context context, ResourceWrapper r) throws BlockException {
    // 获取资源对应的熔断器/断路器
    List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
    if (circuitBreakers == null || circuitBreakers.isEmpty()) {
        return;
    }
    for (CircuitBreaker cb : circuitBreakers) {
        // 判断是否可以通过
        if (!cb.tryPass(context)) {
            // 抛出熔断异常 DegradeException 
            throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
        }
    }
}

3.5 调用堆栈

调用8个功能插槽后的代码堆栈


参考资料

[1] Sentinel介绍
[2] Sentinel工作主流程

(1) 什么是熔断

在生活中,我们经常看到家里的闸里会有保险丝,保险丝会在电流过大时熔断。

(2) 为什么需要熔断

像保险丝在电流过大时熔断保护电路一样
计算机软件系统里的熔断是为了保护系统不被过大的资源消耗拖垮

(3) 熔断的原理

断路器

(3.1) 状态机

熔断状态机

阅读全文 »

什么是限流,为什么要限流?

常见的限流算法有3类:计数器算法令牌桶算法漏桶算法

(1) 计数器算法

(1.1) 普通计数器算法

假设系统每秒能同时处理100个请求,保存一个计数器,如果开始处理一个请求,计数器加一,请求处理完毕之后计数器减一。

每次请求来的时候看看计数器的值,如果超过阈值要么拒绝。

计数器的值要是存内存中就算单机限流算法。存在分布式存储中,集群机器访问就算分布式限流算法。

缺点就是:假设我们允许的阈值是1万,此时计数器的值为0, 当1万个请求在前1秒内一股脑儿的都涌进来,这突发的流量可是顶不住的。
缓缓的增加处理和一下子涌入对于程序来说是不一样的。

(1.1) 固定窗口限流

它相比于计数限流主要是多了个时间窗口的概念。计数器每过一个时间窗口就重置。

(1.1.1) 定义

规则如下:

请求次数小于阈值,允许访问并且计数器 +1;
请求次数大于阈值,拒绝访问;
这个时间窗口过了之后,计数器清零;

(1.1.2) 问题

固定窗口临界问题
系统每秒最多接受100个请求
假设有一个恶意用户,他在09:59:800时,瞬间发送了100个请求,并且10:00:000又瞬间发送了100个请求,那么其实这个用户在1秒里面,瞬间发送了200个请求。

通过在时间窗口的重置节点处突发请求,可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。

刚才的问题其实是因为我们统计的精度太低。那么如何很好地处理这个问题呢?

(1.2) 滑动窗口算法

相对于固定窗口,滑动窗口除了需要引入计数器之外还需要记录时间窗口内每个请求到达的时间点,因此对内存的占用会比较多。

(1.2.1) 定义

将时间窗口进行划分,每过一个时间单位,时间窗口就会往右滑动一格。每一个格子都有自己独立的计数器counter

规则如下,假设时间窗口为1秒:

记录每次请求的时间
统计每次请求的时间 至 往前推1秒这个时间窗口内请求数,并且 1 秒前的数据可以删除。
统计的请求数小于阈值就记录这个请求的时间,并允许通过,反之拒绝。

滑动窗口

1s有1000ms,我们把1秒分成5个时间窗口,一个时间窗口就是200ms。
每过200ms,我们的时间窗口就会往右滑动一格。每一个格子都有自己独立的计数器counter,比如当一个请求 在09:59:800的时候到达,那么800~1000对应的counter就会加1。

(1.2.2) 短时间内的流量攻击

但是滑动窗口和固定窗口都无法解决短时间之内集中流量的突击。

我们所想的限流场景,例如每秒限制100个请求。希望请求每 10ms 来一个,这样我们的流量处理就很平滑,但是真实场景很难控制请求的频率。因此可能存在 5ms 内就打满了阈值的情况。

当然对于这种情况还是有变型处理的,例如设置多条限流规则。不仅限制每秒100个请求,再设置每10ms不超过 2 个。

(1.2.3) 滑动窗口与TCP滑动窗口

这个滑动窗口可与TCP的滑动窗口不一样。TCP的滑动窗口是接收方告知发送方自己能接多少“货”,然后发送方控制发送的速率。


(2) 令牌桶算法

所有的请求在处理之前都需要拿到一个可用的令牌才会被处理
1、所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
2、根据限流大小,设置按照一定的速率往桶里添加令牌;
3、桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
4、请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
5、令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流;

应对突发流量的时候令牌桶表现的更佳。

假设服务没预热,那是不是上线时候桶里没令牌?没令牌请求过来不就直接拒了么?这就误杀了,明明系统没啥负载现在。


(3) 漏桶算法

漏桶算法,可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。

定义

请求来了放入桶中
桶内请求量满了拒绝请求
服务定速从桶内拿请求处理

面对突发请求,漏桶算法的处理速度和平时是一样的,这其实不是我们想要的。
拿漏桶来说,漏桶中请求是暂时存在桶内的。这其实不符合互联网业务低延迟的要求。

(4) 总结

漏桶和令牌桶其实比较适合阻塞式限流场景,即没令牌我就等着,这就不会误杀了,而漏桶本就是等着。比较适合后台任务类的限流。
基于时间窗口的限流比较适合对时间敏感的场景,请求过不了您就快点儿告诉我。

参考资料

[1] 5种限流算法,7种限流方式,挡住突发流量?
[2] 图解+代码|常见限流算法以及限流在单机分布式场景下的思考
[3] 三种常见的限流算法

(1) 原理

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

假如让自己实现一个限流算法,怎么实现?

(1.1) 计数器限流算法

// 

(2) 限流配置

sentinel
限流类型(2种)
线程数限流
QPS数限流

限流策略/流控模式(3种)
直接限流、关联限流、链路限流

流控效果(3种)
直接拒绝、Ware Up、排队等待


(3) 限流源码

源码流程图 todo
时序图 todo

限流有比较重要的几个点
1.限流配置 限流配置管理
2.限流算法 限流实现
3.

(3.1) 限流配置管理-FlowRuleManager

FlowRuleManager对象主要职责是管理资源的限流配置

资源对应的流控配置保存在 Map<String, List<FlowRule>> flowRules

package com.alibaba.csp.sentinel.slots.block.flow;

/**
 * 一个资源可以有多个规则/配置。这些规则按以下顺序生效:
 *  来自指定呼叫方的请求
 *  没有指定的调用者
 */
public class FlowRuleManager {

    /** 资源和对应限流配置集合 重要 */
    private static volatile Map<String, List<FlowRule>> flowRules = new HashMap<>();

    private static final FlowPropertyListener LISTENER = new FlowPropertyListener();

    /**  */
    private static SentinelProperty<List<FlowRule>> currentProperty = new DynamicSentinelProperty<List<FlowRule>>();

    /** 
     * 定时任务线程池 
     * SCHEDULER 的 corePool size 必须设置为 1,这样两个任务startMetricTimerListener()才能由SCHEDULER有序运行
     */
    @SuppressWarnings("PMD.ThreadPoolCreationRule")
    private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1,
        new NamedThreadFactory("sentinel-metrics-record-task", true));

    static {
        currentProperty.addListener(LISTENER);
        startMetricTimerListener();
    }

}

这里用到了观察者模式,DynamicSentinelProperty是被观察者,FlowRuleManager是观察者,FlowRuleManager观察到配置变更后会通过configUpdate方法更新配置信息。

(3.1.1) 限流规则对象-FlowRule

FlowRule对象的主要用来存配置信息

package com.alibaba.csp.sentinel.slots.block.flow;

public class FlowRule extends AbstractRule {

    public FlowRule() {
        super();
        setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
    }

    /**
     * 流控类型  0:线程数限流  1:QPS限流
     * 默认使用 QPS限流
     */
    private int grade = RuleConstant.FLOW_GRADE_QPS;

    /**
     * 流量控制阈值。
     */
    private double count;

    /**
     * 限流策略
     * 基于调用链的流量控制策略。 
     *
     * {@link RuleConstant#STRATEGY_DIRECT} 直接限流 (by origin);
     * {@link RuleConstant#STRATEGY_RELATE} 关联限流 (with relevant resource);
     * {@link RuleConstant#STRATEGY_CHAIN} 链路限流 (by entrance resource).
     */
    private int strategy = RuleConstant.STRATEGY_DIRECT;

    /**
     * 具有相关资源或上下文的流量控制中的参考资源。
     */
    private String refResource;

    /**
     * 流控效果
     * 
     * 0.直接拒绝(reject directly) 
     * 1.热启动(warm up)  
     * 2. 匀速排队(rate limiter) 
     * 3. 热启动 + 匀速排队 warm up + rate limiter
     */
    private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;

    /**
     * 流控效果为预热启动时的预热时长 
     * 默认10s
     */
    private int warmUpPeriodSec = 10;

    /**
     * 流控效果为排队等待时的等待时长
     * 默认500
     */
    private int maxQueueingTimeMs = 500;

    /**
     * 集群模式
     */
    private boolean clusterMode;
    
    /**
     * 集群模式的流规则配置。
     */
    private ClusterFlowConfig clusterConfig;

    /**
     * 流量整形(节流)控制器。
     */
    private TrafficShapingController controller;

}
public abstract class AbstractRule implements Rule {

    /**
     * 规则id
     */
    private Long id;

    /**
     * 规则名称
     */
    private String resource;

    /**
     * 将受来源限制的应用程序名称。
     * 默认的limitApp是"default",即允许所有源应用。
     * 
     * 对于权限规则,多个源名称可以用逗号(',')分隔。
     */
    private String limitApp;

}

(3.2) 插槽-FlowSlot

package com.alibaba.csp.sentinel.slots.block.flow;

/**
 * 结合从前面的插槽(NodeSelectorSlot、ClusterNodeBuilderSlot 和 StatisticSlot)收集的
 * 运行时统计信息,FlowSlot 将使用预设规则来决定是否应阻止传入请求。
 *
 * 如果触发任何规则,SphU.entry(resourceName) 将抛出 FlowException。 
 * 用户可以通过捕获 FlowException 来自定义自己的逻辑。
 *
 * 一个资源可以有多个流规则。 FlowSlot 遍历这些规则,直到其中一条被触发或者所有规则都被遍历。
 *
 * 每个FlowRule主要由这些因素组成:等级、策略、路径。 我们可以结合这些因素来达到不同的效果。
 * 
 * 等级由 FlowRule 中的 grade 字段定义。 
 * 此处,0 用于线程隔离,1 用于请求计数整形 (QPS)。 
 * 线程计数和请求计数都是在真实运行时收集的,
 * 我们可以通过以下命令查看这些统计信息:
 * <pre>
 * curl http://localhost:8719/tree
 *
 * idx id    thread pass  blocked   success total aRt   1m-pass   1m-block   1m-all   exception
 * 2   abc647 0      460    46          46   1    27      630       276        897      0
 * </pre>
 *
 * thread 当前正在处理资源的线程数 
 * pass 一秒内传入请求数 
 * blocked 一秒内被阻塞的请求数 
 * success 一秒内被Sentinel成功处理的请求数 
 * RT 请求在一秒内的平均响应时间 
 * total 一秒内传入请求和阻塞请求的总和 
 * 1m-pass 为一分钟内传入请求数 
 * 1m-block 为一分钟内被阻塞的请求数 
 * 1m-all 是一分钟内传入和阻止的请求总数 
 * exception 为一秒内业务(自定义)异常的计数 
 * 
 * 这个阶段通常用于保护资源不被占用。 (达到限流阈值,服务快撑不住了)
 * 如果资源需要很长时间才能完成,线程将开始占用。 响应时间越长,占用的线程越多。
 * 
 * 除了计数器之外,线程池或信号量也可以用来实现这一点。
 * - 线程池:分配一个线程池来处理这些资源。 当池中不再有空闲线程时,拒绝请求而不影响其他资源。
 * - 信号量:使用信号量来控制该资源中线程的并发数。
 * 
 * 使用线程池的好处是,超时可以优雅的走开。 但它也给我们带来了上下文切换和额外线程的成本。 
 * 如果传入请求已经在单独的线程中提供服务,例如 Servlet HTTP 请求,那么如果使用线程池,线程数几乎会翻倍。
 * 
 * 
 * 流量整形 
 * 当QPS超过阈值时,Sentinel会采取动作控制传入的请求,由流规则中的 controlBehavior 字段配置。
 *   1.立即拒绝(Immediately reject):默认行为。 超出的请求立即被拒绝 并抛出 FlowException
 *   2.热启动(Warmup) : 如果一段时间以来系统的负载很低,大量的请求来了,系统可能无法一次处理所有这些请求。 
 *        然而,如果我们稳定地增加传入请求,系统可以预热并最终能够处理所有请求。 
 *        可以通过在流规则中设置字段 warmUpPeriodSec 来配置此预热期。
 *   3.统一速率限制  此策略严格控制请求之间的间隔。换句话说,它允许请求以稳定、统一的速率传递。
 *        https://raw.githubusercontent.com/wiki/alibaba/Sentinel/image/uniform-speed-queue.png 
 *        该策略是漏桶算法的实现  https://en.wikipedia.org/wiki/Leaky_bucket  
 *        它用于以稳定的速率处理请求,通常用于突发业务(例如消息处理)。
 *        当大量超出系统容量的请求同时到达时,使用此策略的系统将处理请求及其固定速率,直到所有请求都已处理或超时。
 */
@Spi(order = Constants.ORDER_FLOW_SLOT)
public class FlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    private final FlowRuleChecker checker;

    public FlowSlot() {
        this(new FlowRuleChecker());
    }

    /**
     * Package-private for test.
     *
     * @param checker flow rule checker
     * @since 1.6.1
     */
    FlowSlot(FlowRuleChecker checker) {
        AssertUtil.notNull(checker, "flow checker should not be null");
        this.checker = checker;
    }

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        // 校验
        checkFlow(resourceWrapper, context, node, count, prioritized);

        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    // 限流规则校验
    void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
        throws BlockException {
        //   
        checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        fireExit(context, resourceWrapper, count, args);
    }

    private final Function<String, Collection<FlowRule>> ruleProvider = new Function<String, Collection<FlowRule>>() {
        @Override
        public Collection<FlowRule> apply(String resource) {
            // Flow rule map should not be null.
            Map<String, List<FlowRule>> flowRules = FlowRuleManager.getFlowRuleMap();
            return flowRules.get(resource);
        }
    };
}

(3.2.1) 流量控制规则的规则检查-FlowRuleChecker

FlowRuleChecker的主要作用是 流量控制规则的规则检查

package com.alibaba.csp.sentinel.slots.block.flow;

/**
 * 流量控制规则的规则检查器。
 */
public class FlowRuleChecker {

    public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
                          Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
        if (ruleProvider == null || resource == null) {
            return;
        }
        // 获取资源对应的规则集合 
        Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
        if (rules != null) {
            for (FlowRule rule : rules) {
                // 是否可以通过流量控制规则检查
                if (!canPassCheck(rule, context, node, count, prioritized)) {
                    // 被限流,抛异常FlowException
                    throw new FlowException(rule.getLimitApp(), rule);
                }
            }
        }
    }

}    
/**  */
public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount,
                                                boolean prioritized) {
    String limitApp = rule.getLimitApp();
    if (limitApp == null) {
        return true;
    }

    // 是否是集群节点
    if (rule.isClusterMode()) {
        // 集群检查
        return passClusterCheck(rule, context, node, acquireCount, prioritized);
    }

    // 本地检查
    return passLocalCheck(rule, context, node, acquireCount, prioritized);
}
/**  */
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
                                      boolean prioritized) {
    // 
    Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
    if (selectedNode == null) {
        return true;
    }

    return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
}
/**  */
static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) {
    // 限流服务 默认为default
    String limitApp = rule.getLimitApp();
    // 流控策略
    int strategy = rule.getStrategy();
    // 
    String origin = context.getOrigin();

    if (limitApp.equals(origin) && filterOrigin(origin)) {
        // 流控策略 = 直接拒绝
        if (strategy == RuleConstant.STRATEGY_DIRECT) {
            // Matches limit origin, return origin statistic node.
            return context.getOriginNode();
        }
        // 
        return selectReferenceNode(rule, context, node);
    } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) { // limitApp = "default" 
        // 流控策略 = 直接拒绝
        if (strategy == RuleConstant.STRATEGY_DIRECT) {
            // 返回集群节点
            return node.getClusterNode();
        }
        // 
        return selectReferenceNode(rule, context, node);
    } else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
        && FlowRuleManager.isOtherOrigin(origin, rule.getResource())) { // limitApp = "other" && 
        // 流控策略 = 直接拒绝
        if (strategy == RuleConstant.STRATEGY_DIRECT) {
            return context.getOriginNode();
        }
        // 
        return selectReferenceNode(rule, context, node);
    }

    return null;
}
static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) {
    // 资源名
    String refResource = rule.getRefResource();
    // 流控策略
    int strategy = rule.getStrategy();

    if (StringUtil.isEmpty(refResource)) {
        return null;
    }

    // 流控策略-关联限流
    if (strategy == RuleConstant.STRATEGY_RELATE) {
        // 
        return ClusterBuilderSlot.getClusterNode(refResource);
    }

    // 流控策略-链路限流
    if (strategy == RuleConstant.STRATEGY_CHAIN) {
        if (!refResource.equals(context.getName())) {
            return null;
        }
        return node;
    }
    // No node.
    return null;
}

(3.3) 流控效果-TrafficShapingController

TrafficShapingController 实现类有3类共4个

DefaultController 快速失败流控效果实现 默认节流控制器(立即拒绝策略)。
RateLimiterController 排队等待流控效果实现
WarmUpController 冷启动流控效果实现
WarmUpRateLimiterController 冷启动排队等待流控效果实现

(3.3.1) 默认快速失败限流-DefaultController

快速失败限流器

如果

package com.alibaba.csp.sentinel.slots.block.flow.controller;

/**
 * 默认节流控制器(立即拒绝策略)。 
 */
public class DefaultController implements TrafficShapingController {

    private static final int DEFAULT_AVG_USED_TOKENS = 0;

    private double count;
    private int grade;

    public DefaultController(double count, int grade) {
        this.count = count;
        this.grade = grade;
    }
/**
 * 1.获取已经使用的令牌 (qps数或线程数)
 * 2.如果 已使用数+申请数 <= 阈值,通过
 * 3.如果 已使用数+申请数 > 阈值 
 *     判断是否允许 透支(使用下个窗口令牌)
 */
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
    // 当前令牌数  (qps数或线程数)
    int curCount = avgUsedTokens(node);
    // 判断已使用令牌数和申请的令牌数 是否大于 配置的令牌数
    if (curCount + acquireCount > count) {
        // 如果阈值类型是QPS 并且 优先获取时,允许使用下个窗口令牌/透支
        if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {
            long currentTime;
            long waitInMs;
            // 当前时间戳
            currentTime = TimeUtil.currentTimeMillis();
            // 预占用下个时间段的令牌
            // 计算下一个时间窗口需要等待的时间
            waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);
            // 如果等待时间 < 最大等待超时时间
            if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {
                node.addWaitingRequest(currentTime + waitInMs, acquireCount);
                node.addOccupiedPass(acquireCount);
                sleep(waitInMs);

                // PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.
                throw new PriorityWaitException(waitInMs);
            }
        }
        // 不通过 开始限流
        return false;
    }
    return true;
}
/** 获取令牌个数 */
private int avgUsedTokens(Node node) {
    if (node == null) {
        return DEFAULT_AVG_USED_TOKENS;
    }
    // 线程个数 或 通过QPS数
    return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps());
}

StatisticNode::tryOccupyNext

/**  */
@Override
public long tryOccupyNext(long currentTime, int acquireCount, double threshold) {
    // 
    double maxCount = threshold * IntervalProperty.INTERVAL / 1000;
    // 当前时间窗口内已经预占的令牌数
    long currentBorrow = rollingCounterInSecond.waiting();
    if (currentBorrow >= maxCount) {
        return OccupyTimeoutProperty.getOccupyTimeout();
    }

    int windowLength = IntervalProperty.INTERVAL / SampleCountProperty.SAMPLE_COUNT;
    // 桶/时间窗口开始时间
    long earliestTime = currentTime - currentTime % windowLength + windowLength - IntervalProperty.INTERVAL;

    int idx = 0;
    /*
     * Note: here {@code currentPass} may be less than it really is NOW, because time difference
     * since call rollingCounterInSecond.pass(). So in high concurrency, the following code may
     * lead more tokens be borrowed.
     */

    long currentPass = rollingCounterInSecond.pass();
    // 计算经过多少时间后可以申请到令牌
    while (earliestTime < currentTime) {
        long waitInMs = idx * windowLength + windowLength - currentTime % windowLength;
        if (waitInMs >= OccupyTimeoutProperty.getOccupyTimeout()) {
            break;
        }
        long windowPass = rollingCounterInSecond.getWindowPass(earliestTime);
        if (currentPass + currentBorrow + acquireCount - windowPass <= maxCount) {
            return waitInMs;
        }
        earliestTime += windowLength;
        currentPass -= windowPass;
        idx++;
    }

    return OccupyTimeoutProperty.getOccupyTimeout();
}

StatisticNode

@Override
public double passQps() {
    return rollingCounterInSecond.pass() / rollingCounterInSecond.getWindowIntervalInSec();
}

(4) sentinel限流规则初始化

  1. FlowRule对象用来存配置信息
  2. FlowRuleManager用来加载配置信息
  3. 后台定时任务每秒获取一次最新配置 通过 MetricTimerListener 实现
    SentinelConfig初始化配置及获取最新配置
    MetricTimerListener 定时的数据采集,然后写到log文件里去
    遍历集群节点 汇总统计的数据
    
    StatisticNode 使用滑动窗口实时统计数据

流控策略

LimitApp的作用域只在配置的流控策略为RuleConstant.STRATEGY_DIRECT(直接关联)时起作用。
其有三种配置,分别为default,origin_name,other
default 如果配置为default,表示统计不区分来源,当前资源的任何来源流量都会被统计(其实就是选择 Node 为 clusterNode 维度)
origin_name 如果配置为指定名称的 origin_name,则只会对当前配置的来源流量做统计
other 如果配置为other 则会对其他全部来源生效但不包括第二条配置的来源

当策略配置为 RuleConstant.STRATEGY_RELATE 或 RuleConstant.STRATEGY_CHAIN 时
STRATEGY_RELATE 关联其他的指定资源,如资源A想以资源B的流量状况来决定是否需要限流,这时资源A规则配置可以使用 STRATEGY_RELATE 策略
STRATEGY_CHAIN 对指定入口的流量限流,因为流量可以有多个不同的入口(EntranceNode)

流控策略校验

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

校验 FlowRuleChecker#selectNodeByRequesterAndStrategy

String limitApp = rule.getLimitApp(); // 不设置默认为default,设置了就是自己指定的

// 调用方限流-直接限流-针对特定的调用者  origin变量 不为`default` 或 `other`,并且配置的limitApp和origin变量 相等
if (limitApp.equals(origin) && filterOrigin(origin)) {
    if (strategy == RuleConstant.STRATEGY_DIRECT) {
        // Matches limit origin, return origin statistic node.
        return context.getOriginNode();
    }

    return selectReferenceNode(rule, context, node);
}
// 调用方限流-直接限流-默认
if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
    if (strategy == RuleConstant.STRATEGY_DIRECT) {
        // Return the cluster node.
        return node.getClusterNode();
    }

    return selectReferenceNode(rule, context, node);
}
// 调用方限流-直接限流-其它
if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
    && FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {
    if (strategy == RuleConstant.STRATEGY_DIRECT) {
        return context.getOriginNode();
    }

    return selectReferenceNode(rule, context, node);
}
// 
static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) {
    String refResource = rule.getRefResource();
    int strategy = rule.getStrategy();

    if (StringUtil.isEmpty(refResource)) {
        return null;
    }

    if (strategy == RuleConstant.STRATEGY_RELATE) {
        return ClusterBuilderSlot.getClusterNode(refResource);
    }

    if (strategy == RuleConstant.STRATEGY_CHAIN) {
        if (!refResource.equals(context.getName())) {
            return null;
        }
        return node;
    }
    // No node.
    return null;
}

流控效果 流量控制实现

// 加载流控规则
FlowRuleManager.loadRules(rules);

com.alibaba.csp.sentinel.slots.block.flowFlowRuleUtil::buildFlowRuleMap

/**
 * Build the flow rule map from raw list of flow rules, grouping by provided group function.
 *
 * @param list          raw list of flow rules
 * @param groupFunction grouping function of the map (by key)
 * @param filter        rule filter
 * @param shouldSort    whether the rules should be sorted
 * @param <K>           type of key
 * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules
 */
public static <K> Map<K, List<FlowRule>> buildFlowRuleMap(List<FlowRule> list, Function<FlowRule, K> groupFunction,
                                                          Predicate<FlowRule> filter, boolean shouldSort) {
    // 存储所有的流控规则
    Map<K, List<FlowRule>> newRuleMap = new ConcurrentHashMap<>();
    if (list == null || list.isEmpty()) {
        return newRuleMap;
    }
    Map<K, Set<FlowRule>> tmpMap = new ConcurrentHashMap<>();

    // 循环加载流控规则 
    for (FlowRule rule : list) {
        if (!isValidRule(rule)) {
            RecordLog.warn("[FlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule);
            continue;
        }
        if (filter != null && !filter.test(rule)) {
            continue;
        }
        if (StringUtil.isBlank(rule.getLimitApp())) {
            rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
        }

        // 获取流控规则的阈值
        TrafficShapingController rater = generateRater(rule);
        rule.setRater(rater);

        K key = groupFunction.apply(rule);
        if (key == null) {
            continue;
        }
        Set<FlowRule> flowRules = tmpMap.get(key);

        if (flowRules == null) {
            // Use hash set here to remove duplicate rules.
            flowRules = new HashSet<>();
            tmpMap.put(key, flowRules);
        }

        flowRules.add(rule);
    }
    Comparator<FlowRule> comparator = new FlowRuleComparator();
    for (Entry<K, Set<FlowRule>> entries : tmpMap.entrySet()) {
        List<FlowRule> rules = new ArrayList<>(entries.getValue());
        if (shouldSort) {
            // Sort the rules.
            Collections.sort(rules, comparator);
        }
        newRuleMap.put(entries.getKey(), rules);
    }

    return newRuleMap;
}
// 0. 直接拒绝(reject directly), 
// 1. 热启动(warm up), 
// 2. 匀速排队 rate limiter, 
// 3. 热启动 + 匀速排队 warm up + rate limiter
private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) {
    // 是否是QPS限流
    if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
        switch (rule.getControlBehavior()) {
            case RuleConstant.CONTROL_BEHAVIOR_WARM_UP:
                // 1  热启动 warm up   缓慢放量
                return new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(),
                    ColdFactorProperty.coldFactor);
            case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER:
                // 3 热启动 + 匀速排队 warm up + rate limiter
                return new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount());
            case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER:
                // 2 匀速排队 rate limiter
                return new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(),
                    rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor);
            case RuleConstant.CONTROL_BEHAVIOR_DEFAULT:
                // 直接拒绝
            default:
                // Default mode or unknown mode: default traffic shaping controller (fast-reject).
        }
    }
    // 直接拒绝 
    return new DefaultController(rule.getCount(), rule.getGrade());
}

参考资料

[1] Sentinel源码分析—FlowRuleManager加载规则做了什么?
[2] Sentinel源码分析—Sentinel是如何进行流量统计的?
[3] Sentinel源码分析— QPS流量控制是如何实现的?
[4] Sentinel源码解析四(流控策略和流控效果)
[5] sentinel 滑动窗口 限流

(1) 什么是Sentinel

Sentinel介绍

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。
Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。

Sentinel架构

阅读全文 »

JVM调优前一定要只要遇到什么问题、优化后的目标。否则投入多收获少。得不偿失。

2020-11-17T04:59:16.623+0800: 5.285: [GC (Allocation Failure) 2020-11-17T04:59:16.624+0800: 5.285: [ParNew2020-11-17T04:59:16.648+0800: 5.309: [SoftReference, 0 refs, 0.0002508 secs]2020-11-17T04:59:16.648+0800: 5.309: [WeakReference, 332 refs, 0.0001633 secs]2020-11-17T04:59:16.648+0800: 5.310: [FinalReference, 5452 refs, 0.0057402 secs]2020-11-17T04:59:16.654+0800: 5.315: [PhantomReference, 0 refs, 0 refs, 0.0002858 secs]2020-11-17T04:59:16.654+0800: 5.316: [JNI Weak Reference, 0.0000681 secs]
Desired survivor size 107347968 bytes, new threshold 6 (max 6)
- age   1:   17515352 bytes,   17515352 total
: 1677824K->17187K(1887488K), 0.0309183 secs] 1677824K->17187K(6081792K), 0.0310078 secs] [Times: user=0.14 sys=0.02, real=0.03 secs]
2020-11-17T04:59:17.966+0800: 6.628: [CMS-concurrent-abortable-preclean: 1.144/1.845 secs] [Times: user=4.88 sys=0.32, real=1.84 secs]
2020-11-17T04:59:17.966+0800: 6.628: [GC (CMS Final Remark) [YG occupancy: 925231 K (1887488 K)]2020-11-17T04:59:17.967+0800: 6.628: [Rescan (parallel) , 0.0808829 secs]2020-11-17T04:59:18.047+0800: 6.709: [weak refs processing2020-11-17T04:59:18.047+0800: 6.709: [SoftReference, 0 refs, 0.0002601 secs]2020-11-17T04:59:18.048+0800: 6.709: [WeakReference, 0 refs, 0.0001719 secs]2020-11-17T04:59:18.048+0800: 6.709: [FinalReference, 0 refs, 0.0001500 secs]2020-11-17T04:59:18.048+0800: 6.709: [PhantomReference, 0 refs, 0 refs, 0.0002673 secs]2020-11-17T04:59:18.048+0800: 6.710: [JNI Weak Reference, 0.0000138 secs], 0.0009146 secs]2020-11-17T04:59:18.048+0800: 6.710: [class unloading, 0.0111246 secs]2020-11-17T04:59:18.059+0800: 6.721: [scrub symbol table, 0.0063213 secs]2020-11-17T04:59:18.066+0800: 6.727: [scrub string table, 0.0006271 secs][1 CMS-remark: 0K(4194304K)] 925231K(6081792K), 0.1009594 secs] [Times: user=0.33 sys=0.01, real=0.10 secs]
2020-11-17T04:59:18.068+0800: 6.729: [CMS-concurrent-sweep-start]
2020-11-17T04:59:18.068+0800: 6.729: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2020-11-17T04:59:18.068+0800: 6.729: [CMS-concurrent-reset-start]
2020-11-17T04:59:18.165+0800: 6.827: [CMS-concurrent-reset: 0.098/0.098 secs] [Times: user=0.11 sys=0.08, real=0.10 secs]

GC (Allocation Failure)
Concurrent Abortable Preclean
CMS-concurrent-abortable-preclean

References

[]
[]
[] JVM调优实战:解决CMS concurrent-abortable-preclean LongGC的问题

如果一台机器上有10w个定时任务,如何做到高效触发?

rpc调用时超时怎么实现?

订单超时未支付变更订单状态怎么实现?

阅读全文 »

Hystrix是一个延迟和容错库,旨在隔离对远程系统,服务和第三方库的访问点,停止级联故障,并在不可避免发生故障的复杂分布式系统中实现弹性。

Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.

阅读全文 »

 泛型主要解决了代码的复用问题。 提高了代码的抽象性。

(1) 为什么会引入泛型

 泛型的意义:适用于多种数据类型执行相同的代码(代码复用)

 通过一个例子来阐述,先看下下面的代码:

public static int add(byte x, byte y) {
    return x + y;
}

public static int add(short x, short y) {
    return x + y;
}


public static int add(int x, int y) {
    return x + y;
}

 如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法。

 通过泛型,我们可以复用为一个方法:

private static <T extends Number> int add(T x, T y) {
    return x.intValue() + y.intValue();
}
阅读全文 »

什么是Java里的引用

引用可以理解为指针

有什么用

不同引用不同用途
强引用
软引用
弱引用
虚引用

阅读全文 »
0%