springMVC在restful風(fēng)格的性能優(yōu)化方案
springMVC在restful風(fēng)格的性能優(yōu)化
目前,restful的接口風(fēng)格很流行,使用springMVC來搭配restful也是相得益彰。如下,使用@PathVariable注解便可以獲取URL上的值。
@RequestMapping(value = "restful/{name}", method = RequestMethod.GET) public String restful(@PathVariable String name){ return name; }
不過如果你認(rèn)真的研究過springMVC就會(huì)發(fā)現(xiàn),restful風(fēng)格的接口的性能會(huì)大大低于正常形式的springMVC接口。比如下面這種方式。
@RequestMapping(value = "norestful", method = RequestMethod.GET) public String norestful(@RequestParam String name){ return name; }
測(cè)試
為了看到效果,我先進(jìn)行了測(cè)試,工具是Apache-Jmeter
測(cè)試參數(shù),并發(fā)量50,總量10000次。
1、非restful接口
2、restful接口
對(duì)比很明顯,非restful接口的性能是restful接口的1.5倍左右,而且restful接口隨著@Requestmapping接口數(shù)量的增多會(huì)越來越慢,而非restful接口不會(huì)。
不止如此,非restful接口的最大響應(yīng)時(shí)間是67ms,而restful接口的最大響應(yīng)時(shí)間達(dá)到了381ms,這在極端情況下很可能會(huì)造成請(qǐng)求超時(shí)。
匹配原理
先講一下springMVC的路徑匹配邏輯吧。springMVC的請(qǐng)求主要在DispatcherServlet中處理,而請(qǐng)求分發(fā)規(guī)則則在doDispatch()方法中完成。
最后處理邏輯在AbstractHandlerMethodMapping類的lookupHandlerMethod方法中進(jìn)行。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<Match>(); List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { // No choice but to go through all mappings... addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); }
這段代碼中匹配邏輯有三:
1、List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
這個(gè)方法是非常直觀的根據(jù)URL來獲取,springMVC會(huì)在初始化的時(shí)候建立URL和相應(yīng)RequestMappingInfo的映射。如果不是restful接口,這里就可以直接獲取到了。
2、如果1中已經(jīng)獲取到,則調(diào)用方法addMatchingMappings(directPathMatches, matches, request)進(jìn)行匹配校驗(yàn)。
3、如果1中未獲取到匹配方法信息,則調(diào)用方法addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);進(jìn)行全局(all mappings)掃描匹配(this.mappingRegistry.getMappings().keySet())。且會(huì)把所有的RequestMappingInfo都遍歷完才會(huì)停止,也就是說項(xiàng)目中的@RequestMapping方法越多,這個(gè)匹配的效率就越低,性能越差。
在遍歷過程中,SpringMVC首先會(huì)根據(jù)@RequestMapping中的headers, params, produces, consumes, methods與實(shí)際的HttpServletRequest中的信息對(duì)比,剔除掉一些明顯不合格的RequestMapping。
如果以上信息都能夠匹配上,那么SpringMVC會(huì)對(duì)RequestMapping中的path進(jìn)行正則匹配,剔除不合格的。
接下來會(huì)對(duì)所有留下來的候選@RequestMapping進(jìn)行評(píng)分并排序。最后選擇分?jǐn)?shù)最高的那個(gè)作為結(jié)果。
評(píng)分的優(yōu)先級(jí)為:
path pattern > params > headers > consumes > produces > methods
綜上所述,當(dāng)使用非restful接口時(shí)就會(huì)直接獲取對(duì)應(yīng)的HandlerMethod來處理請(qǐng)求,但使用restful接口時(shí),就會(huì)每次遍歷所有的方法來查找,性能差由此形成。
優(yōu)化方案
原理:
1、在每個(gè)@RequestMapping中添加接口對(duì)應(yīng)服務(wù)名的信息。
2、實(shí)現(xiàn)自己定義的HandlerMethod查詢邏輯,在HandlerMethod注冊(cè)時(shí)記錄與之對(duì)應(yīng)的服務(wù)名,在查詢時(shí)通過HTTP請(qǐng)求頭中的服務(wù)名查表獲得HandlerMethod。
實(shí)現(xiàn):
每次請(qǐng)求都執(zhí)行這段復(fù)雜的匹配邏輯是不可取的。我們要做的就是找辦法繞開它。spring是一個(gè)符合開閉原則的框架。對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。它提供了很多擴(kuò)展性給我們。
springmvc中,AbstractHandlerMethodMapping.MappingRegistry里提供了@Requestmapping中name屬性和HandlerMethod的映射如下
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();
我們剛好可以使用它。
另外,我們看到實(shí)現(xiàn)匹配邏輯的方法HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);其本身是個(gè)protected方法,
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception
由此便可以在子類中擴(kuò)展它。
代碼:
我使用基于java config的注解配置.
1、繼承WebMvcConfigurationSupport類,復(fù)寫createRequestMappingHandlerMapping方法返回自定義的RequestMappingHandlerMapping類。
@Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { return new RestfulRequestMappingHandlerMapping(); }
2、繼承RequestMappingHandlerMapping類
2.1重寫lookupHandlerMethod方法,完成自己的查找邏輯。
@Override protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { //自己的查找邏輯,如果找不到,再執(zhí)行原有的邏輯,以免出現(xiàn)錯(cuò)誤情況 HandlerMethod handlerMethod = lookupHandlerMethodHere(lookupPath, request); if (handlerMethod == null) handlerMethod = super.lookupHandlerMethod(lookupPath, request); return handlerMethod; } //自己的查找邏輯,根據(jù)從請(qǐng)求頭中獲取服務(wù)名servicename,進(jìn)行匹配查找 private HandlerMethod lookupHandlerMethodHere(String lookupPath, HttpServletRequest request) { String servicename = request.getHeader("servicename"); if (!StringUtils.isEmpty(servicename)) { List<HandlerMethod> methodList = this.getHandlerMethodsForMappingName(servicename); if (methodList.size() > 0){ HandlerMethod handlerMethod = methodList.get(0); RequestMappingInfo requestMappingInfo = mappingLookup.get(handlerMethod); handleMatch(requestMappingInfo, lookupPath, request); return handlerMethod; } } return null; }
2.2因?yàn)镽ESTful接口存在@PathVariable,我們還需要調(diào)用handleMatch方法來將HTTP請(qǐng)求的path解析成參數(shù)。然而這個(gè)方法需要的參數(shù)是RequestMappingInfo,并不是HandlerMethod,SpringMVC也沒有提供任何映射。
做法:重寫registerHandlerMethod方法,再初始化的時(shí)候構(gòu)建一個(gè)從HandlerMethod—>RequestMappingInfo的反向映射。
//映射map private final Map<HandlerMethod, RequestMappingInfo> mappingLookup = new LinkedHashMap<HandlerMethod, RequestMappingInfo>(); @Override protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) { HandlerMethod handlerMethod = createHandlerMethod(handler, method); mappingLookup.put(handlerMethod, mapping); super.registerHandlerMethod(handler, method, mapping); }
由此,springMVC優(yōu)化邏輯編寫完成??创a量很少,但想通過自己寫出來,需要對(duì)springMVC有相當(dāng)了解,深入理解springMVC的可擴(kuò)展點(diǎn)。
最終測(cè)試
看下優(yōu)化過后的restful接口
吞吐量和非restful接口差不多,各項(xiàng)應(yīng)能都接近,達(dá)到預(yù)期效果。
spring restful使用中遇到的一個(gè)性能問題
在使用spring restful開發(fā)過程中遇到個(gè)棘手的問題,解決后來做個(gè)備注。希望其他遇到相同問題的朋友可以參考下。
客戶端訪問rest api速度過慢,每次請(qǐng)求超過1秒鐘
原因:
返回類型是強(qiáng)類型,SPRING將其序列化為json對(duì)象消耗時(shí)間過長。
解決方案:
返回類型改為String,改動(dòng)很小,只需要將原來的強(qiáng)類型對(duì)象通過fastjson的JSON.toJSONString方法進(jìn)行轉(zhuǎn)換即可;
@RequestMapping加參數(shù)produces = { "application/json;charset=UTF-8" }
通過以上修改,原先1秒鐘左右的請(qǐng)求變?yōu)?0-50毫秒。雖然解決,但是否是spring本身問題還是配置問題,抑或代碼寫法問題,還未深究,暫時(shí)先趕項(xiàng)目進(jìn)度,項(xiàng)目完成后再回頭查找具體原因。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringCloud項(xiàng)目中集成Sentinel問題
在SpringCloud項(xiàng)目中集成Sentinel,可以實(shí)現(xiàn)流量控制、熔斷降級(jí)等功能,提升系統(tǒng)穩(wěn)定性和可用性,集成步驟包括添加Sentinel依賴、配置控制臺(tái)地址、啟動(dòng)控制臺(tái)、配置限流熔斷規(guī)則、使用注解和集成SpringCloudGateway,這有助于處理高并發(fā)場景,保護(hù)服務(wù)穩(wěn)定運(yùn)行2024-10-10Java easyexcel導(dǎo)出報(bào)內(nèi)存溢出的問題解決
在Java開發(fā)時(shí),使用EasyExcel處理大數(shù)據(jù)量導(dǎo)出可能遇到內(nèi)存溢出問題,本文深入分析了內(nèi)存溢出的原因,并提出了優(yōu)化策略,感興趣的可以了解一下2024-10-10

為什么ConcurrentHashMap的key value不能為null,map可以?

Java編程實(shí)現(xiàn)鄰接矩陣表示稠密圖代碼示例

SpringBoot?整合Redis?數(shù)據(jù)庫的方法