If you want to make a million yuan a year, can Alibaba Sentinel support RESTful interfaces?

Recently, I was preparing to use Alibaba Sentinel, and found that the RESTful interface does not support very well. Some children's shoes may not know Sentinel very well. Let's give you a brief introduction.

About Sentinel

Sentinel is an open-source traffic defense framework of Alibaba. The Github address is: https://github.com/alibaba/Sentinel . With the popularity of microservices, the stability between services becomes more and more important. Sentinel takes traffic as the entry point, and protects the stability of services from multiple dimensions such as traffic control, fuse degradation, system load protection, etc.

More information can be found in Github document Learn from.

Welcome to WeChat official account: Wan Mao society, dry cargo Java technology every Monday.

Problem description

In the RESTful interface of Spring MVC or Spring Boot, there are a lot of @ PathVariable annotations, that is, put the parameters in the URL, for example:

@RestController
public class DemoController {
    @GetMapping(value = "/hello/{name}")
    public String helloWithName(@PathVariable String name) {
        return "Hello, " + name;
    }
}

However, in Sentinel, the URL of each request is used as the unique resource name for matching and traffic control, which results in an interface that should be a resource but is treated as multiple resources, so the purpose of traffic control cannot be achieved.

White whoring tips: what are resources? As long as the code surrounded by Sentinel API is a resource, it can be protected by Sentinel. For example, a service provided by an application, or another application called by an application, can even be a piece of code. Each resource has its own unique resource name, which is used to identify this resource.

Welcome to WeChat official account: Wan Mao society, dry cargo Java technology every Monday.

Find the cause of the problem

The root cause of the problem is: how does sentinel use each request URL as a unique resource name? After reading and debugging Sentinel's source code, I found the doFilter method of CommonFilter. The following is the main code:

//Call the filterTarget method to get the URL of the current request
String target = FilterUtil.filterTarget(sRequest);
UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner();
if (urlCleaner != null) {
    target = urlCleaner.clean(target);
}
if (!StringUtil.isEmpty(target)) {
    String origin = parseOrigin(sRequest);
    String contextName = webContextUnify ? WebServletConfig.WEB_SERVLET_CONTEXT_NAME : target;
    ContextUtil.enter(contextName, origin);

    if (httpMethodSpecify) {
        //If the configuration is prefixed with the HTTP method name, the URL is prefixed with the HTTP method name and then the resource name.
        String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + COLON + target;
        urlEntry = SphU.entry(pathWithHttpMethod, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
    } else {
        //If the HTTP method name is not prefixed, the URL is directly used as the resource name.
        urlEntry = SphU.entry(target, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
    }
}

In the above code, we see the whole process of requesting URL as resource name, and also find that there is a UrlCleaner interface, and the request URL will be processed by its clean method. Let's do an article on the UrlCleaner interface.

Welcome to WeChat official account: Wan Mao society, dry cargo Java technology every Monday.

Solution

RestfulPattern

First, we create a class to store URL s and corresponding regular expressions:

package onemore.study.sentineldemo;

import java.util.regex.Pattern;

/**
 * @author Wanmao Society
 */
public class RestfulPattern implements Comparable<RestfulPattern> {
    private Pattern pattern;
    private String realResource;

    public RestfulPattern(Pattern pattern, String realResource) {
        this.pattern = pattern;
        this.realResource = realResource;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public String getRealResource() {
        return realResource;
    }

    @Override
    public int compareTo(RestfulPattern o) {
        return o.getPattern().pattern().compareTo(this.getPattern().pattern());
    }
}

RestfulUrlCleaner

Write another class that implements the UrlCleaner interface, and write your own logic in the clean method:

package onemore.study.sentineldemo;

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Wanmao Society
 */
public class RestfulUrlCleaner implements UrlCleaner {

    private List<RestfulPattern> patterns = new ArrayList<>();

    private RestfulUrlCleaner() {
    }

    /**
     * Create a RestfulUrlCleaner matching the flow control rule
     * @param rules Flow control rules
     * @return RestfulUrlCleaner
     */
    public static RestfulUrlCleaner create(List<FlowRule> rules) {
        RestfulUrlCleaner cleaner = new RestfulUrlCleaner();
        if (rules == null || rules.size() == 0) {
            return cleaner;
        }
        Pattern p = Pattern.compile("\\{[^\\}]+\\}");
        for (FlowRule rule : rules) {
            Matcher m = p.matcher(rule.getResource());
            //If a structure similar to {xxx} is found, it is determined to be a RESTful interface
            if (m.find()) {
                cleaner.patterns.add(
                        new RestfulPattern(Pattern.compile(m.replaceAll("\\\\S+?")), rule.getResource()));
            }
        }
        //Reorder by regular expression
        Collections.sort(cleaner.patterns);
        return cleaner;
    }

    @Override
    public String clean(String originUrl) {
        for (RestfulPattern pattern : patterns) {
            if (pattern.getPattern().matcher(originUrl).matches()) {
                return pattern.getRealResource();
            }
        }
        return originUrl;
    }
}

unit testing

To verify the correctness of the code, let's write a unit test again:

package onemore.study.sentineldemo;

import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import org.junit.Assert;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Wanmao Society
 */
public class RestfulUrlCleanerTest {

    @Test
    public void test(){
        List<FlowRule> rules = new ArrayList<>();
        rules.add(new FlowRule("/hello"));
        rules.add(new FlowRule("/hello/{name}"));
        rules.add(new FlowRule("/hello/{firstName}/{lastName}"));
        rules.add(new FlowRule("/hello/{firstName}/and/{lastName}"));
        RestfulUrlCleaner cleaner = RestfulUrlCleaner.create(rules);

        Assert.assertEquals("/hello", cleaner.clean("/hello"));
        Assert.assertEquals("/hello/{name}", cleaner.clean("/hello/onemore"));
        Assert.assertEquals("/hello/{firstName}/{lastName}", cleaner.clean("/hello/onemore/study"));
        Assert.assertEquals("/hello/{firstName}/and/{lastName}", cleaner.clean("/hello/onemore/and/study"));
    }
}

Run the unit test and there are no errors.

Welcome to WeChat official account: Wan Mao society, dry cargo Java technology every Monday.

Set UrlCleaner

In actual development, traffic control rules may be configured in Redis, ZooKeeper or Apollo. Wherever the flow control rules change, the UrlCleaner is reset every time. We take the hard coded flow control rule as an example:

package onemore.study.sentineldemo;

import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class DemoConfiguration {
    @PostConstruct
    public void initRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("/hello/{name}");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //Set QPS current limit threshold to 1
        rule.setCount(1);
        rules.add(rule);

        WebCallbackManager.setUrlCleaner(RestfulUrlCleaner.create(rules));
        FlowRuleManager.loadRules(rules);
    }
}

So far, the problem of multiple resources of RESTful interface has been solved perfectly.

WeChat official account: Wan Mao Society

Wechat scan QR code

Get more Java technology dry goods

Tags: Java github Spring Junit

Posted on Wed, 13 May 2020 22:44:14 -0400 by Rich464