Да, речь пойдет о keep alive. Суть в том, что, начиная с http 1.1, клиент и сервер могут договориться не закрывать установленное tcp-соединение после завершения запроса, а переиспользовать его для следующих запросов. Это нужно потому, что на установку соединения требуется время. Иногда это время больше, чем время самого запроса. И если все серверы уже давным-давно такую возможность поддерживают, а все браузеры и большинство других клиентов её используют, то у разработчиков различных библиотек для популярных языков программирования здесь почему-то пробел.
Рассмотрим простой код на PHP, который последовательно делает 10 запросов к одному серверу:
<?php for ($i = 0; $i < 10; $i += 1) { $ch = curl_init(); curl_setopt_array($ch, array( CURLOPT_URL => "https://evernote.com/favicon.ico", CURLOPT_VERBOSE => True, CURLOPT_RETURNTRANSFER => True, )); $resp = curl_exec($ch); curl_close($ch); }
Это каркас 95% библиотек, обращающихся к сторонним ресурсам. Опция CURLOPT_VERBOSE позволяет видеть в консоли все, что делает библиотека curl во время выполнения скрипта. И самые интересные строчки будут повторяться все 9 запросов (кроме первого):
* Connection #0 to host evernote.com left intact * Closing connection #0 * About to connect() to evernote.com port 443 (#0) * Trying 204.154.94.73...
Как видите, curl оставляет соединение после запроса открытым, но мы его тут же закрываем. Результат печален: 10 запросов создают 10 соединений, скрипт выполняется не менее 17 секунд.
Это говорит о том, что сам curl знаком с http 1.1, а мы мешаем ему нормально работать. Но исправить это очень просто:
<?php $ch = curl_init(); for ($i = 0; $i < 10; $i += 1) { curl_setopt_array($ch, array( CURLOPT_URL => "https://evernote.com/favicon.ico", CURLOPT_VERBOSE => True, CURLOPT_RETURNTRANSFER => True, )); $resp = curl_exec($ch); } curl_close($ch);
Мы просто вынесли создание и удаление дескриптора из цикла, и картина при следующем запуске поменялась:
* Connection #0 to host evernote.com left intact * Re-using existing connection! (#0) with host (nil) * Connected to (nil) (204.154.94.73) port 443 (#0)
А время работы сократилось до 5,5 секунд. Конечно, тут я намеренно обращаюсь к статическому файлу. В реальных условиях некоторое время займет форматирование запроса. Плюс, если вы используете http без ssl, время соединения будет немного меньше. Тем не менее, постоянные соединения в любом случае дают существенный выигрыш.
Я провел несколько экспериментов, измеряя время, необходимое на 10 запросов по протоколам http и https с использованием отдельных соединений и keep-alive для файлов разного размера с разными пингами до сервера. Брался лучший результат за 5-6 измерений.
evernote.com/favicon.ico, Пинг ≈ 200 ms, размер 27054 байт.
Reconnect | Keep-Alive | Ratio | |
---|---|---|---|
http | 10 | 5 | 2x |
https | 17 | 5,5 | 3,1x |
twitter.com/favicon.ico, Пинг ≈ 200 ms, размер 1150 байт.
Reconnect | Keep-Alive | Ratio | |
---|---|---|---|
http | 4,3 | 2,5 | 1,7x |
https | 8,5 | 2,7 | 3,1x |
yandex.st/lego/_/pDu9OWAQKB0s2J9IojKpiS_Eho.ico, Пинг ≈ 17 ms, размер 1150 байт.
Reconnect | Keep-Alive | Ratio | |
---|---|---|---|
http | 0,33 | 0,17 | 1,9x |
https | 0,8 | 0,2 | 4x |
Цифры говорят сами за себя. Но прелесть даже не в них, а в том, что добиться этого очень просто. Все что вам нужно сделать в случае использования curl — перенести вызов curl_init()
из метода, который делает запрос, в конструктор класса (вот один и другой пример, где это легко можно сделать). При этом curl_close()
можно выкинуть совсем, ресурсы и так освободятся при завершении запроса. Curl сам держит пул соединений для каждой пары хост и порт, к которым вы обращаетесь, и переоткрывает закрытые соединения.
На самом деле речь, конечно, не про curl и php. В каждом языке можно найти библиотеку, реализующую такой же пул. Например, для python это прекрасная urllib3 — на основе которой построена популярная библиотека requests. С ней дела обстоят точно так же, как с curl в php, её очень просто использовать, но не все это делают правильно. И я бы хотел показать несколько примеров, как это можно исправить. Вот так я сделал поддержку постоянных соединений в клиенте stripe. После этого функциональные тесты в нашем проекте стали выполняться в 2 раза быстрее, хотя они не только со страйпом обращались, конечно. А так я пофискил нашу библиотеку pyuploadcare. В обоих случаях все что нужно было сделать — заменить вызовы функций requests.request()
на вызовы методов объекта session
, созданного заранее.
Надеюсь, мне удалось убедить вас в необходимости обращать на это внимание при разработке библиотек, и показать насколько это просто реализуется.
ссылка на оригинал статьи http://habrahabr.ru/post/184302/
Добавить комментарий