Site icon AppTractor

Как OkHttpClient повышает сетевую производительность

Замечательные абстракции, предоставляемые сетевой библиотекой OkHttp от компании Square, как правило, заслоняют собой оптимизацию, которая часто остается незамеченной разработчиками. А ведь библиотека использует стандартное соединение на основе TCP handshake для новых запросов и предлагает пул соединений по умолчанию из 5 соединений (каждое активно в течение 5 минут) для повторного использования ранее установленного TCP-соединения для последующих запросов.

class ConnectionPool internal constructor(
  internal val delegate: RealConnectionPool
) {

  constructor(
    maxIdleConnections: Int,
    keepAliveDuration: Long,
    timeUnit: TimeUnit
  ) : this(RealConnectionPool(
      taskRunner = TaskRunner.INSTANCE,
      maxIdleConnections = maxIdleConnections,
      keepAliveDuration = keepAliveDuration,
      timeUnit = timeUnit
  ))

  constructor() : this(5, 5, TimeUnit.MINUTES)

   ...
}

Под капотом

Как пул соединений повышает производительность сети? Это можно проверить с помощью специфического EventListener, связанного с вызовом OkHttp.

private class OkHttpEventListenerFactory: EventListener.Factory {
    override fun create(call: Call): EventListener {
        return object: EventListener() {

            override fun callStart(call: Call) {
                Log.d("OkHttp", "CallStart")
            }

            override fun callEnd(call: Call) {
                Log.d("OkHttp", "CallEnd")
            }

            override fun dnsStart(call: Call, domainName: String) {
                Log.d("OkHttp", "dnsStart")
            }

            override fun dnsEnd(
                call: Call,
                domainName: String,
                inetAddressList: List<InetAddress>
            ) {
                Log.d("OkHttp", "dnsEnd")
            }

            override fun secureConnectStart(call: Call) {
                Log.d("OkHttp", "secureConnectStart")
            }

            override fun secureConnectEnd(call: Call, handshake: Handshake?) {
                Log.d("OkHttp", "secureConnectEnd")
            }

            override fun connectStart(
                call: Call,
                inetSocketAddress: InetSocketAddress,
                proxy: Proxy
            ) {
                Log.d("OkHttp", "connectStart")
            }

            override fun connectEnd(
                call: Call,
                inetSocketAddress: InetSocketAddress,
                proxy: Proxy,
                protocol: Protocol?
            ) {
                Log.d("OkHttp", "connectEnd")
            }

            override fun connectionAcquired(call: Call, connection: Connection) {
                Log.d("OkHttp", "connectionAcquired")
            }

            override fun connectionReleased(call: Call, connection: Connection) {
                Log.d("OkHttp", "connectionReleased")
            }

            override fun requestHeadersStart(call: Call) {
                Log.d("OkHttp", "requestHeadersStart")
            }

            override fun requestHeadersEnd(call: Call, request: Request) {
                Log.d("OkHttp", "requestHeadersEnd")
            }

            override fun requestBodyStart(call: Call) {
                Log.d("OkHttp", "requestBodyStart")
            }

            override fun requestBodyEnd(call: Call, byteCount: Long) {
                Log.d("OkHttp", "requestBodyEnd")
            }

            override fun responseHeadersStart(call: Call) {
                Log.d("OkHttp", "responseHeadersStart")
            }

            override fun responseHeadersEnd(call: Call, response: Response) {
                Log.d("OkHttp", "responseHeadersEnd")
            }

            override fun responseBodyStart(call: Call) {
                Log.d("OkHttp", "responseBodyStart")
            }

            override fun responseBodyEnd(call: Call, byteCount: Long) {
                Log.d("OkHttp", "responseBodyEnd")
            }
        }
    }
}

Вышеуказанный слушатель событий регистрируется клиентом. Кэш отключен, чтобы исключить возможность попадания в кэш при последующих запросах.

private val client = OkHttpClient.Builder()
    .cache(null)
    .eventListenerFactory(OkHttpEventListenerFactory())
    .build()

Этот клиент используется для того, чтобы обратиться к конечной точке несколько раз.

    val request = Request.Builder()
        .url("https://www.google.com")
        .build()

    client.newCall(request)

Первое обращение занимает 676 миллисекунд и включает установку нового TCP-соединения, включая разрешение DNS, SSL Handshake и HTTP-запрос и ответ. Теперь это новое соединение добавляется в пул соединений, и, следовательно, connectionReleased не вызывается.

19:57:03.488  D  CallStart
19:57:03.524  D  dnsStart
19:57:03.554  D  dnsEnd
19:57:03.555  D  connectStart
19:57:03.581  D  secureConnectStart
19:57:03.640  D  secureConnectEnd
19:57:03.646  D  connectEnd
19:57:03.646  D  connectionAcquired
19:57:03.646  D  requestHeadersStart
19:57:03.648  D  requestHeadersEnd
19:57:04.114  D  responseHeadersStart
19:57:04.114  D  responseHeadersEnd
19:57:04.115  D  responseBodyStart
19:57:04.115  D  responseBodyEnd
19:57:04.116  D  requestHeadersStart
19:57:04.116  D  requestHeadersEnd
19:57:04.164  D  responseHeadersStart
19:57:04.164  D  responseHeadersEnd

Однако последующее обращение обрабатывается всего за 506 миллисекунд (минуя поиск DNS) за счет использования существующего TCP-соединения из пула соединений.

19:58:20.041  D  CallStart
19:58:20.043  D  connectionAcquired
19:58:20.043  D  requestHeadersStart
19:58:20.044  D  requestHeadersEnd
19:58:20.502  D  responseHeadersStart
19:58:20.502  D  responseHeadersEnd
19:58:20.503  D  responseBodyStart
19:58:20.503  D  responseBodyEnd
19:58:20.503  D  requestHeadersStart
19:58:20.504  D  requestHeadersEnd
19:58:20.547  D  responseHeadersStart
19:58:20.547  D  responseHeadersEnd

Заключение

Хотя стандартный пул подключений достаточно хорош для большинства приложений, приложениям, использующим один и тот же OkHttpClient для нескольких конечных точек (через Retrofit), стоит подумать либо об увеличении пула подключений, либо об использовании специфических OkHttpClient для конечных точек (Service).

Источник

Exit mobile version