最近准备用阿里哨兵,发现RESTful接口支持不是很好。可能有的童鞋对Sentinel不是很了解,先简单介绍一下。Sentinel简介Sentinel是阿里巴巴开源的流量防御框架。Github地址为:https://github.com/alibaba/Sentinel。随着微服务的普及,服务之间的稳定性变得越来越重要。Sentinel以流量为切入点,从流量控制、断路器降级、系统负载保护等多个维度保障服务的稳定性。更多介绍可以参考Github文档。问题描述在SpringMVC或SpringBoot中的RESTful接口中,有很多@PathVariable注解,即在URL中放参数,例如:@RestControllerpublicclassDemoController{@GetMapping(value="/hello/{name}")publicStringhelloWithName(@PathVariableStringname){return"Hello,"+name;但是在Sentinel中,将每次请求的URL作为唯一的资源名进行匹配和流量控制,这就创建了一个接口应该是一个资源,却被当成多个资源,达不到流量控制的目的.卖淫技巧:什么是资源?只要被SentinelAPI包围的代码是资源,就可以被Sentinel保护。例如,一个应用程序提供的服务,或者该应用程序调用的另一个应用程序提供的服务,甚至可能是一段代码。每个资源都有自己唯一的资源名称,用来标识这个资源。查找问题原因问题的根源在于:Sentinel是如何将每个请求的URL作为唯一资源名的?阅读和调试Sentinel的源码后,找到了CommonFilter的doFilter方法,下面是主要代码://调用filterTarget方法获取当前请求URLStringtarget=FilterUtil.filterTarget(sRequest);UrlCleanerurlCleaner=WebCallbackManager.getUrlCleaner();if(urlCleaner!=null){target=urlCleaner.clean(target);}if(!StringUtil.isEmpty(target)){Stringorigin=parseOrigin(sRequest);字符串上下文名称=webContextUnify?WebServletConfig.WEB_SERVLET_CONTEXT_NAME:目标;ContextUtil.enter(contextName,origin);if(httpMethodSpecify){//如果配置以HTTP方法名为前缀,则URL将以HTTP方法名作为资源名作为前缀。StringpathWithHttpMethod=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接口上做文章。解决方案RestfulPattern首先,我们先创建一个类来存放URL和对应的正则表达式:{私有模式模式;私有字符串真实资源;publicRestfulPattern(Patternpattern,StringrealResource){this.pattern=pattern;this.realResource=realResource;}publicPatterngetPattern(){返回模式;StringgetRealResource(){返回真实资源;}@OverridepublicintcompareTo(RestfulPatterno){returno.getPattern().pattern().compareTo(this.getPattern().pattern());}}RestfulUrlCleaner再写一个实现UrlCleaner接口类,在clean方法中写自己的逻辑:packageonemore.study.sentineldemo;importcom.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;importcom.alibaba.cspsentinel.slots.block.flow.FlowRule;导入java.util.ArrayList;导入java.util.Collections;导入java.util.List;importjava.util.regex.Matcher;importjava.util.regex.Pattern;/***@author万猫学社*/publicclassRestfulUrlCleanerimplementsUrlCleaner{privateListpatterns=new数组列表<>();privateRestfulUrlCleaner(){}/***根据流控规则创建匹配的RestfulUrlCleaner*@paramrules流控规则*@returnRestfulUrlCleaner*/publicstaticRestfulUrlCleanercreate(Listrules){RestfulUrlCleanercleaner=newRestfulUrlCleaner();if(rules==null||rules.size()==0){returncleaner;}Patternp=Pattern.compile("\\{[^\\}]+\\}");for(FlowRulerule:rules){Matcherm=p.matcher(rule.getResource());//如果找到类似{xxx}的结构,则判断为RESTful接口if(m.find()){cleaner.patterns.add(newRestfulPattern(Pattern.compile(m.replaceAll("\\\\S+?")),rule.getResource()));}}//重新排序Collections.sort(cleaner.patterns);返回清洁工;}@OverridepublicStringclean(StringoriginUrl){for(RestfulPatternpattern:patterns){if(pattern.getPattern().matcher(originUrl).matches()){returnpattern.getRealResource();}}返回originUrl;}}单元测试为了验证代码的正确性,我们来写一个单元测试:packageonemore.study。sentineldemo;importcom.alibaba.csp.sentinel.slots.block.flow.FlowRule;importorg.junit.Assert;importorg.junit.Test;importjava.util.ArrayList;importjava.util.List;/***@author万猫学社*/publicclassRestfulUrlCleanerTest{@Testpublicvoidtest(){Listrules=newArrayList<>();rules.add(newFlowRule("/你好"));规则。添加(新流程规则(“/你好/{name}”));规则。添加(新流程规则(“/你好/{firstName}/{lastName}”));规则。add(newFlowRule("/hello/{firstName}/and/{lastName}"));RestfulUrlCleanercleaner=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"));}}运行它单元测试,发现设置UrlCleaner没有报错在实际开发中,可能在Redis、ZooKeeper或Apollo中配置流控规则。无论在什么地方,每次流量控制规则改变时,都需要重新设置UrlCleaner。以硬编码流控规则为例:;导入com.alibaba.csp.sentinel.slots.block.flow.FlowRule;导入com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;导入org.springframework.context.annotation.Configuration;导入javax.annotation。PostConstruct;导入java.util.ArrayList;导入java.util.List;@ConfigurationpublicclassDemoConfiguration{@PostConstructpublicvoidinitRules(){Listrules=newArrayList<>();FlowRule规则=newFlowRule();rule.setResource("/你好/{name}");rule.setGrade(RuleConstant.FLOW_GRADE_QPS);//设置QPS限流阈值为1rule.setCount(1);规则。添加(规则);WebCallbackManager.setUrlCleaner(RestfulUrlCleaner.create(rules));FlowRuleManager.loadRules(规则);}}至此,RESTful接口多资源的问题就解决了完美解决方案