Spring urlMapping

Posted on 2016-11-13 15:30  blesslyy  阅读(1473)  评论(0)    收藏  举报

背景

某url性能测试表明,qps单机最高只有4000多,虽然靠堆机器可以解决问题,但是显然不是什么优雅的方案。

试着把controller里的所有的逻辑都屏蔽,只是简单的返回hello world,发现并没有什么用,略略提高了一点,但还是不到5000。显然是什么地方有个坑。

问题查找过程略去不表,坑有两个:

  1. 用了log4j,1.x的版本,这货的性能。。。嗯,不说了,都是泪
  2. spring url mapping也有点坑,在开trace日志的前提下(要知道此时是log也消耗性能),大概消耗了30%的性能。

google & 代码分析

google来google去,看了看spring的源代码,4.2.x。发现这篇文章写的不错。基本说明白了问题。
spring url

盗一张图来描述一下,spring处理url的过程

解释一下什么叫 完全匹配呢?

@RequestMapping(path = "/list/cityId/123", method = RequestMethod.GET) 是完全匹配
@RequestMapping(path = "/list/cityId/{cityId}", method = RequestMethod.GET) 不是完全匹配

看出问题来了吗?
如果path不是一个完全匹配的url,那么就需要遍历所有的path,依次做正则匹配。这个。。。不慢也没有道理吧。慢也就算了,随着一个项目里的url越来越多,那么匹配速度就会越来越慢。。。。

达达的方案

服务端:
1. 在每个@RequestMapping中添加接口对应服务名的信息。
2. 实现自己定义的HandlerMethod查询逻辑,在HandlerMethod注册时记录与之对应的服务名,在查询时通过HTTP请求头中的服务名查表获得HandlerMethod。
客户端:
1. 调用服务时将服务名加入到HTTP请求头中
分析:
* 这样的查询时间复杂度是O(1)的,典型的空间换时间。理论上使用这样的查找逻辑的效率和非RESTful接口的效率是一样的。
* 由于HandlerMethod的注册是在服务启动阶段完成的,且在运行时不会发生改变,所以不用考虑注册的效率以及并发问题。
* SpringMVC提供了一系列的方法可以让我们替换它的组件,所以该方案的可行性很高。

改进

这样做的话,虽然可以解决问题,但是客户端需要配合做修改,不是一个优雅的方案。

实际上,在一个具体的项目中,一般来说path参数一般也就是两种,int和string,比如

/city/id/{id:[\d]+}
/city/shared/

实际上,上面的path,可以用正则表达式:

@RequestMapping(path = "/list/cityId/{cityId}", method = RequestMethod.GET) 

也就是说上面的path pattern可以表示成$ /city/id/{variable} $ 的形式。
那么一个用户的url请求呢? 比如/city/id/123?那就更简单了,/\d{2, }即可。也可以表示成$ /city/id/{variable} $ 的形式。

即用户的请求这可以映射到VariableLikeUrl,通过VariableLikeUrl可以直接找到具体的处理函数。

public class OwnUrlMapping extends RequestMappingHandlerMapping {



    private static final Pattern VARIABLE_PATTERN = Pattern.compile("/\\{[^/]+:[^/]+\\}");
    protected Map<String, Pair<RequestMappingInfo, HandlerMethod>> url2method;

    @Override
    protected void handlerMethodsInitialized(Map<RequestMappingInfo, HandlerMethod> handlerMethods) {
        url2method = new HashMap<>();
        handlerMethods.forEach((info, method) -> {

            Info2key(info).forEach(s -> {
                        RequestMappingInfo to variable like url
            s = VariableLikeUrl
                        if (url2method.containsKey(s)) {
                            throw new IllegalStateException(
                                    "Ambiguous mapping.  " + s + " --> " +
                                            method + " alread " + url2method.get(s));

                        }
                        url2method.put(s, new Pair<>(info, method));
                    }
            );
        });
    }

    @Override
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	    request to variableLikeUrl
        String url = request2case(request.getRequestURI()) + ":" + request.getMethod();
        Pair<RequestMappingInfo, HandlerMethod> method = url2method.get(url);
        if (method != null) {
            // If find it, call it directly
            handleMatch(method.getFirst(), lookupPath, request);
            return method.getSecond();
        } else {
            // Could not find, call super matching method
            LOG.warn("no matching for " + url);
            return super.lookupHandlerMethod(lookupPath, request);
        }
    }
}

总结

好处

  1. 性能可以得到有效的提升。
  2. 客户端完全没有修改
  3. 服务器controller的代码也不需要什么修改。

问题

  1. 项目by项目,如果出现不按格式写的url,就废了。。。
  2. 实际上spring考虑的东西还是比较多的,处理url,method 貌似还有很多别的东东,上述方法。
  3. 和spring的版本绑定,反正4.x版本都适用。

基本上为项目定制了一个url mapping规则,不能替代spring原有的mapping函数,但是依然有推广意义。毕竟没有几个项目把RequstMapping的所有参数都用全了不是。

博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3