天天看點

一些使用jersey的用戶端API封裝rest用戶端優化建議前言三步構造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了。

繼續閱讀