>>>> 😜😜😜 Github: 👉 github.com/black-ant CASE backup: 👉 gitee.com/antblack/ca…

A. The preface

Purpose of the article

  • Sort out how Sentinel is integrated
  • Into the integration principles of Sentinel

Use the tutorial

There are two steps to use: build the rules and transport entities

2.1 Construction Rules

public void buildRule(String resourceName) {

    List<FlowRule> rules = new ArrayList<>();
    // Prepare traffic rule objects
    FlowRule rule = new FlowRule();

    // 设置 Resource 的 ID -> SphU.entry("HelloWorld")
    rule.setResource(resourceName);

    // The number of streams has been limited by QPS
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(20);

    // Add and load the Rule
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}
Copy the code

2.2 Usage Process

public String flowControlSync(Integer qpsNum) {
    logger.info("------> [initialize Sentinel Rule] <-------");
    logger.info("-- -- -- -- -- - > [Step 1: launch a business process, through the Sentinel API] < -- -- -- -- -- -- --");

    for (int i = 0; i < qpsNum; i++) {
        Entry entry = null;

        try {
            // The resource name can be any string with business semantics
            SphU.asyncEntry("FlowControlSync");
            logger.info("------> [Enter Flow Control service logic :{}] <-------", i);
        } catch (BlockException e) {
            logger.error("E----> error :{} -- content :{}", e.getClass(), e.getMessage());
        } finally {
            if(entry ! =null) { entry.exit(); }}}return "success";
}
Copy the code

Three. Deep into the source code

3.1 FlowRuleManager Loads rules

In 2.1, we built a Rule using FlowRuleManager:

  1. Create a FlowRule object
  2. Set resources for the Rule
  3. Set a traffic limiting policy
  4. FlowRuleManager loadRules loading rules

Here’s a closer look at the processing logic for Rules:

Step 1: Load resources, as you can see here, into the SentinelProperty

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

public static void loadRules(List<FlowRule> rules) {
    currentProperty.updateValue(rules);
}

// Add.1: SentinelProperty object- This object is an interface that holds the current value of the configuration and is responsible for notifying all propertyListeners added to the configuration when the configuration is updated - Yes2Implementation class: DynamicSentinelProperty/NoOpSentinelProperty (null implementation)Copy the code

Step 2: PropertyListener listens for configuration changes

When the configuration is complete, the PropertyListener will be notified that the PropertyListener configuration has changed. Take a look at the PropertyListener system first

private static final Map<String, List<FlowRule>> flowRules = new ConcurrentHashMap<String, List<FlowRule>>();


C- FlowPropertyListener
public void configUpdate(List<FlowRule> value) {
    Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(value);
    if(rules ! =null) {
        // Empty first, then add, so repeat here is invalidflowRules.clear(); flowRules.putAll(rules); }}Copy the code

The final result is the following:

Now that the configuration part is complete, let’s look at the intercepting part >>>

3.2 Execution Process

There are two main objects in this section: Entry + SphU

Step 1: Initiate a processing request

// SphU.entry("FlowControl")
public static Entry entry(String name) throws BlockException {
    return Env.sph.entry(name, EntryType.OUT, 1, OBJECTS0);
}
Copy the code

Step 2: Make logical decisions

private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
    throws BlockException {
    Context context = ContextUtil.getContext();
    // NullContext indicates that the number of NullContext has exceeded the threshold,
    if (context instanceof NullContext) {
        // If the operation method is used, only one entry is initialized and Rule verification is not performed
        return new CtEntry(resourceWrapper, null, context);
    }

    if (context == null) {
        // If context is empty, the default context is used
        context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
    }

    // The global switch is off, and the rule check is not performed
    if(! Constants.ON) {return new CtEntry(resourceWrapper, null, context);
    }
    
    // Get the Slot column chain
    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

    // If the chain is null, the number of resources (slot chain) exceeds the constant
    if (chain == null) {
        return new CtEntry(resourceWrapper, null, context);
    }

    Entry e = new CtEntry(resourceWrapper, chain, context);
    try {
        // Execute the list
        chain.entry(context, resourceWrapper, null, count, prioritized, args);
    } catch (BlockException e1) {
        e.exit(count, args);
        throw e1;
    } catch (Throwable e1) {
        // This should not happen, unless there are errors existing in Sentinel internal.
        RecordLog.info("Sentinel unexpected exception", e1);
    }
    return e;
}



Copy the code

Supplement: resourceWrapper

Step 2-1: Initialize the Context

protected static Context trueEnter(String name, String origin) {
    Context context = contextHolder.get();
    if (context == null) {
        // private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
        Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
        DefaultNode node = localCacheNameMap.get(name);
        if (node == null) {
            // The node is null and cannot exceed the maximum volume -> PRO21001
            if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                setNullContext();
                return NULL_CONTEXT;
            } else {
                try {
                    LOCK.lock();
                    // This is similar to singletons
                    node = contextNameNodeMap.get(name);
                    if (node == null) {
                        // MAX_CONTEXT_NAME_SIZE = 2000
                        if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                            setNullContext();
                            return NULL_CONTEXT;
                        } else {
                            // PRO21002: EntranceNode
                            node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                            // Add entrance node.
                            Constants.ROOT.addChild(node);
                            / / build Node
                            Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1); newMap.putAll(contextNameNodeMap); newMap.put(name, node); contextNameNodeMap = newMap; }}}finally {
                    LOCK.unlock();
                }
            }
        }
        context = new Context(node, name);
        context.setOrigin(origin);
        contextHolder.set(context);
    }

    return context;
}

// Question PRO21001: Why is there a concept of maximum volume?


/ / PRO21002 problem
public class DefaultNode extends StatisticNode {

    // The resource associated with the node
    private ResourceWrapper id;
    // A collection of child nodes, where nodes hold uniqueness
    private volatile Set<Node> childList = new HashSet<>();
    // Associated cluster nodes
    private ClusterNode clusterNode;
}


Copy the code

Step 2-1: lookProcessChain obtains the Slot chain

ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
    ProcessorSlotChain chain = chainMap.get(resourceWrapper);
    if (chain == null) {
        // private static final Object LOCK = new Object();
        // Lock by object
        synchronized (LOCK) {
            chain = chainMap.get(resourceWrapper);
            if (chain == null) {
                // Entry size limit.
                if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
                    return null;
                }
                // If empty, build a new SlotChain
                chain = SlotChainProvider.newSlotChain();
                Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
                    chainMap.size() + 1); newMap.putAll(chainMap); newMap.put(resourceWrapper, chain); chainMap = newMap; }}}return chain;
}


// Add: ProcessorSlotChain structure
public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
    // Similar to a one-way chain of responsibility, pointing only to the next slot
    privateAbstractLinkedProcessorSlot<? > next =null;
    
}

/ / PS: DefaultProcessorSlotChain has a first
// AbstractLinkedProcessorSlot
       first = new AbstractLinkedProcessorSlot()

Copy the code

Step 3: DefaultProcessorSlotChain processing

The call process is as follows:

  • C- SphU # entry
  • C- CtSph # entry
  • C- CtSph # entryWithPriority
  • C- DefaultProcessorSlotChain # entry
  • C- AbstractLinkedProcessorSlot # transformEntry
  • C- DefaultProcessorSlotChain # entry
  • C- AbstractLinkedProcessorSlot # fireEntry
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);
}

// 补充 : ProcessorSlotProcessorSlot is an interface that includes the following methods: i-processorSlot M-void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, boolean prioritized,Object... args) 
    M- void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized,Object... args) 
    M- void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args)
    M- void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args)
    
Copy the code

As you can see, multiple slots will be executed,TODO specific slot analysis next

Step 4: Process the FlowSlot

Here we only focus on the process, the next article will look at the slot again, the flow limiting judgment logic is in the FlowSlot:

C- FlowSlot
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);
}

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

// Step 3: Rule

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;
        }
        // Get the rule set
        Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
        if(rules ! =null) {
            for (FlowRule rule : rules) {
                // Filter one by one, fail to throw an exception
                if(! canPassCheck(rule, context, node, count, prioritized)) {throw newFlowException(rule.getLimitApp(), rule); }}}}Copy the code

4. Container handling

4.1 build ContextUtil

Step 1: Store the Context

public class ContextUtil {

    // Store Context via ThreadLocal
    private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
    
}
    
Copy the code

Step 2: Clear the Context

public static void exit(a) {
    Context context = contextHolder.get();
    if(context ! =null && context.getCurEntry() == null) {
        contextHolder.set(null); }}Copy the code

conclusion

As the beginning of Sentinel, it is relatively simple, mainly through the process