一些使用jersey的客户端API封装rest客户端优化建议

前言

        很早之前就在用jersey提供的客户端API封装rest客户端,jersey提供的客户端API,简单易用,可惜这个框架有点重,在使用上因为某些环境原因会引发一些不必要的性能问题,这里根据以往的经验列出几条优化的建议。

三步构造rest客户端

如果用jersey提供的API构造客户端,主要分3步,如下:

1. 初始化Client实例(管理通信的基础设施)

2. 构造WebTarget实例(资源定位)

3. 构造Invocation实例(发起调用)

这里不具体说明了,如果需要代码示例,可以看官方文档,说的很清楚了,这是链接:https://jersey.github.io/documentation/latest/client.html#d0e4369

优化在于细节

这里主要说一下在开发过程中需要注意的几个点:

NO.1 Client实例的单例性

Client实例负责管理低层通信的基础设施,是一个重量级对象(源码注释说的),应该尽可能少的构造它的实例。

        这里注意两点:

        1. 不要每次请求构造一个Client实例

        2. 尽可能少的构造它的实例。注意我说的尽可能少,下面解释下:

        虽然标题我列的是单例,但并不一定全局就构造一个实例,可以视情况而定,考虑用懒初始化的方式构造几个不同的客户端实例缓存起来,比如:如果是普通的http请求了,就用一个基本配置,没有多余特性能力支持配置客户端调用就行;如果想让本次请求支持https协议,那就用一个支持https协议调用的客户端;如果是一些文件上传下载等请求,就用一个注册 了Multipart特性的客户端;请求想支持连接池,那就用支持连接池的客户端...等等。

        但是,千万不要想着全局只用一个客户端注册了以上需要的所有特性,这并不是一个好主意。其实我最开始就犯过这样的错误,直到后来在性能上遇到瓶颈研究源码时才明白,在客户端配置上注册的某些特性支持,即使当前请求并不需要,也可能会有相关的性能或耗时操作,相反真正需要这些特性支持的请求,偶尔才会过来几条。

        不要疑惑哪种请求用哪种客户端,这应该是开发的时候按照相关协议或约定来区分的。总之,尽量保证每个客户端实例的轻量特性。

NO.2 不要频繁构造无参的ClientConfig实例

       只在构造Client实例的时候调用ClientConfig的无参构造方法,其它情况下就不要调用了。Client的单例性,也保证开发时调用它无参构造方法的次数限制。

       虽然看起来只是new了一个ClientConfig的空构造方法,实例化了一个ClientConfig对象,但是在调用它的无参构造方法时,会初始化它的一个连接器提供者的属性,而在初始化这个属性的时候,会扫描应用类路径、扩展类路径、引导类路径下的所有jar包文件,期望通过它的SPI扩展方式查找到一个连接器提供者,其实很多时候我们不会去使用它的这些扩展,所以最终因为没找到还是会用默认的连接器。但是已经做了很多无用功,并且在扫找类路径遍历文件的时候,每次循环都会有一个同步处理的操作(调用的JDK的JarFile内的方法)。有同步就有锁,这样就不好了,并发高的时候,CPU调度慢,锁占有时间久,资源释放慢,就容易有大量线程因此阻塞。而这个锁间接的是我们自己加上的。

       所以不要无谓的调用 ClientConfig的无参构造方法。

NO.3 使用连接池

使用连接池,见名知义,我觉得这个好处就不用我多说了,但是有一些需要注意的地方:

      1. 默认的连接器是HttpUrlConnector,这个是不支持连接池的,使用连接池的话可以考虑更换其它连接器,比如:ApacheConnector。当然也可以自定义。

      2. 读取超时/连接超时配置,连接池本身就是缓存了一些闲置可用的长连接,所以,对于这些连接还怎么设置它的一次请求中的连接超时或者读取超时,其它我也不知道。

NO.4 关闭类路径扫描

官方文档中有这么一段话:

Jersey uses a common Java Service Provider mechanism to obtain all service implementations. It means that Jersey scans the whole class path to find appropriate META-INF/services/ files. The class path scanning may be time consuming. The more jar or war files on the classpath the longer the scanning time. In use cases where you need to save every millisecond of application bootstrap time, you may typically want to disable the services provider lookup in Jersey.

请注意,这个类路径扫描是存在同步操作的,另外它的耗时是跟类路径下jar包或war包的数量相关。

所以jersey提供了关闭这个特性,但是又不影响核心功能的方式:

Since it is possible to configure all SPI implementation classes or instances manually in your Application subclass, disabling services lookup in Jersey does not affect any functionality of Jersey core modules and extensions and can save dozens of ms during application initialization in exchange for a more verbose application configuration code.

The services lookup in Jersey (enabled by default) can be disabled via a dedicated CommonProperties.METAINF_SERVICES_LOOKUP_DISABLE property. There is a client/server counter-part that only disables the feature on the client or server respectively:ClientProperties.METAINF_SERVICES_LOOKUP_DISABLE/ServerProperties.METAINF_SERVICES_LOOKUP_DISABLE. As in all other cases, the client/server specific properties overrides the value of the related common property, when set.

当然了,在配置中启用这个属性并不代表就不会扫描类路径了,还可能在其它地方,比如如果启用Multipart的特性支持,每次请求就会扫描类路径下是否有相关配置,这个关闭不了,所以前面也建议针对不同请求使用不同的客户端。另外,jersey代码内部有一些方法调用依然会触发类路径扫描,但是经过我仔细研究它的源码,找到了一种方法通过自定义扩展的方式可以避免,后续有需要会再说明。

其它的突然之前也想不起来了,最后一个建议,框架选型很重要,比如:如果只是功能要求不多,但是性能要高,可以考虑找一款更轻量级的rest客户端,就不要考虑用jersey了。

 

你可能感兴趣的:(------》jersey)