From: Сысоев Игорь Владимирович <http://www.sysoev.ru>
Date: Mon, 1 Oct 2007 14:31:37 +0000 (UTC)
Subject: FreeBSD для обслуживания 100-200 тысяч соединений
Видеоролик доклада
Стенограмма выступления Игоря Сысоева с конференции РИТ-2007.
mbuf clusters
FreeBSD хранит сетевые данные в mbuf clusters, размер каждого 2Кб, но из
них используется только около 1500 байт (по размеру Ethernet пакета).
mbufs
Для каждого mbuf кластера нужен "mbuf", который имеет размер 256 байт и
нужен для организации связи цепочек из mbuf кластеров. В mbuf можно поместить
полезную информацию в районе 100 байт, но это не всегда используется.
Если в машине 1Гб и больше памяти, то по умолчанию будет создано 25 тыс. mbuf кластеров,
что не всегда достаточно.
При ситуации исчерпания числа свободных mbuf кластеров FreeBSD попадает в
состояние zonelimit и перестает отвечать на запросы по сети,
в top это выглядит как "zoneli". Единственная возможность как-то повлиять на
ситуацию - это зайти с локальной консоли и перезагрузить систему, уничтожить
процесс находящийся в состоянии "zoneli" невозможно. Для Linux 2.6.x данная проблема
тоже характерна, причем работать переставала даже консоль.
PID USERNAME THR PRI NICE SIZE RES STATE TIME WCPU COMMAND
13654 nobody 1 4 0 59912K 59484K zoneli 209:26 0.00% nginx
Для выхода из этой ситуации существует патч возвращающий приложению ошибку ENOBUFS,
сигнализирующий о попадании в состояние "zoneli", после чего программа может
закрыть лишние соединения. К сожалению патч пока не принят в состав FreeBSD.
Состояние задействованных mbuf кластеров можно посмотреть командой:
>netstat -m
4/1421/1425 mbufs in use (current/cache/total)
0/614/614/25600 mbuf clusters in use (current/cache/total/max)
Увеличение числа mbuf кластеров во FreeBSD 6.2 можно произвести в любой момент
через параметр kern.ipc.nmbclusters:
sysctl kern.ipc.nmbclusters=65536
Для более ранних версий FreeBSD число mbuf кластеров можно было установить только
на этапе загрузки:
25000 mbuf кластеров занимают примерно 50Мб памяти, 32000 - 74 Мб, 65000 -
144Мб (рост по степени 2). 65000 - пограничное значение, превышать которое не
рекомендуется, без предварительного расширения адресного пространства
доступного ядру.
Увеличение памяти, доступной ядру
Увеличение адресного пространства ядра, которое на i386 платформе - 1Гб.
Для увеличения до 2Гб, в файле конфигурации ядра необходимо указать:
options KVA_PAGES=512
На платформе amd64 KVA всегда 2G, увеличить к сожалению в настоящее время нельзя.
Кроме увеличения виртуального адресного пространства можно увеличить лимит
физической памяти, которую может использовать ядро (по умолчанию 320Мб). Увеличим
до 1Гб:
/boot/loader.conf:
vm.kmem_size=1G
Затем выделим из него 575Мб под mbuf кластера:
sysctl -w kern.ipc.nmbclusters=262144
Установление соединения. syncache и syncookies
Примерно 1000 байт расходуется на одно соединение.
Примерно 100 байт для одной записи на незаконченное соединение в syncache.
Всего можно держать в памяти информацию об около 15000 соединениях.
Параметры syncache можно посмотреть через "sysctl net.inet.tcp.syncache" (доступен
в режиме только для чтения):
Когда новое соединение не помещается в переполненный syncache, FreeBSD переходит в
режим "syncookies" (TCP SYN cookies). Включается возможность такого перехода через:
sysctl net.inet.tcp.syncookies=1
Заполненность syncache и статистику по syncookies можно посмотреть через команду:
netstat -s -p tcp
...
2079088720 syncache entries added
...
> 0 dropped
2058506523 completed
> 0 bucket overflow
> 0 cache overflow
...
0 cookies sent
0 cookies received
После того как соединение принято оно попадает в "listen socket queue".
Статистику можно посмотреть командой
netstat -Lan
Current listen queue sizes (qlen/incqlen/maxqlen)
Proto Listen Local Address
tcp4 43/0/4096 *.80
tcp4 0/0/128 *.22
4096 - размер очереди (максимум 65тыс.)
43 - заполненность очереди в данный момент (приложение не вытащило из очереди).
Увеличение размера очереди производится через:
sysctl kern.ipc.somaxconn=4096
После того как соединение принято для него FreeBSD создает структуры связанные
с сокетами (sockets).
Увеличить максимальное число открытых сокетов во FreeBSD 6.2, во время работы,
можно через:
Если машина обрабатывает несколько десятков тысяч соединений то tcb hash позволяет быстро
определять принадлежность пришедшего пакета к определенному соединению.
По умолчанию размер tcb hash - 512 элементов.
Текущий размер можно посмотреть через sysctl (только для чтения):
sysctl net.inet.tcp.tcbhashsize
Изменить можно на этапе загрузки:
/boot/loader.conf:
net.inet.tcp.tcbhashsize=4096
Файлы
Приложения работают не с сокетами, а с файлами. По этому для каждого
сокета нужна структура, которая описывает файл. Увеличить можно через:
kern.maxfiles - всего файлов в системе
kern.maxfilesperproc - максимальное число файлов на один процесс.
Параметры можно менять на работающей системе, но они не отразятся на уже запущенных
процессах. Поэтому в nginx были добавлены директивы позволяющие менять число
открытых файлов для уже запущенных процессов (после инициирования операции переконфигурации)
receive buffers
Буферы для приема данных. По умолчанию 64Kб, если нет загрузки больших объемов данных,
то можно уменьшить до 8Кб (меньше вероятность переполнения при DoS атаке).
sysctl net.inet.tcp.recvspace=8192
Для nginx:
nginx.conf:
listen 80 default rcvbuf=8k;
send buffers
Буферы для отправки данных. По умолчанию 32K. Если скачиваются данные небольшого объема
или недостаток mbuf кластеров, можно уменьшить:
sysctl net.inet.tcp.sendspace=16384
Для nginx:
nginx.conf:
listen 80 default sndbuf=16k;
В ситуации когда сервер записал в сокет данные, но клиент не хочет их забирать,
после таймаута по закрытию соединения в ядре данные будут держаться еще
несколько минут. В nginx если директива для принудительного сброса всех данных
после закрытия по таймауту.
nginx.conf:
reset_timedout_connections on;
sendfile
Еще один механизм для экономии mbuf кластеров - это sendfile, он использует
память буферов ядра с данными файлов одновременно для передачи этих данных в
сетевую карту, без промежуточного заполнения лишних буферов.
В nginx включается:
nginx.conf:
sendfile on;
На i386 по умолчанию для систем с 1 или более Гб памяти будет выделено 6656
sendfile буферов. Этого вполне достаточно. На платформе amd64 более оптимальный
вариант реализации и sfbufs буферы не нужны.
Статистика:
i386>netstat -m
...
190/510/6656 sfbufs in use (current/peak/max)
amd64>netstat -m
...
0/0/0 sfbufs in use (current/peak/max)
При переполнении sendfile буфера процесс замирает в состоянии "sfbufa", но
ситуация достаточно быстро приходит в норму после увеличения размера буфера.
PID USERNAME THR PRI NICE SIZE RES STATE TIME WCPU COMMAND
13654 nobody 1 4 0 59912K 59484K sfbufa 209:26 5.00% nginx
Увеличение размера через:
/boot/loader.conf:
kern.ipc.nsfbufs=10240
TIME_WAIT
После того как соединение закрывается сокет переходит в состояние TIME_WAIT
В этом состоянии он может находится по умолчанию в течение 60 секунд.
Время можно изменить через sysctl (в миллисекундах деленных на 2, 2 x 30000 MSL = 60 секунд):
sysctl net.inet.tcp.msl=30000
Во FreeBSD 6.2 TIME_WAIT сокеты обрабатываются отдельно (нужна лишь
часть информации 48 байт из 1 Кб. Ограничение вне лимита
kern.ipc.maxsockets), число их регулируется параметром:
Для использования портов по порядку, вместо случайной выборки (для
исключения ошибки повторного коннекта с одного порта до отработки
TIME_WAIT):
sysctl net.inet.ip.portrange.randomized=0
Во FreeBSD 6.2 появилась возможность не создания состояния TIME_WAIT для
соединений в рамках localhost:
sysctl net.inet.tcp.nolocaltimewait=1
Дополнение от Alexey V. Karagodov <kav@karagodov.name.>
для amd64 оказалось недостаточно указать vm.kmem_size_max=1G в /boot/loader.conf,
(параметр был просто игнорирован) по-этому это надо сделать в файле конфигурации ядра:
net.inet.tcp.syncache.hashsize=1024
net.inet.tcp.syncache.bucketlimit=100
net.inet.tcp.tcbhashsize=4096
kern.ipc.nsfbufs=10240
vm.kmem_size=1G
# ядро для i386 нужно собрать с "options KVA_PAGES=512"
vm.kmem_size_max=1G
/etc/sysctl.conf
net.inet.tcp.blackhole=1
net.inet.udp.blackhole=1
kern.ipc.nmbclusters=0 # Отсутствие ограничений на размер зоны
kern.ipc.nmbclusters=262144
kern.ipc.somaxconn=4096
kern.ipc.maxsockets=204800
kern.maxfiles=204800
kern.maxfilesperproc=200000
net.inet.ip.portrange.first=1024
net.inet.ip.portrange.last=65535
net.inet.ip.portrange.randomized=0
net.inet.tcp.maxtcptw=40960
net.inet.tcp.msl=30000
net.inet.tcp.syncookies=1
net.inet.tcp.nolocaltimewait=1
net.inet.tcp.fast_finwait2_recycle=1