This is the third day of my participation in the August More Text Challenge

Sentinel 1.8.1 Basic Principles

If you feel helpful, please encourage and support. Your likes are my motivation to keep sharing.

[TOC] Sentinel’s underlying process is relatively straightforward, and a lot of its brilliance lies in its algorithms. It’s actually pretty easy to understand from a Sentinel perspective. His business purpose is to limit the flow, that is to do specific indicators, data analysis and collection, and then calculate. Then, with Sentinel, there’s the question of how to communicate with DashBoard. The key point here is to sort out these two problems.

I. The overall process of Sentinel

The whole process of Sentinel is roughly divided into three steps: 1, initial loading, 2, building responsibility chain, 3, processing flow limiting requests. The entry point is to start with the most commonly used API: sphu.entry (KEY); The whole process covers most of the core of Sentinel.

1. Initialize the load

When you follow the entry method, you’ll first see that it was built from the env.sph object

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

If we enter the Env class, we can see the initialization of Sentinel:

public class Env {
    public static final Sph sph = new CtSph();
    static {
        // If init fails, the process will exit.
        InitExecutor.doInit(); //<==== initialization process}}Copy the code

The initExecutor.doinit method loads the initialization component, which is loaded through the SPI mechanism. He’ll go to loading com. Alibaba. CSP. Sentinel. Init. InitFunc different implementation classes, and the implementation class will be in accordance with its @ InitOrder annotations, sorting. Finally, some initialization is done through the init method.

2. Build a chain of responsibility

Then continue tracing the Entry method. Will eventually enter the com. Alibaba. CSP. Sentinel. Ctsph entryWithPriority method

private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
        throws BlockException { Context context = ContextUtil.getContext(); .//<==== build chain of responsibilityProcessorSlot<Object> chain = lookProcessChain(resourceWrapper); . Entry e =new CtEntry(resourceWrapper, chain, context);
        try {
            //<===== responsibility chain initiates the call
            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

Look at lookProcessChain, the first method to build a chain of responsibilities.

First, look at the resourceWrapper parameter he passed in. This object is a wrapper around Sentinel’s resources. So as you can see here, Sentinel builds a separate chain of responsibility for each resource. It’s conceivable that there could be different chains of responsibility for different resources. This is also a scalable point for Sentinel.

Then, in this method, also will use SPI mechanism load com. Alibaba. CSP. The sentinel. Slotchain. The realization of the SlotChainBuilder subclasses. Only under this interface is configured with a subclass com. Alibaba. CSP. The sentinel. Slots. DefaultSlotChainBuilder used to build a chain of responsibility.

@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build(a) {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();

        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
        for (ProcessorSlot slot : sortedSlotList) {
            if(! (slotinstanceof AbstractLinkedProcessorSlot)) {
                RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
                continue; } chain.addLast((AbstractLinkedProcessorSlot<? >) slot); }returnchain; }}Copy the code

In DefaultSlotChainBuilder build method, and through the SPI load com. Alibaba. CSP. The sentinel. Slotchain. ProcessorSlot implementation class, These implementation classes are also sorted in descending Order by the @order annotation above. These processorSlots host the core concrete business. Sentinel builds the overall functional architecture from these processorslots.

# sentinel - core/SRC/main/resources/meta-inf/services/com. Alibaba. CSP. The sentinel. Slotchain. # sentinel ProcessorSlot file 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.DegradeSlotCopy the code

These slots are summarized roughly in order of loading

  • NodeSelectorSlotCollect the paths of resources and store the call paths of these resources in a tree structure for limiting traffic degradation according to the call paths;
  • ClusterBuilderSlotThe statistics of the storage resource and caller information, such as RT, QPS, thread count of the resource, will be used as the basis for multi-dimensional flow limiting and degradation;
  • LogSlotLogs traffic limiting errors during Slot execution.
  • StatisticSlotIt is used to record and statistics the monitoring information of runtime indicators in different latitudes.
  • FlowSlotIs used for traffic control according to preset traffic limiting rules and slot statistics.
  • AuthoritySlotAccording to the configuration of the blacklist and whitelist and call source information, to do the blacklist and whitelist control;
  • DegradeSlotBy statistics and preset rules, to do the circuit breaker downgrade;
  • SystemSlotThe total inlet flow is controlled by the state of the system, such as load1.

In Sentinel, the order of these slots must be fixed because the business data of these slots is dependent. The SPI mechanism also shows that clients can add their own extensions through the SPI mechanism. For details on how to extend this, see the sentinel-Demo-slot-API section in Demo.

And then with this chain of responsibilities, you can see that it’s really just a list of slots. Each Node in a linked list structure, Node, contains a Next pointer to the Next Node.

3. Handle traffic limiting requests

Each Slot implements two methods, entry and exit.

  • Entry is a method for requesting entry, in which each Slot, after completing its business logic, calls the entry method of the next Slot through a fireEntry method.
  • As mentioned in the previous usage article, the exit method must be guaranteed by the business code to correspond to the Entry method, otherwise an exception will be thrown. In the integration process with other frameworks, try-finally method is mostly used to ensure the correspondence with entry method. After an entry is entered, an exit method is entered.

For example, Sentinel provides an integration extension with Spring-Boot

<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
Copy the code

This extension makes it possible to add @sentinelresource annotations to methods that need to be protected in SpringCloud applications, and in this extension, annotation is handled via AOP. In the AOP processing class SentinelResource Ect, the correspondence between exit method and entry method is guaranteed.

With this in mind, we can sort out the overall execution flow of Sentinel like this:

Why list SPI extensions separately? Because each SPI is an extension point that the framework provides to the application.

Here is a brief overview of the overall processing process of Sentinel. As for how to calculate specific indicators in Sentinel, there are too many algorithms involved, such as time rolling window, leaky bucket, token bucket, etc., so we will not sort them out one by one.

Dynamic rule extension

The idea behind Sentinel is that developers only need to focus on the resource definition, and once the resource definition is successful, they can dynamically add rules, degrade flow control, and other controls. Sentinel can be used to modify rules in two ways.

1. Modify directly through API

This approach is relatively straightforward, and there are many examples where rules are loaded in this way.

FlowRuleManager.loadRules(List<FlowRule> rules); / / modify flow control rules DegradeRuleManager. LoadRules (List < DegradeRule > rules); // Modify the reversion ruleCopy the code

In this way, the application actively loads rules in a hard-coded way. It is simple to use, but only accepts rules in memory, which is not conducive to rule synchronization among multiple applications in a distributed environment. In distributed scenarios, if a rule is to be modified, each application must be modified in turn. So this approach is usually only used for testing and demonstrations. Production environments typically require external storage to manage rules, such as files, databases, configuration centers, and so on. In this way, rules between distributed applications can be managed uniformly. The official recommendation is to implement the DataSource interface and actively adapt to various data sources.

Common implementations of the DataSource extension are:

  • Pull mode: pull-based. The client periodically polls the pull rule to a rule management center. This approach has the advantage of simplicity, but has the disadvantage of not capturing changes in a timely manner. The pull mode supports dynamic file data source, Consul, and Enreka.
  • Push mode: push-based. The rule center pushes uniformly, and the client monitors changes at all times by registering listeners. This method has better real-time performance and consistency assurance. Support the Zookeeper, Redis, nacos, Apollo, etcd.

For an example of pull mode, see the sentinel-Demo-dynamic-file-rule module in Demo. Key code:

ClassLoader classLoader = getClass().getClassLoader();
        String flowRulePath = URLDecoder.decode(classLoader.getResource("FlowRule.json").getFile(), "UTF-8");

        // Data source for FlowRule
        FileRefreshableDataSource<List<FlowRule>> flowRuleDataSource = new FileRefreshableDataSource<>(
            flowRulePath, flowRuleListParser);
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
Copy the code

One is FileRefreshableDataSource AutoRefreshDataSource abstract classes implement subclasses. There is also an implementation subclass, EurekaDatasource. The logic for periodic updates is in the abstract class AutoRefreshDataSource. By default, rules are pulled at a 3-second interval. – The initial wait is 3 seconds, and the execution frequency is 3 seconds.

There are many examples of the push pattern, for example see the Sentinel-Demo-nacos-datasource module. Key code:

 ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
                source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
                }));
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
Copy the code

While focusing on how to register listeners, the core is to use Nacos’s own listening service when building the NacosDataSource.

private void initNacosListener(a) {
        try {
            this.configService = NacosFactory.createConfigService(this.properties);
            // Add config listener.
            configService.addListener(dataId, groupId, configListener);
        } catch (Exception e) {
            RecordLog.warn("[NacosDataSource] Error occurred when initializing Nacos data source", e);
            e.printStackTrace();
       
Copy the code

Expansion points of Sentinel

Two SPI extension points are provided under Sentinel’s core package, Is a sentinel – core/SRC/main/resources/meta-inf/services/com. Alibaba. CSP. Sentinel. Init. InitFunc Also is a sentinel – core/SRC/main/resources/meta-inf/services/com. Alibaba. CSP. Sentinel. Slotchain. ProcessorSlot. (There is also an extension point for SlotChainBuilder, mentioned earlier, which is used to build slots in a way that generally does not need to be extended because slots can be dynamically extended.)

1. Initialize function InitFunc

Principle of extension mechanism

Sentinel provides this initialization mechanism based on the SPI mechanism, and business applications can add their own extensions according to the SPI mechanism.

So what can we do with him? For example, when a dynamic rule was registered and the Datasource was initialized, the process could use this extension point to advance its loading time. For example, there’s a Demo on the website

package com.test.init;

public class DataSourceInitFunc implements InitFunc {

    @Override
    public void init(a) throws Exception {
        final String remoteAddress = "localhost";
        final String groupId = "Sentinel:Demo";
        final String dataId = "com.alibaba.csp.sentinel.demo.flow.rule";

        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
            source -> JSON.parseObject(source, newTypeReference<List<FlowRule>>() {})); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); }}Copy the code

You can add the expansion of the SPI files under the application of the classpath meta-inf/services/com. Alibaba. CSP. Sentinel. Init. InitFunc. Add the following to the file:

com.test.init.DataSourceInitFunc
Copy the code

So what does this mechanism do? Many demos load rules by calling Sentinel’s xxxManager in the main method. What’s the difference?

The biggest difference is where the SPI mechanism is loaded. This InitFunc mechanism processing position is in com. Alibaba. CSP. Sentinel. Env class invokes the static static block of code, that is to say, this mechanism is the JVM class loading in the process of loading. If you are familiar with the JVM, you know that it is loaded before the main method executes. Preloading certainly has its benefits, but the benefits of preloading are minimal in practice. This is why his init method doesn’t pass in any other objects.

2. Process processing logic ProcessorSlot

These slots are actually the slots in Sentinel that handle the actual business. With Sentinel providing this SPI extension point, applications can extend their business functions on top of Sentinel.

The load process for these slots, Sentinel, does some wrapping. While the original SPI mechanism was loaded in random Order, Sentinel extends the SPI mechanism to load slots in the Order of the @order annotation in the Slot header. With this mechanism in place, the application can insert any Slot of its own into the Slot chain of Sentinel.

But what good would that do? One of the interesting ones is this StatisticSlot.

@Spi(order = Constants.ORDER_STATISTIC_SLOT) //<==== load order
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 {
            // Triggers the entry method for the next Slot
            fireEntry(context, resourceWrapper, node, count, prioritized, args);
            // Add statisticsnode.increaseThreadNum(); node.addPassRequest(count); . }catch (PriorityWaitException ex) {
            // Add statisticsnode.increaseThreadNum(); . }catch (BlockException e) {
            // Add statisticsnode.increaseBlockQps(count); . }catch(Throwable e) { ..... }}@Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        // Get the current NodeNode node = context.getCurNode(); .// Triggers the exit method of the previous SlotfireExit(context, resourceWrapper, count); }... }Copy the code

A simple reading of the source code for this Slot shows that Sentinel collects a lot of runtime data, such as QPS, BlockQps, ThreadNum, etc. These data, within the Sentinel, by StatisticSlotCallbackRegistry. GetEntryCallbacks () callback method for processing. These entryCallbacks are added through the SPI extension subclass of InitFunc mentioned earlier.

public class MetricCallbackInit implements InitFunc {
    @Override
    public void init(a) throws Exception {
        StatisticSlotCallbackRegistry.addEntryCallback(MetricEntryCallback.class.getCanonicalName(),
            new MetricEntryCallback());
        StatisticSlotCallbackRegistry.addExitCallback(MetricExitCallback.class.getCanonicalName(),
            newMetricExitCallback()); }}Copy the code

This means, we can also through the SPI extension points, extending a own InitFunc, also to add their own entryCallback and exitCallback StatisticSlotCallbackRegistry. This way, the key information collected by StaticSlot can be handled directly in the onpass and onexit methods of these callback. Once you have them, you can send them out via MQ or some other component. Isn’t that a good place to bury data?

In fact, there is a sentinel-parameter-flow-control module under the Sentinel-extension module of Sentinel, which is used to do hotspot parameter analysis. And that’s where he uses the extension point in StaticSlot. Of course, the source code does not use node statistics directly.

public class ParamFlowStatisticSlotCallbackInit implements InitFunc {

    @Override
    public void init(a) {
        StatisticSlotCallbackRegistry.addEntryCallback(ParamFlowStatisticEntryCallback.class.getName(),
            new ParamFlowStatisticEntryCallback());
        StatisticSlotCallbackRegistry.addExitCallback(ParamFlowStatisticExitCallback.class.getName(),
            newParamFlowStatisticExitCallback()); }}Copy the code