У меня возникают задержки при отправке данных через канал TCP, который я не могу понять. Ссылка представляет собой канал 1 ГБ с сквозной задержкой примерно 40 мс. В моей текущей настройке задержка (время от одного сообщения до перехода из пользовательского пространства отправителя в пользовательское пространство получателя) может достигать 100 мс.
Сокет отправителя настроен с параметром TCP_NODELAY. Буфер отправителя (SO_SNDBUF) настроен на 8 МБ. Буфер приема (SO_RCVBUF) также настроен на 8 МБ. Масштабирование окна TCP активировано.
обновление-1: Я использую промежуточное ПО zeromq 3.1.1 для переноса данных. Конфигурация сокета, включая флаг TCP_NODELAY, выполняется промежуточным программным обеспечением. Доступны некоторые параметры, например размеры буфера передачи rx и tx, но не TCP_NODELAY. Насколько я понял, TCP_NODELAY активируется, чтобы гарантировать, что данные будут отправлены, насколько это возможно. Между тем фактическая отправка сокета и решение об отправке сообщения выполняются в двух отдельных потоках. Правильная пакетная обработка выполняется, если в момент отправки первого сообщения в пакете доступно несколько сообщений.
Я запустил захват с помощью tcpdump, из которого были извлечены кадры ниже. После первоначального подтверждения TCP отправитель (172.17.152.124) начинает отправлять данные. Начальный размер окна составляет 5840 байтов для получателя и 5792 байта для отправителя.
Моя проблема в том, что отправитель отправляет два кадра (№6 и №7), а затем останавливается, ожидая ответа от получателя. Насколько я могу судить, размер окна получателя не достигнут, и передача не должна останавливаться (384 байта невыполнены с начальным размером окна приема 5840 байтов). Я начинаю думать, что неправильно понял, что такое TCP. Может кто-нибудь помочь прояснить?
обновление-2: Мои данные состоят из магического числа, за которым следует отметка времени. Я выделил задержанные пакеты, сравнив временные метки полезных данных с временными метками, установленными tcpdump. Полезная нагрузка ts кадра №9 очень близка к полезной нагрузке кадра №6 и №7 и явно меньше, чем временная метка принятого подтверждения в кадре №8.
обновление-1: Тот факт, что кадр № 9 не отправляется немедленно, можно объяснить медленным запуском TCP-канала. Фактически, проблема также появляется, когда соединение работает в течение нескольких минут, поэтому медленный запуск не кажется общим объяснением.
20: 53: 26.017415 IP 172.17.60.9.39943> 172.17.152.124.56001: флаги [S], seq 2473022771, win 5840, параметры [mss 1460, sackOK, TS val 4219180820 ecr 0, nop, wscale 8], длина 0
20: 53: 26.017423 IP 172.17.152.124.56001> 172.17.60.9.39943: флаги [S.], seq 2948065596, ack 2473022772, win 5792, параметры [mss 1460, sackOK, TS val 186598852 ecr 219180820, nop, wscale 9 ], длина 0
20: 53: 26.091940 IP 172.17.60.9.39943> 172.17.152.124.56001: флаги [.], Ack 1, win 23, options [nop, nop, TS val 4219180894 ecr 186598852], длина 0
20: 53: 26.091958 IP 172.17.60.9.39943> 172.17.152.124.56001: флаги [P.], seq 1:15, ack 1, w на 23, параметры [nop, nop, TS val 4219180895 ecr 186598852], длина 14
20: 53: 26.091964 IP 172.17.152.124.56001> 172.17.60.9.39943: флаги [.], Ack 15, win 12, options [nop, nop, TS val 186598927 ecr 4219180895], длина 0
20: 53: 26.128298 IP 172.17.152.124.56001> 172.17.60.9.39943: флаги [P.], seq 1: 257, ack 15, win 12, options [nop, nop, TS val 186598963 ecr 4219180895], длина 256
20: 53: 26.128519 IP 172.17.152.124.56001> 172.17.60.9.39943: флаги [P.], seq 257: 385, ack 15, win 12, options [nop, nop, TS val 186598963 ecr 4219180895], длина 128
20: 53: 26.202465 IP 172.17.60.9.39943> 172.17.152.124.56001: флаги [.], Ack 257, win 27, options [nop, nop, TS val 4219181005 ecr 186598963], длина 0
20: 53: 26.202475 IP 172.17.152.124.56001> 172.17.60.9.39943: флаги [.], Seq 385: 1833, ack 15, win 12, options [nop, nop, TS val 186599037 ecr 4219181005], длина 1448
20: 53: 26.202480 IP 172.17.152.124.56001> 172.17.60.9.39943: флаги [P.], seq 1833: 2305, ack 15, win 12, options [nop, nop, TS val 186599037 ecr 4219181005], длина 472
Если это имеет значение, оба конца - это боксы Linux RHEL5 с ядрами 2.6.18 и сетевыми картами, использующими драйверы e1000e.
обновление-3 Содержимое /etc/sysctl.conf
[jlafaye@localhost ~]$ cat /etc/sysctl.conf | grep -v "^#" | grep -v "^$"
net.ipv4.ip_forward = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
kernel.sysrq = 0
kernel.core_uses_pid = 1
net.ipv4.tcp_syncookies = 1
kernel.msgmnb = 65536
kernel.msgmax = 65536
kernel.shmmax = 68719476736
kernel.shmall = 4294967296
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 1048576
net.core.wmem_default = 1048576
net.ipv4.tcp_rmem = 65536 4194304 16777216
net.ipv4.tcp_wmem = 65536 4194304 16777216
net.core.netdev_max_backlog = 10000
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_mem = 262144 4194304 16777216
kernel.shmmax = 68719476736
После того, как я немного углубился в свой трафик, я смог увидеть, что мои данные представляют собой не что иное, как последовательность небольших пакетов с небольшими периодами простоя между ними.
С полезным инструментом ss
, Мне удалось получить текущий размер окна перегрузки моего соединения (см. cwnd
значение на выходе):
[пользователь @ localhost ~] $ / usr / sbin / ss -i -t -e | grep -A 1 56001
ESTAB 0 0 192.168.1.1:56001
192.168.2.1:45614 uid: 1001 ino: 6873875 sk: 17cd4200ffff8804 ts sackscalable wscale: 8,9 rto: 277 rtt: 74/1 ato: 40 cwnd: 36 send 5.6Mbps rcv_space: 5792
Я запускал инструмент несколько раз и обнаружил, что размер окна перегрузки регулярно сбрасывался до начального значения (10 мс в моем Linux-компьютере). Соединение постоянно возвращалось к фазе медленного запуска. Во время периода медленного старта пакеты с количеством сообщений, превышающим размер окна, задерживались в ожидании подтверждения, относящегося к первым пакетам пакета.
Тот факт, что трафик состоит из последовательности пакетов, вероятно, объясняет сброс размера окна перегрузки.
Деактивировав режим медленного старта после периода простоя, я смог избавиться от задержек.
[пользователь @ хост ~] $ cat / proc / sys / net / ipv4 / tcp_slow_start_after_idle 0
Это не будет какой-то тонкой вещью, как где-то сеттинг. Это будет проблема с протоколом, наложенным поверх TCP, или ошибка кода. Для TCP не существует волшебного переключателя "идти быстрее", за исключением необычных случаев, таких как сети с очень высокой задержкой или потеря пакетов из-за шума.
Наиболее очевидным объяснением было бы то, что код вызывает write
или send
с очень маленькими кусками. Вам необходимо накопить не менее 2 КБ на отправку, в идеале - 16 КБ. Вы говорите, что группируете сообщения, но не совсем понятно, что это значит. Вы передаете их за один звонок write
или send
? Объединяете ли вы их в единый блок данных протокола для протокола, расположенного поверх TCP? Выполнение обоих этих действий очень помогает с задержкой.
Также избавьтесь от TCP_NODELAY. Это может снизить пропускную способность. Это только для приложений, которые не были разработаны для работы с TCP, или для приложений, которые не могут предсказать, с какой стороны нужно будет передать следующую.
Если, конечно, вы фактически не накладываете протокол поверх TCP, где вы не знаете, какая сторона будет передавать следующую (например, telnet
, например). Тогда имеет смысл установить TCP_NODELAY. Чтобы такой протокол работал с малой задержкой, требуется значительный опыт. Если это ваша ситуация, опубликуйте более подробную информацию о протоколе, который вы накладываете поверх TCP, о том, как выглядят его размеры блоков данных протокола и что определяет, какая сторона когда передает.
Если вы действительно собираете сообщения, доступные одновременно, и передаете их за один вызов write
или send
, то, скорее всего, проблема в том, что другая сторона не отправляет подтверждение уровня приложения для каждого пакета. Они уменьшают время ожидания, передавая пакеты TCP ACK для совмещения. Ваш протокол должен включать их, чтобы обеспечить чередование сторон, что помогает снизить задержку.