zoukankan      html  css  js  c++  java
  • 想要年薪百万,阿里Sentinel支持RESTful接口都搞不定?

    最近正准备用阿里Sentinel,发现RESTful接口支持的不是很好。有些童鞋可能对Sentinel不是很了解,我们先简单介绍一下。

    Sentinel简介

    Sentinel是一套阿里巴巴开源的流量防卫框架,Github地址是:https://github.com/alibaba/Sentinel。随着微服务的流行,服务与服务之间的稳定性越来越重要。Sentinel以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

    更多介绍可以在Github文档中了解。

    欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

    问题描述

    在Spring MVC或者Spring Boot中的RESTful接口中,有大量的@PathVariable注解,也就是把参数放在URL里,比如:

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

    但是在Sentinel中把每一次请求的URL作为唯一的资源名,进行匹配和流量控制的,这就造成了一个接口本应是一个资源却被当作多个资源看待,无法达到流量控制的目的。

    白嫖小贴士:什么是资源?只要通过 Sentinel API 包围起来的代码,就是资源,能够被 Sentinel 保护起来。例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。每一个资源都有自己唯一的资源名,用于标识这个资源。

    欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

    查找问题原因

    问题的根本原因就在于:Sentinel是如何把每一次请求URL作为唯一的资源名的?阅读和调试Sentinel的源码后,我找到CommonFilterdoFilter方法,以下是主要代码:

    //调用filterTarget方法获取当前请求的URL
    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) {
            //如果配置加HTTP方法名做前缀,URL前加HTTP方法名后作为资源名。
            String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + COLON + target;
            urlEntry = SphU.entry(pathWithHttpMethod, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
        } else {
            //如果不加HTTP方法名做前缀,就直接使用URL作为资源名。
            urlEntry = SphU.entry(target, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
        }
    }
    

    在上面的代码中,我们看见了请求URL作为资源名的整个过程,同时也发现有一个UrlCleaner接口,请求URL会经过它的clean方法进行处理。我们就在这个UrlCleaner接口上做文章了。

    欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

    解决方案

    RestfulPattern

    首先我们先创建一个类,用来存放URL和对应的正则表达式:

    package onemore.study.sentineldemo;
    
    import java.util.regex.Pattern;
    
    /**
     * @author 万猫学社
     */
    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

    再写一个实现UrlCleaner接口的类,在clean方法中写自己的逻辑:

    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 万猫学社
     */
    public class RestfulUrlCleaner implements UrlCleaner {
    
        private List<RestfulPattern> patterns = new ArrayList<>();
    
        private RestfulUrlCleaner() {
        }
    
        /**
         * 根据流量控制规则创建与之匹配的RestfulUrlCleaner
         * @param 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());
                //如果发现类似{xxx}的结构,断定其为RESTful接口
                if (m.find()) {
                    cleaner.patterns.add(
                            new RestfulPattern(Pattern.compile(m.replaceAll("\\S+?")), rule.getResource()));
                }
            }
            //根据正则表达式重新排序
            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;
        }
    }
    

    单元测试

    为了验证代码的正确性,我们再写一下单元测试:

    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 万猫学社
     */
    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"));
        }
    }
    

    运行一下单元测试,发现没有错误。

    欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

    设置UrlCleaner

    在实际开发中,流量控制规则可能配置在Redis、ZooKeeper或者 Apollo中。无论在哪里,流量控制规则每次发生变更时都要重新设置UrlCleaner。我们就以硬编码流量控制规则为例:

    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);
            //设置QPS限流阈值为1
            rule.setCount(1);
            rules.add(rule);
    
            WebCallbackManager.setUrlCleaner(RestfulUrlCleaner.create(rules));
            FlowRuleManager.loadRules(rules);
        }
    }
    

    至此,RESTful接口多资源的问题被完美解决。

    微信公众号:万猫学社

    微信扫描二维码

    获得更多Java技术干货

  • 相关阅读:
    数据库调优2
    数据库调优
    SQL优化
    支付宝/阿里面试题
    Servlet 工程 web.xml 中的 servlet 和 servlet-mapping 标签 《转载》
    《转载》struts旅程《2》
    《转载》struts旅程《1》
    jsp 自定义标签
    body-content取值的意义
    jsp页面中jstl标签详解
  • 原文地址:https://www.cnblogs.com/heihaozi/p/12886383.html
Copyright © 2011-2022 走看看