Привет!

Продолжаем изучать маршрут сетевого пакета по подсистемам ядра linux.
В первой части мы разобрали отрезок от сетевой карты гипервизора до txqueue - очередь перед виртуальной машиной в которой и крутится наш сервис.

Теперь разберем следующий отрезок - от сетевой карты ВМ и до самого приложения.


RX queue

network packet path

Процесс обработки трафика виртуальной машиной идентичен с гипервизоров - softirqd демоны разгребают RX очереди. Ничего нового.

Различия начинаются на уровнях сетевого стека, точнее перед ним.

input_pkt_queue

network packet path

После RXq пакет прибывает к input_pkt_queue - очередь перед стеком протоколов TCP/IP.

Переполнение input_pkt_queue ведет к росту значения во второй колонке файла /proc/net/softnet_stat, а node_exporter содержит метрику node_softnet_dropped_total.

Узнать максимальную длину, а так же изменить значение можно командой sysctl net.core.netdev_max_backlog.

Теперь поднимемся на уровень TCP протокола.

если создается новое соединение…

network packet path

Для обмена данными, например по HTTP, предварительно требуется установить соединение. Этот процесс называют tcp three-way handshake.

При инициализации соединение меняет свое состояние, например SYN_RECV->ESTABLISHED для server или SYN_SENT->ESTABLISHED для client.

Введем еще две очереди - SYN Queue и Accept Queue.

Процесс со стороны server:

  • поступает SYN пакет;
  • соединение переходит в состояние SYN_RECV и попадает в SYN Queue;
  • SYN-ACK пакет направляется к client;
  • поступает клиентский ACK;
  • соединение переходит в состояние ESTABLISHED и перемещается в Accept Queue;
  • приложение асинхронно вызывает accept() и начинает обслуживать

Подробно процесс описан в блоге alibaba cloud.

Наблюдение:

SYN Queue

  • netstat -s | grep -i "listen"
  • node_netstat_TcpExt_ListenDrops метрика в node_exporter

Accept Queue

  • ss -ntl - текущий размер очереди (Recv-Q) и максимальный размер очереди (Send-Q)
  • netstat -s | grep -i "listen"
  • node_netstat_TcpExt_ListenOverflows метрика в node_exporter

Тюнинг

SYN Queue

В ядрах версий 2.6.20+ вычислить значение непросто:

    backlog = min(somaxconn, backlog)
    nr_table_entries = backlog
    nr_table_entries = min(backlog, sysctl_max_syn_backlog)
    nr_table_entries = max(nr_table_entries, 8)
    // roundup_pow_of_two: 将参数向上取整到最小的 2^n,注意这里存在一个 +1
    nr_table_entries = roundup_pow_of_two(nr_table_entries + 1)
    max_qlen_log = max(3, log2(nr_table_entries))
    max_queue_length = 2^max_qlen_log

Для упрощения вычисления можно по-прежнему ориентироваться на файл /proc/sys/net/ipv4/tcp_max_syn_backlog, но о нюансах стоит помнить.

Accept Queue

  • min(somaxconn, backlog,sysctl_max_syn_backlog), где
    • somaxconn - /proc/sys/net/core/somaxconn
    • backlog - параметр системного вызова int listen(int sockfd, int backlog)
      • например в nginx существует одноименная директива backlog.

если соединение уже установлено…

network packet path

Напомню, что TCP протокол гарантирует надежную доставку и правильный порядок следования пакетов. Для этого проверяется их порядковый номер - sequence number или SYN.

Если порядок следования не нарушается, то пакет помещается напрямую в буфер сокета (recvQ). В противном случае в OFO Queue (out of order), где он дожидается запоздавших соседей и восстановления порядка следования. Только после этого пакет отправят в recvQ.

Out of order пакеты негативно влияют на производительность:

  • провоцируют механизм fast retransmits, а значит понижают объем полезного трафика, т.н. goodput;
  • повышают latency - тратится время на ожидание и восстановление правильного порядка следования пакетов.

Причины возникновения могут быть в нескольких маршрутов в рамках одного TCP потока и/или при потерях на сетевом оборудовании.

Проблематика out of order пакетов обсуждалась в докладе Тюним память и сетевой стек в Linux / Дмитрий Самсонов (Одноклассники), советую ознакомиться.

Наблюдение

OFO Queue

  • netstat -s | grep -E 'TCPOFOQueue|TCPOFODrop'
  • метрики node_exporter:
    • node_netstat_TcpExt_TCPOFOQueue - объем поступающих OFO пакетов;
    • node_netstat_TcpExt_TCPOFODrop - дропы пакетов при переполнении;

в релизе node_exporter 1.8.0 версии метрика TCPOFOQueue стала дефолтной для коллектора netstat.

recvQ

  • netstat -s | grep -E 'TCPRcvQDrop|TCPRcvCollapsed' - дропы пакетов при переполнении сокета;
  • метрики node_exporter:
    • node_netstat_TcpExt_TCPRcvQDrop - дропы пакетов при переполнении сокета;
    • node_netstat_TcpExt_TCPRcvCollapsed - прежде чем дропать при переполнении сокета, ядро “из последних сил” пытается стабилизировать ситуацию.

В свое время ребята из Cloudflare провели исследование как TCP collapse влияет на производительность, must read.

Тюнинг

OFO Queue значение задается при создании сокета через системный вызов sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_REORDERING, 65536)

recvQ

  • net.ipv4.tcp_mem - объем памяти всех tcp буферов в системе
  • net.core.rmem_max - объем памяти tcp буферов приема в системе
  • net.ipv4.tcp_rmem - объем конкретного экземпляра tcp буфера

В следующей части соберем всё воедино и сделаем дашборд для удобного наблюдения за каждым из компонентов.

Удачи!