FAQ appendix 1: как писать сервера

From
Valentin Nechayev ()
To
All ()
Date
2003-06-02T11:04:10Z
Area
RU.UNIX.PROG
From: "Valentin Nechayev" <netch@segfault.kiev.ua>

RU.UNIX.PROG FAQ - приложение 1

$Id: FAQ.a1,v 1.5 2003/03/22 07:17:39 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 модели)
    - Существенно затрудняется отладка.

Примеры:
- Oops!
- 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:
  - Есть в слишком малом количестве систем.
  - Очередь сигналов может переполняться.
  - Хуже 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,
если не в сторону первого так в сторону последнего как теоретически -
экспериментального исследования...
--- ifmail v.2.15dev5
 * Origin: Dark side of coredump (2:5020/400)