Sentinel is a lightweight and highly available flow control component that is open source by Ali middleware team and oriented to distributed service architecture. It mainly takes traffic as the entry point and helps users protect the stability of services from multiple dimensions such as flow control, fuse downgrade and system load protection.

You may be asking: What are the similarities and differences between Sentinel and Netflix Hystrix, which is a common fuse downgrade library? The Sentinel website has a comparison article. Here’s a summary of the table, and a comparison can be found here.

Compare the content of Sentinel Hystrix
Isolation strategy Semaphore isolation Thread pool isolation/semaphore isolation
Fuse downgrading strategy Based on response time or failure rate Based on failure ratio
Real-time indicator realization The sliding window Sliding Windows (based on RxJava)
Rule configuration Support for multiple data sources Support for multiple data sources
scalability Multiple extension points Plug-in form
Annotation-based support support support
Current limiting Based on QPS, traffic limiting based on call relationships is supported Does not support
Traffic shaping Support slow start and constant speed mode Does not support
System load protection support Does not support
The console Out of the box, you can configure rules, view second-level monitoring, machine discovery, etc imperfect
Common framework adaptation Servlet, Spring Cloud, Dubbo, gRPC, etc Servlet, Spring Cloud Netflix

It can be seen from the comparison table that Sentinel is more powerful than Hystrix in terms of functionality. In this article, let’s take a look at the source code of Sentinel and uncover the mystery of Sentinel.

The project structure

Fork the Sentinel source code into your Github repository, clone the source code locally, and start reading the source code.

First, let’s take a look at the Sentinel project’s overall structure:

  • Sentinel-core core modules, current limiting, degradation, system protection and so on are implemented here

  • Sentinel – Dashboard console module, can be connected to the Sentinel client to achieve visual management

  • The Sentinel-Transport transport module provides basic monitoring server and client APIS, as well as some implementations based on different libraries

  • Sentinel -extension module, mainly for DataSource partial extension implementation

  • Sentinel-adapter adapter module, mainly to achieve some common framework adaptation

  • How to use Sentinel for traffic limiting and degradation

  • The Sentinel-benchmark module provides benchmarks for the accuracy of core code

Run the sample

Almost every framework comes with sample modules, some called Example, some called demo, and Sentinel is no exception.

Let’s take an example from the Sentinel demo and run it to see the general situation. As mentioned above, the main core function of Sentinel is to do flow limiting, downgrading and system protection. Let’s start with the implementation principle of Sentinel from “flow limiting”.

We can see that there are many different examples in sentinel-Demo module. We can find the flow package in basic module. Below this package is the corresponding flow limiting sample, but there are many types of limiting.


     
  1. public class FlowQpsDemo {

  2.    private static final String KEY = "abc";

  3.    private static AtomicInteger pass = new AtomicInteger();

  4.    private static AtomicInteger block = new AtomicInteger();

  5.    private static AtomicInteger total = new AtomicInteger();

  6.    private static volatile boolean stop = false;

  7.    private static final int threadCount = 32;

  8.    private static int seconds = 30;

  9.    public static void main(String[] args) throws Exception {

  10.        initFlowQpsRule();

  11.        tick();

  12.        // first make the system run on a very low condition

  13.        simulateTraffic();

  14.        System.out.println("===== begin to do flow control");

  15.        System.out.println("only 20 requests per second can pass");

  16.    }

  17.    private static void initFlowQpsRule() {

  18.        List<FlowRule> rules = new ArrayList<FlowRule>();

  19.        FlowRule rule1 = new FlowRule();

  20.        rule1.setResource(KEY);

  21.        // set limit qps to 20

  22.        rule1.setCount(20);

  23. // Set the traffic limiting type according to QPS

  24.        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);

  25.        rule1.setLimitApp("default");

  26.        rules.add(rule1);

  27. // Load the rules for limiting traffic

  28.        FlowRuleManager.loadRules(rules);

  29.    }

  30.    private static void simulateTraffic() {

  31.        for (int i = 0; i < threadCount; i++) {

  32.            Thread t = new Thread(new RunTask());

  33.            t.setName("simulate-traffic-Task");

  34.            t.start();

  35.        }

  36.    }

  37.    private static void tick() {

  38.        Thread timer = new Thread(new TimerTask());

  39.        timer.setName("sentinel-timer-task");

  40.        timer.start();

  41.    }

  42.    static class TimerTask implements Runnable {

  43.        @Override

  44.        public void run() {

  45.            long start = System.currentTimeMillis();

  46. System.out.println("begin to statistic!!!" );

  47.            long oldTotal = 0;

  48.            long oldPass = 0;

  49.            long oldBlock = 0;

  50. while (! stop) {

  51.                try {

  52.                    TimeUnit.SECONDS.sleep(1);

  53.                } catch (InterruptedException e) {

  54.                }

  55.                long globalTotal = total.get();

  56.                long oneSecondTotal = globalTotal - oldTotal;

  57.                oldTotal = globalTotal;

  58.                long globalPass = pass.get();

  59.                long oneSecondPass = globalPass - oldPass;

  60.                oldPass = globalPass;

  61.                long globalBlock = block.get();

  62.                long oneSecondBlock = globalBlock - oldBlock;

  63.                oldBlock = globalBlock;

  64.                System.out.println(seconds + " send qps is: " + oneSecondTotal);

  65.                System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal

  66.                    + ", pass:" + oneSecondPass

  67.                    + ", block:" + oneSecondBlock);

  68.                if (seconds-- <= 0) {

  69.                    stop = true;

  70.                }

  71.            }

  72.            long cost = System.currentTimeMillis() - start;

  73.            System.out.println("time cost: " + cost + " ms");

  74.            System.out.println("total:" + total.get() + ", pass:" + pass.get()

  75.                + ", block:" + block.get());

  76.            System.exit(0);

  77.        }

  78.    }

  79.    static class RunTask implements Runnable {

  80.        @Override

  81.        public void run() {

  82. while (! stop) {

  83.                Entry entry = null;

  84.                try {

  85.                    entry = SphU.entry(KEY);

  86.                    // token acquired, means pass

  87.                    pass.addAndGet(1);

  88.                } catch (BlockException e1) {

  89.                    block.incrementAndGet();

  90.                } catch (Exception e2) {

  91.                    // biz exception

  92.                } finally {

  93.                    total.incrementAndGet();

  94. if (entry ! = null) {

  95.                        entry.exit();

  96.                    }

  97.                }

  98.                Random random2 = new Random();

  99.                try {

  100.                    TimeUnit.MILLISECONDS.sleep(random2.nextInt(50));

  101.                } catch (InterruptedException e) {

  102.                    // ignore

  103.                }

  104.            }

  105.        }

  106.    }

  107. }

Copy the code

After executing the above code, print the following result:

As you can see, the number of passes in the result above is different from our expectation. We expected to allow 20 pass requests per second, but currently there are many passes with more than 20 requests.

The reason is that the code we are testing here uses multiple threads. Notice the value of threadCount. There are 32 threads to simulate. The number of passes will be higher than 20.

The following model can be used to describe a TimeTicker thread doing statistics every 1 second. There are N RunTask threads impersonating requests, the accessed Business code is protected by the resource key, and by rule only 20 requests per second are allowed to pass.

Because counters such as pass, block, and total are shared globally, and multiple RunTask threads do not have internal lock protection when they apply for and obtain entry through SphU. Entry, the number of passes exceeds the set threshold.

In order to prove the correctness and reliability of the flow at the single-threaded lower limit, our model should be like this:

ThreadCount = 1; threadCount = 1; threadCount = 1;

You can see that the number of passes is basically staying at 20, but the first count is still over 20. What causes this?

In fact, if you look closely at the code in the Demo, you can see that one thread is used to simulate the request, and another thread is used to count the results, and the statistics thread is used to count the results every 1 second. There is a time error between the two threads. As can be seen from the timestamp printed by the TimeTicker thread, although statistics are collected every second, the current printing time and the last time are still different, not exactly 1000ms interval.

In order to really verify the 20 requests per second limit and keep the data accurate, you need to do benchmarks.That’s not the focus of this article, but if you’re interested, you can check out the JMH.

In-depth principle

Using a simple example program, sentinel can limit the flow of requests. In addition to limiting the flow, sentinel also has degradation and system protection. Now let’s dig deep into the source code to see how Sentinel works.

Start with the entry: sphu.entry (). This method will apply for an entry. If the application succeeds, the flow is not restricted. Otherwise, a BlockException will be thrown, indicating that the flow is restricted.

Following the sphu.entry () method will enter sph.Entry (). The default Sph implementation class is CtSph, Will perform to the entry in the CtSph ResourceWrapperresourceWrapper intcount, Object… Args)throwsBlockException this method.

Let’s look at an implementation of this method:


     
  1. public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {

  2.    Context context = ContextUtil.getContext();

  3.    if (context instanceof NullContext) {

  4.        // Init the entry only. No rule checking will occur.

  5.        return new CtEntry(resourceWrapper, null, context);

  6.    }

  7.    if (context == null) {

  8.        context = MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType());

  9.    }

  10.    // Global switch is close, no rule checking will do.

  11. if (! Constants.ON) {

  12.        return new CtEntry(resourceWrapper, null, context);

  13.    }

  14. // Obtain the SlotChain corresponding to the resource

  15.    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

  16. / *

  17.     * Means processor cache size exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, so no

  18.     * rule checking will be done.

  19. * /

  20.    if (chain == null) {

  21.        return new CtEntry(resourceWrapper, null, context);

  22.    }

  23.    Entry e = new CtEntry(resourceWrapper, chain, context);

  24.    try {

  25. // Executes the Slot entry method

  26.        chain.entry(context, resourceWrapper, null, count, args);

  27.    } catch (BlockException e1) {

  28.        e.exit(count, args);

  29. / / throw BlockExecption

  30.        throw e1;

  31.    } catch (Throwable e1) {

  32.        RecordLog.info("Sentinel unexpected exception", e1);

  33.    }

  34.    return e;

  35. }

Copy the code

This method can be divided into the following parts:

  • 1. Check parameters and global configuration items. If the parameters do not meet the requirements, a CtEntry object is returned.

  • 2. Obtain SlotChain based on the packaged resource object

  • 3. Run the Entry method of SlotChain

    • 3.1. If a BlockException is thrown by SlotChain’s Entry method, the exception is thrown upwards

    • 3.2. If the Entry method of SlotChain is executed properly, the entry object is returned

  • 4. If the upper method catches a BlockException, the request is curbed; otherwise, the request will execute normally

The two most important steps are step 2 and step 3. Let’s break these two steps down.

Create SlotChain

First look at the lookProcessChain method implementation:


     
  1. private ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {

  2.    ProcessorSlotChain chain = chainMap.get(resourceWrapper);

  3.    if (chain == null) {

  4.        synchronized (LOCK) {

  5.            chain = chainMap.get(resourceWrapper);

  6.            if (chain == null) {

  7.                // Entry size limit.

  8.                if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {

  9.                    return null;

  10.                }

  11. // Specify the chain construction method

  12.                chain = Env.slotsChainbuilder.build();

  13.                Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(chainMap.size() + 1);

  14.                newMap.putAll(chainMap);

  15.                newMap.put(resourceWrapper, chain);

  16.                chainMap = newMap;

  17.            }

  18.        }

  19.    }

  20.    return chain;

  21. }

Copy the code

This method is cached using a HashMap where the key is the resource object. I’ve got a lock here, and I’ve got a doublecheck. Concrete structure chain method is through: Env. SlotsChainbuilder. Build () this code to create. Enter this method.


     
  1. public ProcessorSlotChain build() {

  2.    ProcessorSlotChain chain = new DefaultProcessorSlotChain();

  3.    chain.addLast(new NodeSelectorSlot());

  4.    chain.addLast(new ClusterBuilderSlot());

  5.    chain.addLast(new LogSlot());

  6.    chain.addLast(new StatisticSlot());

  7.    chain.addLast(new SystemSlot());

  8.    chain.addLast(new AuthoritySlot());

  9.    chain.addLast(new FlowSlot());

  10.    chain.addLast(new DegradeSlot());

  11.    return chain;

  12. }

Copy the code

ProcessorSlotChain is a list of slots added to ProcessorSlotChain. The realization of the specific need to go to the DefaultProcessorSlotChain.


     
  1. public class DefaultProcessorSlotChain extends ProcessorSlotChain {

  2. AbstractLinkedProcessorSlot<? > first = new AbstractLinkedProcessorSlot<Object>() {

  3.        @Override

  4.        public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, Object... args)

  5.            throws Throwable {

  6.            super.fireEntry(context, resourceWrapper, t, count, args);

  7.        }

  8.        @Override

  9.        public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {

  10.            super.fireExit(context, resourceWrapper, count, args);

  11.        }

  12.    };

  13. AbstractLinkedProcessorSlot<? > end = first;

  14.    @Override

  15. public void addFirst(AbstractLinkedProcessorSlot<? > protocolProcessor) {

  16.        protocolProcessor.setNext(first.getNext());

  17.        first.setNext(protocolProcessor);

  18.        if (end == first) {

  19.            end = protocolProcessor;

  20.        }

  21.    }

  22.    @Override

  23. public void addLast(AbstractLinkedProcessorSlot<? > protocolProcessor) {

  24.        end.setNext(protocolProcessor);

  25.        end = protocolProcessor;

  26.    }

  27. }

Copy the code

DefaultProcessorSlotChain have two AbstractLinkedProcessorSlot types of variables: the first and the end, this is the head of the linked list node and end node.

Create DefaultProcessorSlotChain object, the first to create the first node, then the first node to the end node, can be expressed in below:

After the first node is added to the list, the structure of the entire list looks like the following:

After all the nodes are added to the linked list, the structure of the entire linked list becomes as shown below:

So will all the Slot objects added to the list, each Slot is inherited from AbstractLinkedProcessorSlot. And AbstractLinkedProcessorSlot is a chain of responsibility design, each object has a next attribute, point to an another AbstractLinkedProcessorSlot object. In fact, the chain of responsibility mode is in many frameworks, such as Netty through pipeline to achieve.

Now that you know how SlotChain is created, it’s time to look at how the Slot Entry method is executed.

Run the Entry method of SlotChain

Instances of ProcessorSlotChain lookProcessChain method is DefaultProcessorSlotChain, then execute chain. Entry method, Will perform DefaultProcessorSlotChain entry method, while DefaultProcessorSlotChain entry method is like this:


     
  1. @Override

  2. public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, Object... args)

  3.    throws Throwable {

  4.    first.transformEntry(context, resourceWrapper, t, count, args);

  5. }

Copy the code

That is to say, the entry of DefaultProcessorSlotChain actual transformEntry method is executed first attribute.

And transformEntry method performs the entry of the current node, the first node in the DefaultProcessorSlotChain rewrote the entry method, specific as follows:


     
  1. @Override

  2. public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, Object... args)

  3.    throws Throwable {

  4.    super.fireEntry(context, resourceWrapper, t, count, args);

  5. }

Copy the code

The entry method of the first node is actually the fireEntry method of the super, so we continue to focus on the fireEntry method, as follows:


     
  1. @Override

  2. public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, Object... args)

  3.    throws Throwable {

  4. if (next ! = null) {

  5.        next.transformEntry(context, resourceWrapper, obj, count, args);

  6.    }

  7. }

Copy the code

As you can see, the execution entry is passed from the fireEntry method, which executes the next node transformEntry method of the current node. As we’ve already analyzed, the transformEntry method triggers the entry of the current node. In other words, the fireEntry method actually triggers the entry method of the next node. The specific process is shown in the figure below:

As you can see from the figure, the initial entry() method calling the Chain is changed to the entry() method calling the slots in the SlotChain. From the above analysis, the first Slot node in SlotChain is NodeSelectorSlot.

Execute the Slot entry method

Now we can look at the entry method of the first node in SlotChain, NodeSelectorSlot, as follows:


     
  1. @Override

  2. public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, Object... args)

  3.    throws Throwable {

  4.    DefaultNode node = map.get(context.getName());

  5.    if (node == null) {

  6.        synchronized (this) {

  7.            node = map.get(context.getName());

  8.            if (node == null) {

  9.                node = Env.nodeBuilder.buildTreeNode(resourceWrapper, null);

  10.                HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());

  11.                cacheMap.putAll(map);

  12.                cacheMap.put(context.getName(), node);

  13.                map = cacheMap;

  14.            }

  15.            // Build invocation tree

  16.            ((DefaultNode)context.getLastNode()).addChild(node);

  17.        }

  18.    }

  19.    context.setCurNode(node);

  20. // This triggers the entry method for the next node

  21.    fireEntry(context, resourceWrapper, node, count, args);

  22. }

Copy the code

NodeSelectorSlot NodeSelectorSlot NodeSelectorSlot NodeSelectorSlot NodeSelectorSlot NodeSelectorSlot NodeSelectorSlot NodeSelectorSlot

  • NodeSelectorSlot Collects the paths of resources and stores the call paths of these resources in a tree structure for traffic degradation based on the call paths.

  • ClusterBuilderSlot is used to store resource statistics and caller information, such as RT, QPS and Thread Count of the resource. These information will be used as the basis for multi-dimensional traffic limiting and degradation.

  • StatistcSlot is used to record and count runtime information of different latitudes.

  • FlowSlot is used to limit traffic according to preset flow limiting rules and slot statistics.

  • AuthorizationSlot is used to control the blacklist and whitelist based on the blacklist and whitelist.

  • Degrades a pack with statistics and preset rules.

  • SystemSlot controls the total incoming traffic based on the system state, such as load1.

After performing the business logic processing, the fireEntry() method is called, which triggers the entry method of the next node. This is how the sentinel chain of responsibility is passed: After each Slot node completes its business, fireEntry is called to trigger the entry method of the next node.

So you can complete the above diagram as follows:

At this point, the entry() method of each node is invoked through SlotChain. Each node performs its own logical processing according to the created rules. When the statistical result reaches the set threshold, events such as traffic limiting and degradation are triggered, specifically, BlockException is thrown.

conclusion

Sentinel is a linked list based on seven different slots. Each Slot does its job and passes the request to the next Slot until a BlockException is thrown in one of the slots.

The first three slots collect statistics, and the latter slots control whether the request is blocked or allowed based on the statistics results and configured rules.

There are also many options for the type of control: by QPS, thread count, cold start, and so on.

Then, based on this core method, many other functions were derived:

  • 1. Dashboard console, which can visually control each sentinel client connected (by sending heartbeat messages) and communicate with the client via HTTP protocol.

  • 2. Rule persistence: By implementing the DataSource interface, you can persist the configured rules in different ways. The default rule is in memory

  • 3. Adapt mainstream frameworks, including Servlet, Dubbo, rRpc, etc

The Dashboard console

Sentinel-dashboard is a standalone application that is powered by Spring-Boot and provides a lightweight console that provides machine discovery, real-time monitoring of stand-alone resources, cluster resource summary, and rule management.

We only need to configure the application to use these features.

1 Starting the Console

1.1 Download the code and compile the console

  • Download Console Engineering

  • Package the code into a fat JAR using the following command: MVN cleanPackage

1.2 start

Start the compiled console with the following command:


     
  1. $ java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -jar target/sentinel-dashboard.jar

Copy the code

Port =8080 is used to specify the Spring Boot port 8080.

2 Connect the client to the console

After the console starts, perform the following steps to connect the client to the console.

2.1 Importing the Client JAR Package

Importing jar packages via POM.xml:


     
  1. <dependency>

  2.    <groupId>com.alibaba.csp</groupId>

  3.    <artifactId>sentinel-transport-simple-http</artifactId>

  4.    <version>x.y.z</version>

  5. </dependency>

Copy the code

2.2 Setting Startup Parameters

Started to join the JVM parameter – Dcsp. Sentinel. Dashboard. Server = consoleIp: specified console port address and port. If multiple applications are started, you need to run -dcsp.sentinel.api. port= XXXX to specify the port of the client monitoring API (8719 by default).

In addition to modifying JVM parameters, the same effect can be achieved through configuration files. For more information, see startup Configuration items.

2.3 Triggering Client Initialization

To ensure traffic to the client, Sentinel initializes the first time the client calls and starts sending heartbeat packets to the console.

Sentinel-dashboard is a standalone Web application that accepts connections from clients and communicates with them using HTTP. Their relationship is shown below:

dashboard

Dashboard starts will wait for the client connection, one of the specific practice is in MachineRegistryController receiveHeartBeat method, the client sends a heartbeat message, is this method through HTTP request.

After dashboard receives the heartbeat message from the client, it encapsulates the IP and port information transmitted by the client into a MachineInfo object. The object is then added to a ConcurrentHashMap via the addMachine method of the MachineDiscovery interface to save it.

This is a problem because the client information is stored in Dashboard memory, so when the Dashboard application restarts, the client information that has been sent before will be lost.

client

During startup, the client selects a CommandCenterInitFunc and only one CommandCenter.

CommandHandler implementation classes are scanned and obtained by spi before startup, and all CommandHandlers are registered in a HashMap for later use.

PS: Consider why CommandHandler does not need to be persisted and is stored directly in memory.

After registering CommandHandler, CommandCenter is launched, which has two implementation classes:

  • SimpleHttpCommandCenter starts a server with ServerSocket and accepts socket connections

  • NettyHttpCommandCenter starts a server over Netty that accepts channel connections

After CommandCenter is started, it waits for the Dashboard to send a message. After receiving a message, it processes the message through a specific CommandHandler and returns the result to the Dashboard.

Note here that Dashboard sends messages to clients via asynchronous httpClient, in the HttpHelper class.

But the weird thing is, since the message is sent asynchronously, and the CountDownLatch is used to wait for the message to return and then get the result, there is no sense of asynchron. The specific code is as follows:


     
  1. private String httpGetContent(String url) {

  2.    final HttpGet httpGet = new HttpGet(url);

  3.    final CountDownLatch latch = new CountDownLatch(1);

  4.    final AtomicReference<String> reference = new AtomicReference<>();

  5.    httpclient.execute(httpGet, new FutureCallback<HttpResponse>() {

  6.        @Override

  7.        public void completed(final HttpResponse response) {

  8.            try {

  9.                reference.set(getBody(response));

  10.            } catch (Exception e) {

  11.                logger.info("httpGetContent " + url + " error:", e);

  12.            } finally {

  13.                latch.countDown();

  14.            }

  15.        }

  16.        @Override

  17.        public void failed(final Exception ex) {

  18.            latch.countDown();

  19.            logger.info("httpGetContent " + url + " failed:", ex);

  20.        }

  21.        @Override

  22.        public void cancelled() {

  23.            latch.countDown();

  24.        }

  25.    });

  26.    try {

  27.        latch.await(5, TimeUnit.SECONDS);

  28.    } catch (Exception e) {

  29.        logger.info("wait http client error:", e);

  30.    }

  31.    return reference.get();

  32. }

Copy the code

Adaptation of mainstream frameworks

Sentinel also ADAPTS some mainstream frameworks so that sentinel can enjoy the protection even when using mainstream frameworks. Currently supported adapters include the following:

  • Web Servlet

  • Dubbo

  • Spring Boot / Spring Cloud

  • gRPC

  • Apache RocketMQ

In fact, the adaptation is done through the extension points of the major frameworks, and then the Sentinel limiting degradation code is added to the extension points. Take a look at the Servlet adaptation code, the specific code is:


     
  1. public class CommonFilter implements Filter {

  2.    @Override

  3.    public void init(FilterConfig filterConfig) {

  4.    }

  5.    @Override

  6.    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

  7.        throws IOException, ServletException {

  8.        HttpServletRequest sRequest = (HttpServletRequest)request;

  9.        Entry entry = null;

  10.        try {

  11. // Resources generated on request

  12.            String target = FilterUtil.filterTarget(sRequest);

  13.            target = WebCallbackManager.getUrlCleaner().clean(target);

  14. // "Apply" for the resource

  15.            ContextUtil.enter(target);

  16.            entry = SphU.entry(target, EntryType.IN);

  17. // If the resource can be applied successfully, the traffic is not restricted

  18. // Will request release

  19.            chain.doFilter(request, response);

  20.        } catch (BlockException e) {

  21. // Otherwise, if BlockException is caught, the request is restricted

  22. // Redirects the request to a default page

  23.            HttpServletResponse sResponse = (HttpServletResponse)response;

  24.            WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse);

  25.        } catch (IOException e2) {

  26. // Omit some code

  27.        } finally {

  28. if (entry ! = null) {

  29.                entry.exit();

  30.            }

  31.            ContextUtil.exit();

  32.        }

  33.    }

  34.    @Override

  35.    public void destroy() {

  36.    }

  37. }

Copy the code

The Servlet Filter is extended to implement a Filter, and then the request is restricted in the doFilter method. If the request is restricted, the request is redirected to a default page, otherwise the request is released to the next Filter.

Rules are persistent and dynamic

The idea behind Sentinel is that developers only need to focus on the definition of the resource, and when the resource definition is successful, various flow control degradation rules can be dynamically added.

Sentinel provides two ways to change rules:

  • Modify directly via API (loadRules)

  • Modify data sources using a DataSource

You can use the following apis to modify different rules:


     
  1. FlowRuleManager.loadRules(List<FlowRule> rules); // Modify the flow control rule

  2. DegradeRuleManager.loadRules(List<DegradeRule> rules); // Modify the reversion rule

  3. SystemRuleManager.loadRules(List<SystemRule> rules); // Modify system rules

Copy the code

The DataSource extension

The loadRules() method above only accepts in-memory rule objects, but the in-memory rules are lost after the application restarts. More often, rules are better stored in files, databases, or configuration centers.

The DataSource interface gives us the ability to connect to any configuration source. Implementing the DataSource interface is a much more reliable way to modify rules directly through the API.

It is recommended that you set rules on the console and push the rules to a unified rule center. You only need to implement the DataSource interface to monitor the rule changes in the rule center and obtain the changed rules in real time.

DataSource extensions are commonly implemented in the following ways:

  • Pull mode: The client periodically polls the pull rules to a rule management center, such as SQL, files, or VCS. The way to do this is simple, but the disadvantage is that changes cannot be captured in a timely manner;

  • Push mode: the rule center pushes the rules uniformly, and the client monitors the changes constantly by registering listeners, such as using Nacos and Zookeeper configuration centers. This method has better real-time performance and consistency assurance.

So far, the basic situation of Sentinel has been analyzed, and you can continue to read the source code for more details.

I am Hou Yi, if the article is helpful to you, welcome you to like and pay attention, and welcome you to pay attention to my public account: