Источник: Nuances of Programming
Введение
Мы на Booking.com знаем, как важна для наших пользователей производительность, в том числе сетевая. Недавно мы исследовали производительность сетевого стека нашего приложения для Android и нашли некоторые области, где можно ради удобства пользователей улучшить как производительность, так и само приложение. Мы хотим поделиться некоторыми советами о том, как оптимизировать повторное использование соединения OkHttp, а также осветить процесс отладки сторонней библиотеки.
На Booking.com мы используем OkHttp, библиотеку HTTP-клиента для Java/JVM, которая из коробки эффективна, проста в тестировании и позволяет определять общее поведение сетевых запросов с помощью составления перехватчиков (Interceptors).
Исследование проблемы
Узкое место производительности
Нам хочется знать продолжительность промежутка времени с момента готовности отправить сетевой запрос до момента получения пригодного к использованию результата (включая подготовку запроса, его выполнение, обработку ответа и синтаксический анализ). Посмотрев, сколько занимает каждый этап, можно понять, где стоит внести улучшения. Мы использовали небольшую утилиту-логгер, чтобы избежать влияния инструментов профилирования на время выполнения (для кода, который выполняется часто, лучше подходит Android Benchmark), и увидели, что самой заметной проблемой однозначно стало выполнение сетевых запросов, особенно задержка в них (ниже можете увидеть различные выполнения с использованием Stetho):
Network: 1.51s: Latency 1.32 s - Download 198 ms
Network: 1.43s: Latency 1.26 s - Download 197 ms
Network: 1.24s: Latency 1.16 s - Download 76 ms
Мы обнаружили расхождение между временем, отмеченным “на часах” на бэкенде и на клиенте. Наверняка мы можем что-нибудь сделать, чтобы сократить этот разрыв.
Как OkHttp выполняет запросы
В OkHttp есть расширение Logging Interceptor — механизм, который подключается к выполнению запроса с обратными вызовами и регистрирует информацию о выполнении запроса. Заглянув для начала в документацию, давайте посмотрим лог, полученный с помощью HttpLoggingInterceptor:
И с помощью LoggingEventListener:
LoggingEventListener предоставляет интересную информацию: похоже, приложение настраивает соединение повторно с различными версиями TLS. OkHttp стремится уменьшить количество соединений сокетов, переиспользуя их в HTTP-запросах, но поскольку это возможно не всегда, здесь есть потенциал для повышения производительности. К сожалению, рассматривая код для переиспользования соединений, мы видим, что при создании нового RealConnection не происходит никакого отдельного обратного вызова.
Отладка OkHttp
Хотя отсутствие обратного вызова для конкретного события усложняет его отслеживание, оно всё еще возможно, учитывая, что зависимость идет в комплекте с источниками (а если нет, IntelliJ включает в себя Java-декомпилятор). Это значит, что у нас есть полный доступ ко всему стеку выполнения кода, включая все переменные и свойства. Мы можем установить отладчик в точке, где создаётся соединение, и таким образом увидеть текущее состояние вызова метода:
К примеру, проверяем параметр Route:
И поскольку мы хотим сосредоточиться на конечных точках наших мобильных приложений, настраиваем точку останова отладчика с условием:
Мы знаем, что соединение сокета с хостом можно переиспользовать, но в действительности оно не переиспользуется. Поэтому мы можем перейти к методу, который проверяет условие для переиспользования RealConnection:
Можно проверить с помощью отладчика, что именно transmitterAcquirePooledConnection приводит к несоблюдению условия:
Заглянув внутрь метода, видим вот что:
Либо RealConnection поддерживает мультиплекс (HTTP/2, в настоящее время не поддерживается), либо isEligible имеет значение false. Глядя на isEligible, видим:
Мы можем взглянуть на пул существующих RealConnection:
И сравним Address одного из них с тем же хостом, чтобы найти первопричину проблемы.
Вот как выглядит метод сравнения:
С помощью отладчика можно увидеть, что все свойства равны, кроме sslsocket Factory:
Как видим, здесь у нас есть пользовательский тип SSLSocketFactory, который не переиспользуется и не реализует equals, препятствуя эффективному повторному использованию соединения. Отладчик Java позволяет нам не только проверять код, но и вызывать свойства и методы сущностей в текущей области видимости.
Решение проблемы
Для максимизации переиспользования соединения были приняты следующие две меры, которые также стоит рассмотреть и вам при настройке OkHttp:
Если вам просто нужен внешний поставщик безопасности, то используйте предложенный OkHttp подход:
Security.insertProviderAt(Conscrypt.newProvider(), 1)
Эти изменения были проведены в эксперименте, который показывает показавшим хорошие результаты, сокращая TTI, когда требуется сетевой запрос: результаты варьируются от разницы в несколько миллисекунд до двадцатипроцентного улучшения, в зависимости от количества требуемых соединений. Как только основное переиспользование соединения между запросами оптимизировано, мы можем выполнить дальнейшие оптимизации, такие как тонкая настройка пользовательского пула соединений, чтобы дополнительно ускорить время выполнения сетевых запросов.
Надеюсь, вы узнали что-то новое и полезное о сетевом стеке OkHttp и возможностях отладки с помощью сторонней библиотеки!
Спасибо за чтение!
Читайте также:
Перевод статьи: Diego Gómez Olvera, “Maximizing OkHttp connection reuse”