簡體   English   中英

在多線程環境中重用 JAX RS 客戶端(使用 resteasy)

[英]Reusing JAX RS Client in multi-threaded environment (with resteasy)

根據文檔,

“客戶端是管理客戶端通信基礎設施的重量級對象。客戶端實例的初始化和處置可能是一項相當昂貴的操作。因此建議在應用程序中只構造少量的客戶端實例。”

好的,我正在嘗試將 Client 本身和 WebTarget 實例緩存在 static 變量中,在多線程環境中調用 someMethod() :

private static Client client = ClientBuilder.newClient();
private static WebTarget webTarget = client.target("someBaseUrl");
...
public static String someMethod(String arg1, String arg2)
{
    WebTarget target = entrTarget.queryParam("arg1", arg1).queryParam("arg2", arg2);
    Response response = target.request().get();
    final String result = response.readEntity(String.class);
    response.close();
    return result;
}

但有時(不總是)我得到一個例外:

BasicClientConnManager 的無效使用:連接仍然分配。 確保在分配另一個連接之前釋放連接。

Client/WebTarget 如何正確重用/緩存? JAX RS 客戶端 API 可以嗎? 或者我必須使用一些特定於框架的功能(resteasy/jersey)你能提供一些例子或文檔嗎?

您的實現不是線程安全的。 當兩個線程同時訪問someMethod時,它們共享同一個Client並且當第一個請求未完成時,將嘗試發出第二個請求。

你有兩個選擇:

  • 手動同步對ClientWebTarget的訪問。
  • 讓容器通過使用@javax.ejb.Singleton注釋封閉類型來管理並發性,這可以保證線程安全。 (參見EJB規范的第4.8.5章)

如果在容器管理環境中使用someMethod ,我會使用第二種方法。

由於此問題在編寫時仍處於打開狀態(版本3.0.X),因此RESTEASY:不推薦使用Apache類清理

您可以更深入地使用較新的,不推薦使用的類來創建resteasy客戶端。 您還可以更好地控制池的使用方式等。

這是我做的:

// This will create a threadsafe JAX-RS client using pooled connections.
// Per default this implementation will create no more than than 2
// concurrent connections per given route and no more 20 connections in
// total. (see javadoc of PoolingHttpClientConnectionManager)
PoolingHttpClientConnectionManager cm =
        new PoolingHttpClientConnectionManager();

CloseableHttpClient closeableHttpClient =
        HttpClientBuilder.create().setConnectionManager(cm).build();
ApacheHttpClient4Engine engine =
        new ApacheHttpClient4Engine(closeableHttpClient);
return new ResteasyClientBuilder().httpEngine(engine).build();

還要確保在撥打電話后釋放連接 調用response.close()會為你做這個,所以可能把它放在finally塊中。

首先,不要重用WebTarget。 為簡單起見,您始終可以創建新的WebTarget。

其次,如果您正在使用Resteasy,則可以將Resteasy客戶端提供的依賴項添加到您的項目中。 Gradle中的示例:

    provided 'org.jboss.resteasy:resteasy-client:3.0.14.Final'

然后,您可以像這樣創建連接:

        ResteasyClientBuilder builder = new ResteasyClientBuilder();
        builder.connectionPoolSize(200);

沒有必要設置maxPooledPerRoute,這是由RestEasy自動設置的(可以在RestEasyClientBuilder類源代碼中找到)。

設置connectionPoolSize時,在重用Client時不會再出錯,您可以在整個應用程序中重復使用它們。 我在許多項目中嘗試過這個解決方案,但實際上效果很好。 但是當您將應用程序部署到非resteasy容器(如Glassfish)時,您的代碼將無法運行,您將不得不再次使用ClientBuilder類。

不幸的是,文檔對於什么可以和不能安全地重用不是很清楚。 如有疑問,請不要重復使用。 但是,如果您決心將開銷降至最低,則可以根據調用的方法安全地重用大多數對象。

從您的代碼開始,這里有一些關於正在發生的事情的評論:

// (1) Store an instance of Client with its own configuration
private static Client client = ClientBuilder.newClient();
// (2) Store an instance of WebTarget with its own configuration (inherited from client)
private static WebTarget webTarget = client.target("someBaseUrl");
...
public static String someMethod(String arg1, String arg2)
{
    // (3) New instance of WebTarget (copy entrTarget config) with "arg1" param
    // (4) New instance of WebTarget (copy anonymous config) with "arg2" param
    WebTarget target = entrTarget.queryParam("arg1", arg1).queryParam("arg2", arg2);
    // (5) New instance of Invocation.Builder (copy target config)
    // (6) Invoke GET request with stored configuration
    Response response = target.request().get();
    final String result = response.readEntity(String.class);
    response.close();
    return result;
}

我按原樣評論了代碼,但我猜(3)應該引用了 static webTarget字段。

這里創建了很多對象。 每次創建 object 時,都會有一個新實例及其自己的配置副本(因此不會影響其前身)。 在這種特殊情況下,應該沒有競爭條件,但肯定有一些方法可以 go 出錯。

如果在(3)之前或之前你做過這樣的事情(假設這些是合法的財產):

WebTarget target = webTarget.property("foo", fooProperty).queryParam("arg1", arg1);

然后您將更改 static webTarget字段的配置,這可能會導致競爭條件。 有很多方法可以更改 static 字段的配置,因此您要么需要小心保護它們,要么根本不使用它們。

另外,請注意,幾乎每個從原始client生成的 object 都會引用它,以確定 httpEngine 是否已關閉。 因此,除非您試圖優雅地關閉您的應用程序,否則關閉客戶端可能永遠不是一個好主意。

我通過挖掘源代碼發現了所有這些,因為真的沒有很好的參考。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM