Статьи

Перевод: Николай Мациевский aka sunnybear
Опубликована: 19 мая 2008

Обходим ограничения браузера на число соединений

Примечание: ниже находится перевод стетьи Circumventing browser connection limits for fun and profit, в которой автор рассуждает о способах увеличения числа одновременных соединений для отдельно взятого сайта и рассказывает о собственном положительном опыте в этой области. Мои комментарии далее курсивом.

Несколько дней назад эта видео-запись размещенная на metacafe высветилась на digg. В ней объяснялось, как увеличить скорость открытия сайтов путем тонкого тюнинга браузера и изменения его настроек, отвечающих за число параллельных соединений. Чтобы объяснить, почему это работает, давайте немного углубимся в то, как браузеры обслуживают серверные соединения.

Утилитарный выбор

При разработке любых приложений всем разработчикам приходится делать то, что называется «утилитарным» выбором (utilitarian choice). Если несколько вычурно перефразировать Jeremy Bentham, то «утилитарным» можно назвать тот подход, «в результате которого мы получаем наибольшее количество добра для наибольшего числа [людей]». Много раз производительностью жертвовали для небольшого числа пользователей, чтобы, в результате, средняя производительность для всех пользователей в совокупности была бы выше.

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

Чтобы найти подходящий баланс, IE и Firefox, по умолчанию, ограничивают пользователей всего шестью одновременными соединениями или двумя соединениями на хост для протокола HTTP 1.1. HTTP 1.0 немного отличается в этом плане, но это уже совсем другая история, потому что все выгоды от постоянных соединений доступны только, если вы будете использовать HTTP 1.1 (или уже делаете это).

Времена меняются

Естественно, в реальном мире все эти утилитарные решения имеют особенность устаревать, когда уходит то время, когда они были актуальны. Сегодня у большинства пользователей широкополосный доступ в Интернет, поэтому наиболее узким местом является уже не клиентская сторона (естественно, клиентская сторона была, есть и будет наиболее узким местом в производительности ваших веб-приложений, просто нужно понимать, как именно можно ее оптимизировать в каждом конкретном случае). Обычно задержки при получении отдельных объектов сильно больше, чем время на установление нового соединения и отправку запроса. Увеличивая число одновременных соединений, мы можем распараллелить это место и гораздо быстрее пробиться через множество объектов, которые находятся в списке ожидаемых к загрузке, что приведет, в итоге, к увеличению ощущаемой скорости загрузки у пользователя «до скорости молнии» — если воспользоваться гиперболой из клипа metacafe.

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

«Режем» соединения

Большинство сайтов обладают только всего одним хостом, поэтому все запросы вынуждены бороться за 2 доступных соединения к этому хосту. Одним из наиболее эффективных методов для увеличения числа параллельных потоков будет распределение вашего содержания по нескольким хостам. Это не так сложно сделать, потому что браузеры обращают внимание только на название хоста, а не на IP-адрес. Таким образом, к каждому из хостов images1.yoursite.com и images2.yoursite.com браузеры смогут установить по два соединения.

В качестве практического примера можно рассмотреть это веб-приложение, которое Buddy и я разработали для нашей мастерской на Web Builder 2.0. Небольшие изображения альбомов, по умолчанию, загружаются с родительского хоста, поэтому они так же вынуждены использовать те же два доступных соединения. Ниже представлена примерная диаграмма загрузки объектов на странице, полученная при помощи Gomez' — внешнего сервиса для проверки производительности.

Загрузка при двух соединениях

Рисунок 1. Загрузка при двух соединениях. По ссылке доступна диаграмма в полный размер.

На этом графике хорошо видно, что для musicstore.ajaxperformance.com открыто только 2 соединения (хочу заметить, что данная диаграмма является модельной и справедлива только для IE, во всех остальных браузерах, по умолчанию, открывается большие соединений): C0 и C2. Мы используем протокол HTTP 1.1, поэтому нам не нужно открывать отдельное соединение для каждой картинки, но мы, по-прежнему, теряем кучу времени на обслуживание индивидуальных запросов к объектам. Время на установление соединения (время до получения первого байта, синяя полоска на диаграмме) явно доминирует над временем загрузки данных, которое не так велико (красная полоска на диаграмме).

Лучше, больше, быстрее

Чтобы улучшить производительность мы создали CNAME-записи в DNS-таблице для images1.ajaxperformance.com, images2.ajaxperformance.com и images3.ajaxperformance.com, каждая из которых указывает обратно на основной хост. Этот небольшой кусочек кода на Rails в шаблоне, отвечающем за отображение альбома, распределяет картинки по нескольким разным хостам:

img_url = "/images/album_art/#{album.id}.jpg"
if @perf_multiplex_images
  idx = (album.id % 3) + 1
  img_url = "http://images#{idx}.ajaxperformance.com/images/album_art/#{album.id}.jpg"
end

Стоит обратить внимание, что при изменении порядка картинок хосты, с которых они должны загружаться, также изменяться, поэтому пользователю придется загружать те же картинки еще раз (если у него работало кеширование). При проектировании масштабируемых приложений, которые будут распределять объекты по различным хостам, нужно использовать хеш-функцию, которая установит однозначное соответствие между названием изображения и хостом, с которого оно должно загружаться.

Результаты можно наблюдать на обновленной версии. При первой загрузке производительность будет значительно лучше. Как можно видеть из нижеприведенного графика (и снова, картинка во весь размер доступна по ссылке), сейчас мы используем уже 6 соединений для загрузки наших картинок.

Загрузка при шести соединениях

Рисунок 2. Загрузка при шести соединениях.

Заключение

А что во всем этом хорошего, спросите вы? Довольно много, если брать во внимание относительно простые изменения в исходном коде. Ниже приведено сравнение общего времени загрузки страницы, которое замерялось в течение 24 часов из 8 точек доступа по всей стране.

Дневной график времени загрузки

Рисунок 3. Дневной график времени загрузки

Среднее время загрузки при использовании 2 соединений составило 7,919 секунды. Среднее время при использовании 6 соединений только 4,629 секунды. Время загрузки страницы уменьшилось больше чем на 40%. И эта техника будет работать во всех случаях, когда у вас большой пул запросов к объектам, которые расположены на одном сервере.

Существует масса примеров применения этого метода в реальных AJAX-приложениях. Чтобы утилизировать параллельность соединений, на Google Maps картинки поставляются с нескольким хостов, начиная с mt0.google.com и заканчивая mt3.google.com. На Virtual Earth также используется эта техника.

Этот подход можно также использовать, чтобы изолировать отдельные части вашего приложения друг от друга. Если некоторые его элементы требуют доступа к базе данных, и их загрузка задерживается больше, чем для статичных объектов, стоит устранить их из числа тех двух соединений, которые будут использоваться для загрузки картинок на вашем сайте, например, разместив их на поддомене (в данном случае, наверное, наиболее практичным решением будет размещение всей статики: CSS-, JS- и других файлов — на отдельном домене, например, static.example.com, а загрузки HTML-страниц, которые требовательны к базе, будет вестись с основного хоста. При этом static.example.com может иметь даже другой IP-адрес и обслуживаться любым «легким» сервером). Этот прием, может быть, и сильно не ускорит загрузку вашей страницы, но определенно улучшит ощущаемую производительность, позволяя пользователю загружать все статические файлы без дополнительных задержек.

Читать дальше

Все комментарии (habrahabr.ru)