Главная → НовостиНовостиНовости, 201605Новости, 201605 → Одна маленькая проблема скачивания файлов на медленных соединениях

Одна маленькая проблема скачивания файлов на медленных соединениях

Проблема: некоторые пользователи не могли скачать бинарный файл объемом несколько мегабайт

КиТ :: Будь в СЕТИ! KiT - Keep-inTouch :: RSS-лента

Сети :: [Профессиональные] | [Зарубежные] | [Тематические] | [Молодежные] | [Детские] | [Купоны] | [Прочее]

Категории (метки) :: Авто | Музыка | Книги | Хобби | Профессии | Сообщества | Семья | Бизнес | Регионы | Спорт | Статусы

Соединение почему-то обрывалось, хотя файл находился в процессе скачивания. Вскоре мы убедились, что где-то в нашей системе был баг. Воспроизвести проблему можно было достаточно просто единственной командой curl, но исправить ее потребовало невероятных затрат сил и времени.

Проблемные скачивания

Две вещи нас удивили в этой проблеме: во-первых, только пользователи мобильных устройств были подвержены проблеме, во-вторых, файл, который вызывал проблемы, был все-таки достаточно большим – порядка 30 мегабайт.

После плодотворной работы с tcpdump один из наших инженеров смог воспроизвести проблему. Оказалось, что достаточно положить большой файл для скачивания на тестовом домене, и использовать опцию в команде :

$ curl -v http://example.com/large.bin --limit-rate 10k > /dev/null * Closing connection #0 curl: (56) Recv failure: Connection reset by peer

Ковыряние в tcpdump показало, что всегда был RST-пакет, который прилетал с нашего сервера точно на 60 секунде после установки соединения:$ tcpdump -tttttni eth0 port 80 00:00:00 IP 192.168.1.10.50112 > 1.2.3.4.80: Flags [S], seq 3193165162, win 43690, options [mss 65495,sackOK,TS val 143660119 ecr 0,nop,wscale 7], length 0 ... 00:01:00 IP 1.2.3.4.80 > 192.168.1.10.50112: Flags [R.], seq 1579198, ack 88, win 342, options [nop,nop,TS val 143675137 ecr 143675135], length 0

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

Не наша проблема?

Чтобы изолировать проблему, мы запустили базовый NGINX-сервер со стандартными настройками, и проблема оказалась легко воспроизводима локально:$ curl --limit-rate 10k localhost:8080/large.bin > /dev/null * Closing connection #0 curl: (56) Recv failure: Connection reset by peer

Это показало, что проблема не является специфичной для нашей установки, это была более широкая проблема, связанная с NGINX.

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

Это запускает RST-пакет вместо нормального завершения TCP-соединения. Вот лог из NGINX:04:20:22 setsockopt(5, SOL_SOCKET, SO_LINGER, {onoff=1, linger=0}, 8) = 0 04:20:22 close(5) = 0

Мы могли бы просто отключить , но это не решило бы проблему. Вопрос стоял так: почему вообще NGINX закрывает это соединение?

Далее мы обратили внимание на параметр . Его значение по умолчанию – 60 секунд, в точности, как мы наблюдали в своем случае. http { send_timeout 60s; ...

Параметр используется в NGINX, чтобы убедиться, что все соединения рано или поздно будут завершены. Этот параметр контролирует время, разрешенное между последовательными вызовами / в каждом соединении. Говоря по-простому, это неправильно, чтобы одно соединение использовало ресурс сервера слишком долго. Если скачивание длится слишком долго, или вообще прекратилось, это нормально, если http-сервер оборвет соединение.

Также и не-NGINX проблема

C в руках мы посмотрели, что NGINX делает:04:54:05 accept4(4, ...) = 5 04:54:05 sendfile(5, 9, [0], 51773484) = 5325752 04:55:05 close(5) = 0

В конфигурации мы указали NGINX использовать , чтобы передавать данные. Вызов проходит успешно и отправляет 5 мегабайт данных в буфер отправки. Что интересно: это почти такой же размер, который у нас установлен по умолчанию в буфере записи:$ sysctl net.ipv4.tcp_wmem net.ipv4.tcp_wmem = 4096 5242880 33554432

Спустя минуту после первого вызова сокет закрывается. Что будет, если мы увеличим значение до какого-то большего значения, например 600 секунд:08:21:37 accept4(4, ...) = 5 08:21:37 sendfile(5, 9, [0], 51773484) = 6024754 08:24:21 sendfile(5, 9, [6024754], 45748730) = 1768041 08:27:09 sendfile(5, 9, [7792795], 43980689) = 1768041 08:30:07 sendfile(5, 9, [9560836], 42212648) = 1768041 ...

После первого большого «выпихивания» данных,

sendfile вызывается еще несколько раз. Между каждым последовательным вызовом он передает примерно 1,7 мегабайт. Между этими вызовами, примерно каждые 180 секунд, сокет постоянно пустел из-за медленного curl, так почему же NGINX не пополнял его постоянно?

Асимметрия

Девиз Unix: «всё является файлом». По-другому можно сказать «всё может быть прочитано или записано с помощью poll». Давайте рассмотрим поведение сетевых сокетов в Linux.

Семантика чтения из сокета проста:

Вызов будет возвращать данные, доступные в сокете, пока он не опустеет. отвечает, что сокет доступен для чтения, когда в нем есть какие-то данные.

Можно подумать, что похожим образом выглядит запись в сокет:

Вызов будет копировать данные в буфер, пока буфер отправки не заполнится. отвечает, что сокет доступен для записи, если в нем есть хоть сколько-нибудь свободного места.

Как ни удивительно, но это НЕ так.

Разные «пути» кода

Важно понять, что в ядре Linux есть два различных механизма работы исполняемого кода: по одному пишутся (отправляются) фактические данные, а по второму проверяется, является ли сокет доступным для записи.

Чтобы команда выполнилась успешно, нужно, чтобы были выполнены два условия:

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

С другой стороны, условия, при которых poll считает сокет доступным для записи, несколько строже:

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

Последнее условие является критичным. После того, как буфер отправки заполнен на 100%, он снова будет доступен для записи не раньше, чем его уровень его заполнения опустится хотя бы до 66%.

Если мы вернемся к отслеживанию поведения NGINX, то во втором случае c sendfile мы увидели вот что:08:24:21 sendfile(5, 9, [6024754], 45748730) = 1768041

Успешно были отправлены 1,7 мегабайт данных, это близко к 33% от 5 мегабайт, нашего дефолтного значения размера буфера отправки wmem.

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

Решение

Теперь мы можем точно сказать, когда случается проблема:

Буфер отправки сокета заполнен на как минимум 66%. Скорость скачивания пользователем низкая, и буфер не опустошается до 66% за 60 секунд. Когда это происходит, буфер отправки не пополняется, он не считается доступным для записи, и соединение разрывается по тайм-ауту.

Существует несколько способов решить проблему.

Один – это увеличить до, скажем, 280 секунд. Тогда при заданном размере буфера отправки, пользователи, чья скорость больше, чем 50Kb/s, не будут отключаться по тайм-ауту.

Другой вариант, это уменьшить размер буфера отправки .

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

, в котором реализован вариант с опцией , которая позволяет определить насколько медленное скачивание разрешено клиенту.

Выводы

Сетевой стек Linux очень сложен. Хотя обычно все работает хорошо, иногда в нем можно найти сюрпризы.

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

И теперь мы знаем, что из-за значений wmem можно получить неожиданные тайм-ауты при отправке данных.

Главная → НовостиНовостиНовости, 201605Новости, 201605 → Одна маленькая проблема скачивания файлов на медленных соединениях

Google запустил бесплатного «убийцу SMS» в обход операторов сотовой связи

Корпорация Google начала поэтапное внедрение нового стандарта обмена сообщениями под названием RCS, который призван заменить SMS. Поддержка RCS реализована в операционной системе Android на уровне встроенного приложения «Сообщения» (Android Messages)...

В России усилят контроль за онлайн-платежами

Агрегаторов, обеспечивающих онлайн-оплаты, поставят под контроль кредитных организаций....

Россия намерена создать свою криптовалюту с Китаем и странами БРИКС

Россия и страны БРИКС могут создать единую платежную систему на основе специальной криптовалюты с целью упрощения расчетов между собой и снижения зависимости от американской валюты. При этом в России не существует ни одного закона, регулирующего обра...

Роскомнадзор пригрозил заблокировать Google

При этом в ведомстве надеются, что до таких радикальных мер не дойдет, и компания придет к конструктивному сотрудничеству....

Основатель «Википедии» создал «убийцу» Twitter и Facebook

Как отметил Джимми Уэйлс в интервью Financial Times, его соцсеть будет ориентирована исключительно на качественный контент, но при этом показ рекламы не планируется даже в перспективе, потому что работу WT: Social предполагается поддерживать с помощь...

[Популярные социальные сети] [*Добавить сайт]

Нравится

Группы: ВК | FB | Tw | G+ | OK

Рубрики | КАТАЛОГ | Новости | Контакты |