Перейти к содержанию

2. Архитектура HAProxy

Процесс, потоки, цикл событий, chroot, журналы, часы и модель TCP-прокси

HAProxy — многопоточный неблокирующий демон с событийной архитектурой. Это означает, что для планирования всех своих действий он использует мультиплексирование событий, вместо того чтобы полагаться на системный планировщик при переключении между ними. Чаще всего он работает как один процесс, поэтому вывод “ps aux” в системе показывает только один процесс “haproxy”, кроме случаев, когда идёт плавная перезагрузка конфигурации и старый процесс завершает работу параллельно с новым. Поэтому его работу всегда легко отслеживать с помощью утилиты strace. Чтобы масштабироваться с ростом числа доступных процессоров, по умолчанию haproxy запускает по одному рабочему потоку на каждый процессор, на котором ему разрешено выполняться. Если явно не задана иная конфигурация, входящий трафик распределяется между всеми этими потоками, каждый из которых выполняет один и тот же цикл событий. Особое внимание уделяется сведению зависимостей между потоками к строгому минимуму, чтобы добиться масштабируемости, близкой к линейной. Это имеет ряд следствий, в частности каждое соединение обслуживается одним потоком. Поэтому для использования всей доступной вычислительной мощности необходимо как минимум столько же соединений, сколько потоков, что почти всегда обеспечивается.

HAProxy спроектирован так, чтобы при запуске изолировать себя в окружении chroot, где он вообще не может обращаться к файловой системе. Это относится и к библиотекам, от которых он зависит (например, libc, libssl и т. д.). Непосредственное следствие: работающий процесс не может повторно загрузить файл конфигурации, чтобы применить изменения; вместо этого запускается новый процесс с обновлённым файлом конфигурации. Есть и менее очевидные последствия: некоторые файлы часовых поясов или настройки разрешения имён, к которым libc может попытаться обратиться во время выполнения, не будут найдены, хотя обычно этого происходить не должно, поскольку после запуска они не нужны. Полезное следствие этого принципа — процесс HAProxy совершенно не сохраняет состояние, и после его принудительного завершения не нужна очистка: любой действенный способ завершения приведёт к правильному результату.

HAProxy не записывает файлы журналов, а использует стандартный протокол syslog для отправки записей на удалённый сервер (который часто находится на той же системе).

Для соблюдения тайм-аутов HAProxy использует внутренние часы, которые основаны на системном времени, но корректируют неожиданные отклонения. Для этого ограничивается время ожидания события в poll() и измеряется фактическая длительность ожидания. На практике оно никогда не превышает одну секунду. Поэтому при запуске strace для полностью бездействующего процесса видны периодические вызовы poll() (или одного из его вариантов), заключённые между двумя вызовами gettimeofday(). Это нормально, совершенно безвредно и настолько дёшево, что создаваемая нагрузка совершенно незаметна в масштабе системы; ничего необычного здесь нет. Пример:

16:35:40.002320 gettimeofday({1442759740, 2605}, NULL) = 0
16:35:40.002942 epoll_wait(0, {}, 200, 1000) = 0
16:35:41.007542 gettimeofday({1442759741, 7641}, NULL) = 0
16:35:41.007998 gettimeofday({1442759741, 8114}, NULL) = 0
16:35:41.008391 epoll_wait(0, {}, 200, 1000) = 0
16:35:42.011313 gettimeofday({1442759742, 11411}, NULL) = 0

HAProxy — TCP-прокси, а не маршрутизатор. Он работает с установленными соединениями, проверенными ядром, а не с пакетами какого-либо вида или сокетами в других состояниях (например, не с SYN_RECV и не с TIME_WAIT), хотя их наличие может помешать привязке к порту. Приём входящих соединений и установление исходящих он поручает системе. Непосредственное следствие: пакеты, наблюдаемые с двух сторон пересылаемого соединения, не связаны друг с другом и могут различаться размером, количеством и даже семейством. Поскольку соединение можно принять только с сокета в состоянии LISTEN, все прослушиваемые сокеты обязательно видны при использовании утилиты “netstat” для отображения прослушивающих сокетов. Пример:

# netstat -ltnp

Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1629/sshd tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2847/haproxy tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 2847/haproxy