1. 程式人生 > >想要年薪百萬,阿里Sentinel支援RESTful介面都搞不定?

想要年薪百萬,阿里Sentinel支援RESTful介面都搞不定?

最近正準備用阿里Sentinel,發現RESTful介面支援的不是很好。有些童鞋可能對Sentinel不是很瞭解,我們先簡單介紹一下。 ### Sentinel簡介 ![](https://img2020.cnblogs.com/blog/145687/202005/145687-20200514091403697-2066806172.png) Sentinel是一套阿里巴巴開源的流量防衛框架,Github地址是:[https://github.com/alibaba/Sentinel](https://github.com/alibaba/Sentinel)。隨著微服務的流行,服務與服務之間的穩定性越來越重要。Sentinel以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。 更多介紹可以在[Github文件](https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D)中瞭解。 歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。 ### 問題描述 在Spring MVC或者Spring Boot中的RESTful介面中,有大量的@PathVariable註解,也就是把引數放在URL裡,比如: ```java @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的原始碼後,我找到`CommonFilter`的`doFilter`方法,以下是主要程式碼: ```java //呼叫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和對應的正則表示式: ```java package onemore.study.sentineldemo; import java.util.regex.Pattern; /** * @author 萬貓學社 */ public class RestfulPattern implements Comparable { 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`方法中寫自己的邏輯: ```java 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 patterns = new ArrayList<>(); private RestfulUrlCleaner() { } /** * 根據流量控制規則建立與之匹配的RestfulUrlCleaner * @param rules 流量控制規則 * @return RestfulUrlCleaner */ public static RestfulUrlCleaner create(List 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; } } ``` #### 單元測試 為了驗證程式碼的正確性,我們再寫一下單元測試: ```java 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 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`。我們就以硬編碼流量控制規則為例: ```java 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 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技術乾貨