在 Singleton 中使用 Apache CloseableHttpClient 的风险

Risks of using Apache CloseableHttpClient in a Singleton

我正在使用 Apache Http Client 4.5.3,目前正在重构一些代码。

目前我有一个单例 Util,它有几个方法,其职责是使用 gets、posts、patches 等来命中 API。之前,我们一直使用 HttpClientBuilder 来构造一个CloseableHttpClient 每个方法的每次调用的对象。大致来说,Singleton 的架构是这样的:

import com.google.gson.Gson
import org.apache.http.client.methods.{HttpGet, HttpPost}
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.{CloseableHttpClient, HttpClientBuilder}
import org.apache.http.util.EntityUtils
import org.json4s.DefaultFormats
import org.json4s.jackson.JsonMethods.parse

object ApiCallerUtil {

  case class StoreableObj(name:String, id:Long)
  case class ResponseKey(key:Long)

  def getKeyCall(param:String): ResponseKey = {
    implicit val formats = DefaultFormats
    val get = new HttpGet("http://wwww.someUrl.com/api/?value=" + param)
    val client:CloseableHttpClient = HttpClientBuilder.create().build()
    val response = client.execute(get)
    try {
      val entity = response.getEntity
      val entityStr = EntityUtils.toString(entity)
      parse(entityStr).extract[ResponseKey]
    } finally {
      response.close()
      client.close()
    }
  }

  def postNewObjCall(param:String, i:Long): Boolean = {
    val post = new HttpPost(("http://wwww.someUrl.com/api/createNewObj"))
    val client = HttpClientBuilder.create().build()
    post.setHeader("Content-type", "application/json")
    val pollAsJson = new Gson().toJson(StoreableObj(param, i))
    post.setEntity(new StringEntity(pollAsJson))
    val response = client.execute(post)
    try {
      if (response.getStatusLine.getStatusCode < 300) true else false
    } finally {
      response.close()
      client.close()
    }
  }

  //... and so on

}

关于如何使用它的注意事项 - 我们的系统中有许多 类 使用此 Singleton Util 来调用 API。这个 Singleton 将经历短期的大量使用,其中几个 类 将以高频率(在几分钟内高达 @1000 次)点击相同的调用,并且还会在很长一段时间内多次点击一段时间(一小时一次或两次),或者一次几个小时根本不做。此外,它命中的所有 URL 都将以相同的 URL 开头(例如 www.someUrl.com/api/

但我想知道在 val client = HttpClientBuilder.create().build 被调用一次作为对象内可访问变量的私有 val 的情况下实现它是否有意义。这样它只在对象实例化时创建一次。这是我暂停的地方,Apache documentation 确实说了这两件事:

1.2.1. [Closeable]HttpClient implementations are expected to be thread safe. It is recommended that the same instance of this class is reused for multiple request executions.

1.2.2. When an [Closeable]HttpClient instance is no longer needed and is about to go out of scope it is important to shut down its connection manager to ensure that all connections kept alive by the manager get closed and system resources allocated by those connections are released.

我已经通读了大部分文档,但对以下问题没有可靠的答案:

  1. 将 CloseableHttpClient 实例作为私有全局变量是否存在任何风险?我担心某些东西如果过时可能会关闭,并且我必须在一段时间后重新实例化它,或者在大量使用的情况下,它会造成太多瓶颈。根据上面的 #1.2.2,变量将 "never" 超出范围,因为它是一个单例对象。但是由于我只构建客户端并在我进行时将 HttpRequest 对象传递给它,而不是单独将它连接到请求之外的 API ,所以这似乎无关紧要。

  2. 由于这个 ApiCallerUtil 单例的使用方式的性质,使用它们的 HttpClientConnectionManager 或。 PoolingHttpClientConnectionManager 以保持与 www.someUrl.com/api/ 的稳定连接?性能提升是否值得?到目前为止,当前的实现似乎没有任何严重的性能缺陷。

感谢您的反馈!

  1. 有none(基于我15+年的HttpClient经验)。

  2. 这实际上取决于各种因素(TLS 会话握手的开销等)。我想人们真的希望确保通过持久连接执行一系列相关请求。在长时间不活动期间,可能需要从连接池中逐出所有空闲连接。这有一个额外的好处,可以减少 运行 成为陈旧(半关闭)连接问题的可能性。