From: "Valentin Nechayev" <netch@segfault.kiev.ua>
Newsgroups: fido7.ru.unix.prog
Subject: Подходы к организации серверного приложения (как писать сервера)
Date: Mon, 2 Feb 2004 08:04:15 +0000 (UTC)
RU.UNIX.PROG FAQ - приложение 1
$Id: FAQ.a1,v 1.6 2003/05/12 19:35:59 netch Exp $
>Q: Как писать сервера?
A: (Lev Walkin, Dmitri Lenev, множественные дополнения, особенно от
Igor Sysoev и Igor Khasilev)
Возможны следующие варианты:
1. Сервер может использовать однопросессную FSM (Finite State Machine)
архитектуру, используя те или иные методы получения данных о состоянии
сокетов (select, poll, etc).
Плюсы:
+ Очень эффективный метод с точки зрения CPU.
Минусы:
- Не масштабируется с ростом числа процессоров.
- Серверные FSM, как правило, достаточно сложны и
требуют тщательного подхода при проектировании.
- в случае если обработка данных пришедших по соединению
требует долгой (в частности блокирующей) операции, то на
время выполнения этой операции невозможно обрабатывать
другие соединения. Соответственно возникают проблемы с
задержкой обработки запросов...
Проблема в том что:
а) Например в случае ввода вывода на диск, неблокирующий ввод-вывод
по select/poll не всегда поддерживается...
б) даже если мы пользуемся другим механизмом не обладающим данным
недостатком, например kqueue, или aio, то нам все равно может быть
не доступна напрямую работа с файлом. Ну например есть библиотека
для работы с СУБД и нет возможности залезть в ее внутренности
чтобы получить файловые дескрипторы соответствующие соединениям с
сервером СУБД.
в) даже если мы имеем полный контроль над вводом выводом то может
возникать потребность в долгих вычислениях (то есть затык в
занятости процессора)... Ну можно конечно вручную пытаться
квантовать работу но это не всегда удобно...
В принципе все три проблемы можно решить используя для выполнения
длительных или блокирующих операций slave процессы или нити
делая их тем самым не блокирующими. В принципе про данный подход
можно посмотреть здесь: http://www.cs.princeton.edu/~vivek/flash99/
(Dmitri Lenev)
+ По собственному опыту могу сказать что имея скажем проработанную
библиотеку классов писать сервера на FSM достаточно легко...
Примеры реализации:
- innd
- squid (с ufs хранилищем)
- named (с поправкой на протокол UDP для большинства передач)
Пример реализации со slave процессами для блокирующих операций:
- squid с diskd
Пример реализации со slave тредами для блокирующих операций:
- squid с aufs
2. Сервер может использовать несколько процессов, каждый из которых
обслуживает собственного клиента:
Плюсы:
+ Простая модель данных
+ Скалируется с ростом числа процессоров.
+ Ошибки в одном процессе не приводят к отказу в
обслуживании остальных клиентов.
Минусы:
- Процесс - это достаточно тяжелый объект OS, поэтому
метод неприменим при большом количестве одновременно
работающих клиентов (больше нескольких десятков или
сотен).
- Несмотря на масштабируемость, модель очень тяжела
и в среднем гораздо менее эффективна, чем предыдущая.
Примеры реализации:
- Большинство MTA (sendmail, postfix, exim, qmail)
- Традиционные попперы (qpopper, cucipop, popa3d)
- И другие традиционные unix'овые сервисы (telnetd, rlogind, nnrpd,...)
У всех перечисленных выше время жизни процесса - время обслуживания клиента.
- apache 1.*
У apache - процессы форкаются заранее, процесс может жить
неограниченное время.
3. Сервер может использовать небольшое число процессов, каждый из
которых имплементирует FSM (a la пункт 1).
Плюсы:
+ Если уже имеется система по типу #1, то перевод ее
на рельсы системы #3 как правило, достаточно простой.
Это дает возможность сделать систему скалируемой за
счет очень небольших усилий.
Минусы:
- Все равно придется делать полную FSM.
4. Сервер - процесс, использующий нити (threads) для каждого клиента
(сокета).
Плюсы:
+ Небольшая сложность разработки, похожа на #2.
Требуется проработка механизмов защиты общих данных.
+ В зависимости от OS, модель может быть и масштабируемой,
и эффективной (Solaris, HP-UX).
Минусы:
- В зависимости от OS, модель может быть как неэффективной (Linux,
так как нить "весит" почти столько же, сколько и процесс), так и
не масштабируемой с ростом числа процессоров (FreeBSD
с user-space threads).
- (Igor Khasilev) Если планируется обслуживать одновременно большое число
подключенных клиентов (от тысячи и выше в зависимости от ОС) эта модель
может оказаться нерабочей по причинам: расход адресного пространства
на стек для каждой нити, большая нагрузка на планировщик и
ограничение на общее число нитей в системе (особенно в случае
1:1 модели). Иногда может спасти экстенсивный путь - переход на
64-битные платформы.
- Существенно затрудняется отладка.
Примеры:
- Oops! 1.*
- apache 2.*
- CommunigatePro (исходный код недоступен, но в Usenet можно найти много
деталей устройства с авторским описанием)
5. Сервер - процесс, использующий небольшое количество нитей, каждая
из которых обслуживает некоторое количество сокетов одновременно.
Плюсы:
+ На архитектурах с kernel-threads (Linux, Solaris)
обладает масштабируемостью и очень эффективна.
Минусы:
- Требуется разработка FSM по типу #1, плюс разработка
разграничения доступа к общим данным (#4).
- Не приносит масштабируемости на некоторых имплементациях
потоков (FreeBSD), поэтому в целом несколько менее
эффективна, чем #1.
6. Несколько процессов, каждый из которых поддерживает нескольких
клиентов путем выделения по потоку на клиента или методом #5.
Плюсы:
+ Система защищена от неустранимых сбоев при
обработке одного клиента, так как остаются работать
остальные процессы.
+ Система масштабируется с ростом числа процессоров.
Минусы:
- Очевидно, складывается сложность всех перечисленных
выше методов.
- Не предоставляет преимуществ перед #3 на одном
процессоре.
Некоторые методы получения состояния (активности) сокета
(файлового дескриптора):
Плюсы select():
+ Широкая портабельность.
+ Очень эффективен при относительно небольшом числе одновременно
активных сокетов (передача в ядро и назад по три бита на сокет).
Минусы select():
- На многих платформах максимальное ограничение на 1024 (иногда
другое) файловых дескрипторах не обходится без
перекомпилирования приложения или даже ядра системы (для
FreeBSD не нужно перекомпилировать ядро, только приложение).
- При большом количестве неактивных клиентов передача в ядро и
назад пустого состояния сокета представляет собой сплошные
накладные расходы.
Плюсы poll():
+ Не содержит имманентного ограничения на максимальное
количество обслуживаемых сокетов.
+ Достаточно широкая портабельность.
Минусы poll():
- Менее эффективен, чем select() (так как передает в ядро
и назад по восемь байт на сокет) (Реализация в Linux
использует особенно тормозной алгоритм обхода данных в poll())
Плюсы /dev/poll (Последние Solaris, патчи для Linux):
+ Не имеет ограничения на максимальное количество обслуживаемых
сокетов.
+ Из-за модели передачи event'ов вместо модели передачи
состояний, очень эффективен при обслуживании большого количества
клиентов, только часть из которых очень активна (типичная
ситуация для web- и другого вида серверов). Состояния неактивных
сокетов не вызывают расхода ресурсов.
Минусы /dev/poll:
- Не портабелен.
- Неадекватно реагирует на закрытие дескриптора, занесённого в список
контроля и не вынесенного оттуда перед закрытием. Дескриптор остаётся
в списке (числясь своим номером). Поэтому перед закрытием надо
обязательно выносить дескриптор из списка.
Ещё про /dev/poll см. http://soldc.sun.com/articles/polling_efficient.html
Плюсы kqueue/kevent (FreeBSD, OpenBSD):
+ Не имеет ограничения на максимальное количество обслуживаемых
сокетов.
+ Имеет "вкусные фичи", которые позволяют использовать его
более эффективным образом не только для сокетов, но и для
объектов другого типа (файлов, процессов).
+ Из-за модели передачи event'ов вместо модели передачи
состояний, очень эффективен при обслуживании большого количества
клиентов, только часть из которых очень активна (типичная
ситуация для web- и другого вида серверов). Состояния неактивных
сокетов не вызывают расхода ресурсов.
+ (Igor Sysoev) kqueue/kevent эффективнее, чем /dev/poll.
Минусы:
- Не портабелен.
- Линус Торвальдс его не любит. (См.
http://www.uwsg.iu.edu/hypermail/linux/kernel/0010.3/0013.html; впрочем,
epoll повторяет тот же "silly" триплет)
Плюсы realtime signals (sigtimedwait,
http://www.kegel.com/c10k.html#nb.sigio, Linux 2.4+):
+ Не имеет ограничения на максимальное количество обслуживаемых
дескрипторов. (Однако, количество сигналов ограничено, и если дескрипторы
группировать по сигналам, внутри группы придется опрашивать все
дескрипторы.)
Минусы realtime signals:
- Есть в слишком малом количестве систем.
- Очередь сигналов может переполняться. (Linux в этом случае даёт SIGIO,
что означает необходимость итерирования всех дескрипторов. Но это лучше,
чем замалчивание переполнения очереди.)
- Хуже kqueue/kevent - только один сигнал обрабатывается за раз; kevent()
может принять и передать несколько событий за один вызов.
Плюсы epoll (Linux 2.5.44+):
+ Не имеет ограничения на максимальное количество обслуживаемых
сокетов.
+ Из-за модели передачи event'ов вместо модели передачи
состояний, очень эффективен при обслуживании большого количества
клиентов, только часть из которых очень активна (типичная
ситуация для web- и другого вида серверов). Состояния неактивных
сокетов не вызывают расхода ресурсов.
Вообще, epoll похож на kevent. 4-я версия научилась level triggering
в дополнение к edge triggering (что уже умела kqueue/kevent).
Минусы epoll:
- Не портабелен - только Linux и только после 2.5.44.
- Слабее по возможностям чем kqueue/kevent (нет наблюдения за процессами,
файлами, таймерами, завершениями AIO запросов; только одно событие
на входе и выходе системного вызова).
Еще по epoll см. http://www.uwsg.iu.edu/hypermail/linux/kernel/0210.3/1164.html
Поднятые здесь вопросы также обсуждаются в документе по адресу:
http://www.kegel.com/c10k.html
Еще стоит посмотреть в сторону D.C. Schmidt'овкого ACE и JAWS,
если не в сторону первого так в сторону последнего как теоретически -
экспериментального исследования...
1072 Прочтений • [Подходы к организации серверного приложения (как писать сервера) (select fork socket poll threads gcc)] [08.05.2012] [Комментариев: 0]