1. Introduction

Today, many projects carry a large number of business functions, and development in a single project can result in an explosion of code, which creates obstacles for project development, deployment, operation and maintenance, and expansion. In this context, the emergence of micro-services is conducive to solving the problems mentioned above, but the stability of services causes great problems.

The Ali Sentinel component introduced in this chapter takes traffic as the entry point to protect the stability of services from multiple dimensions such as flow control, fuse downgrading and system load protection. The core functionality of the component does not depend on any external project, and officials say that the performance loss from the introduction of Sentinel is very small. The impact is only significant (around 10%) when the operational QPS scale exceeds 25W, and the loss is negligible when the QPS scale is not too large.

Sentinel has the following characteristics:

  • Rich application scenarios: Sentinel has undertaken the core scenarios of Alibaba’s double Eleven traffic drive in the past 10 years, such as SEC killing (i.e. burst traffic control within the range of system capacity), message peaking and valley filling, cluster flow control, real-time fusing of unavailable downstream applications, etc.
  • Complete real-time monitoring: Sentinel also provides real-time monitoring capabilities. From the console, you can see a summary of the performance of a single machine-by-second data, or even a cluster of less than 500 machines, for accessing the application.
  • Extensive Open source ecosystem: Sentinel provides out-of-the-box integration modules with other open source frameworks/libraries, such as Spring Cloud, Dubbo, and gRPC. You can quickly access Sentinel by introducing the appropriate dependencies and simple configuration.
  • Sophisticated SPI extension points: Sentinel provides an easy-to-use, sophisticated SPI extension interface. You can quickly customize the logic by implementing an extension interface. For example, customize rule management and adapt dynamic data sources.

For more information about the project, please refer to the official documentation.

Basis of 2.

The Sentinel component takes resources as unit to conduct data statistics and provide basic data for control. Each resource has a resource name, and the following concepts are involved in a call:

  • Slot: indicates a function slot. Each slot has different responsibilities. For example, the statistics slot collects data of the current system, and the traffic limiting slot checks whether the traffic limiting rule is valid.
  • Slot chain: indicates the slot chain. Each resource is subject to different check rules. Therefore, each resource has its own slot chain in Sentinel to ensure a one-to-one mapping between the resource and slot chain. To request resource X, you only need to execute the slot chain corresponding to resource X.
  • Node: statistics Node, including global data statistics of resources, statistics of single link invocation, and other nodes.
  • Entry: Each invocation of a resource creates an Entry, which stores the invocation information, such as creation time and current statistics node. An Entry is responsible for executing a slot chain.
  • Context: Invokes the link Context, maintained by ThreadLocal. In a processing, might involve multiple request for a resource (can be understood as a thread of request twice Entry), such as user access to the information of the article, to get the reading and comments of the article, the data is stored in another service S, then may call resource S twice, so creating two Entry, But only one Context is maintained.

3. Execute the process

To use Sentinel in the project, the basic usage is as follows:

Public static void main(String[] args) {// Make resource calls continuously.while (true) {
        Entry entry = null;
        try {
	    entry = SphU.entry("HelloWorld"); // The logic in the resource. System.out.println("hello world");
	} catch (BlockException e1) {
	    System.out.println("blocked!");
	} finally {
	   if(entry ! = null) { entry.exit(); }}}}Copy the code

We do this by tracing entry = SphU. Entry (“HelloWorld”); Statement to see the Sentinel execution flow. The key code in this process is the method by which CtSph gets the Entry.

private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
        throws BlockException {
        Context context = ContextUtil.getContext();
        if (context instanceof NullContext) {
            // The {@link NullContext} indicates that the amount of context has exceeded the threshold,
            // so here init the entry only. No rule checking will be done.
            return new CtEntry(resourceWrapper, null, context);
        }

        if (context == null) {
            // Using default context.
            context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
        }

        // Global switch is close, no rule checking will do.
        if(! Constants.ON) {return new CtEntry(resourceWrapper, null, context);
        }

        ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

        /*
         * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
         * so no rule checking will be done.
         */
        if (chain == null) {
            return new CtEntry(resourceWrapper, null, context);
        }

        Entry e = new CtEntry(resourceWrapper, chain, context);
        try {
            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

3.1. Rule switch

Rule checking can be controlled by setting the constants.on value.

if(! Constants.ON) {return new  CtEntry(resourceWrapper, null, context); 
}
Copy the code

3.2. The Context

Context context = ContextUtil.getContext();
if (context instanceof NullContext) {
    // The {@link NullContext} indicates that the amount of context has exceeded the threshold,
    // so here init the entry only. No rule checking will be done.
    return new CtEntry(resourceWrapper, null, context);
}

if (context == null) {
    // Using default context.
    context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
Copy the code

This section gets the Conext object mentioned above, focusing first on the ContextUtil class:

private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();

public static Context getContext() {
        return contextHolder.get();
    }
Copy the code

As you can see, Sentinel instantiates a Context object for each thread, which is saved by ThreadLocal. Typically, a user request is made in one thread at a time, so a thread is guaranteed to have a unique Context. When the thread obtains the context for the first time, this method returns null and needs to be instantiated as follows:

protected static Context trueEnter(String name, String origin) {
        Context context = contextHolder.get();
        if (context == null) {
            Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
            DefaultNode node = localCacheNameMap.get(name);
            if (node == null) {
                if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                    setNullContext();
                    return NULL_CONTEXT;
                } else {
                    try {
                        LOCK.lock();
                        node = contextNameNodeMap.get(name);
                        if (node == null) {
                            if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                                setNullContext();
                                return NULL_CONTEXT;
                            } else {
                                node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                                // Add entrance node.
                                Constants.ROOT.addChild(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;
    }
Copy the code

The Sentinel will create a Context and put it into the contextHolder. This variable is declared as follows:

private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
Copy the code

This variable holds the EntranceNode object with the Name of the Context as the key. In a component, the call path of a resource is stored in a tree structure for flow control based on the call path. By default, a call forms the following structure:

             machine-root
                 /     
                /
         EntranceNode(sentinel_default_context)
              /
             /   
      DefaultNode(nodeA)
Copy the code

The EntranceNode above is generated by the code above. Note that when generating EntranceNode, a NullContext is returned if MAX_CONTEXT_NAME_SIZE is exceeded. If the following methods are used to generate different Contextnames in one call, the following structure is formed.

 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();Copy the code
                machine-root
                   /         \
                  /           \
          EntranceNode1   EntranceNode2
                /               \
               /                 \
       DefaultNode(nodeA)   DefaultNode(nodeA)
Copy the code

See official documentation for details

3.3. slot chain

 ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
Copy the code

The above code is used to obtain the slot chain for this execution, which is a series of slots for data statistics and rule checking. With the lookProcessChain method, we get the following information:

  1. As mentioned above, each resource has a slot chain. The slot chain information is stored in Ctsph::chainMap with the key of the resource name.
  2. Constants.MAX_SLOT_CHAIN_SIZE (default 6000) of resources beyond which it will not take effect.
  3. Obtained through SlotChainProvider: : newSlotChain slot chain, by default will use DefaultSlotChainBuilder * * * * structure slot chain, can be a single configuration through SPI (SPI, Service Provider Interface (Service Provider Interface)

After obtaining the slot chain, an Entry object is constructed, and then slot chain is called using the following method to execute each slot in turn, ending the basic process.

chain.entry(context, resourceWrapper, null, count, prioritized, args);
Copy the code

In DefaultSlotChainBuilder, the slot chain is returned using the following method, each of which will be described in detail in a future article.

4. Afterword.

This article gives a brief introduction to Sentinel and describes the basic execution process. On this basis, we will follow the source code to learn the different slots of Sentinel.