nxweb - це новий вбудований високопродуктивний веб-сервер для програм на Сі. За функціональністю це фреймворк для написання обробників HTTP запитів. Аналоги: G-WAN/libevent/Mongoose, Apache/mod_<ваш улюблена мова >, Tomcat, Node.js. Розробник - Ярослав Ставничий. Мене проект зацікавив насамперед тим, що він представляє реальну альтернативу існуючим рішенням, кожне з яких володіє своїми недоліками. Вибір - це добре. Можливо, і вам сподобається поєднання особливостей, плюсів і мінусів цього сервера.
- Q: Розкажи про себе, чим займаєшся, де працюєш?
- Q: Значить nxweb створювався з метою - крутити банери?
- Q: Висока відвідуваність - це скільки запитів на секунду?
- Q: Тому що mongoose використовує один OS потік на кожен запит?
- Q: Добре, розкажи про архітектуру nxweb коротко.
- Q: Чому кожен мережевий потік не робить accept цикл? Тоді OS зможе розподіляти коннекти і можна зробити кілька accept-ів паралельно на різних ядрах.
- Q: Зрозуміло. Перша версія використовувала libev, але зараз у wiki написано, що залежності від libev немає. Що змінилося?
- Q: Саме Edge-Triggered дозволив отримати останній приріст?
- Q: Ігор Сисоєв і автори libev стверджують, що у epoll є маса проблем. Ти вже стикався з ними?
- Q: Що щодо резервування пам'яті? Ти аллоціруєш якийсь великий пул, щоб робити менше сисколів?
- Q: Ти використовуєш власну реалізацію парсера HTTP або брав щось готове?
- Q: З іншого боку, я б не дав нікому крім nginx слухати зовнішній порт бойового сервера. А за ним всі некоректні запити вже відфільтровані.
- Q: Є ряд бібліотек, які роблять в Сі userland потоки, одна з них libtask, використовується в mongrel2. У кожної корутини свій стек, тому бібліотека містить кілька інструкцій на асемблері для перемикання стека. Це дозволяє писати звичайний послідовний код, без колбеків типу on_request, on_success...
- Q: Істотний виграш може бути в підтримуваності коду обробників, коли люди захочуть не просто request-response, а, наприклад, sleep в середині або в базу сходити. Для простої банерокрутилки, звичайно, ніякого сенсу в них немає.
- Q: Як би ти описав поточний статус nxweb? Стабільність, помилки?
- Q: Особисто мені як потенційному розробнику під nxweb потрібна можливість повністю керувати обробкою всіх запитів і товста бібліотека допоміжних функцій (це доречно у вигляді модулів), типу порівняй урл, sendfile, встав заголовок, розпарси кукі, запроксируй на бекенд. Саме так я уявляю собі програмування критичних ділянок на nxweb.
- Q: Наступне питання, швидше для галочки, що ти думаєш з приводу підтримки Windows?
- Q: В G-WAN є досить зручна фіча - автокомпіляція додатків. Звичайно, тут є маса відкритих питань, типу безпеки, прапорів компілятора, оновлення на льоту. Чи плануєш ти додати таку ж фічу в nxweb?
- Q: Добре, більш нагальне питання, в якій формі ти плануєш поширювати nxweb? Дерево вихідних? Бібліотека? Статична/динамічна?
- Q: Припустимо, я хочу написати свій hello world додаток. Мої дії?
- Q: Ще потрібна якась документація, опис можливих модулів. Приклади вирішення типових завдань.
- Q: Далі - де спілкуватися зацікавленим розробникам. Розсилка/форум. Якесь місце для питань і відповідей, з історією і пошуком.
- Q: Коли будуть стабільні версії, заморожування API?
- Q: access log якраз не потрібен, потрібно логувати програму.
- Q: Далі, за планами. Було б чудово конфігурувати кількість тредів автоматично. Я бачу тут два напрямки: 1) при запуску визначити кількість ядер і поставити стільки потоків, 2) породжувати нові потоки поки LA нижче заданого значення, ну і з верхнім обмеженням, звичайно.
- Q: Проксирувати і SSL, як nginx вміє. Не зовні ставити nxweb.
- Q: Схоже що use case-и nxweb можна розділити на дві великі категорії, з різними потребами:
Під катом детальна інформація про проект з інтерв'ю з розробником.
Q: Розкажи про себе, чим займаєшся, де працюєш?
Yaroslav: Я давно займаюся розробкою софту. Компанія Nexoft. Пишу в основному на Java, але і C/C + + періодично згадую, коли потрібно щось швидкісне зробити. Наприклад, рушій банерокрутилки для сайту з високою відвідуваністю.
Q: Значить nxweb створювався з метою - крутити банери?
Yaroslav: Ну, не тільки заради банерів, але це одне з перших завдань. Конкретне завдання, принаймні, не абстрактне.
Q: Висока відвідуваність - це скільки запитів на секунду?
Yaroslav: Зараз приходить близько 100 запитів на секунду. Частина з них відправляється на Tomcat. Але в кожну HTML сторінку вставляється до 20 кодів банерів. Серверу, в принципі, важко, тому хочеться простим завданням віддачі коротких HTML фрагментів сильно його не обтяжувати. Може, звичайно, і на перлі це все працювало б, але ми легких шляхів не шукаємо.
Власне, движок такий у мене був давно, написаний як модуль для Apache, але останнім часом всі проекти переношу на nginx.
Взагалі писати модулі під Apache і nginx - не найприємніше заняття. Там купа мішури, нав'язаної турботою про переносимість. До того ж, вони багатопроцесні, а це окрема біда. З потоками (threads) все набагато простіше. Загальна пам'ять - безцінна. Я звик до Java, там саме так. Поліз всередину nginx, вже дуже все там громіздко, плюс, знову ж таки, shared memory тощо.
Вирішив спробувати реалізувати свій додаток на мікро-движку типу Mongoose, натикався на нього в мережі раніше. Став копати, знайшов ще кілька альтернатив, в т. ч. G-WAN. Дуже все в ньому сподобалося, крім closed-source. Я пробував G-WAN саме як сервер додатків, а не як відвантажувач статики. Але досить швидко став натикатися на глюки, знайти і виправити які не представляється можливим через закритість коду.
Отже, написав крутилку на Mongoose, став тестувати швидкодію і виявив, що вона вже боляче низька порівняно з G-WAN. Це вже потім я здогадався 2500 тредів запустити, а спочатку думав, що 8-16 вистачить. З такою кількістю там взагалі ніяк.
Q: Тому що mongoose використовує один OS потік на кожен запит?
Yaroslav: Так, власне, 2500 тредів - теж не рішення. Пам'яті вони жеруть тонну, і, знову ж таки, з'явиться 2501-й запит - і привіт...
Став копати глибше, знайшов microhttpd і libevent. За продуктивністю, якщо порівнювати з G-WAN, вони виглядали блідо. Поки я з усім цим розбирався, копався у вихідниках, прийшло розуміння, що написати веб-сервер не так вже й складно. У mongoose, наприклад, і так весь HTTP відповідь треба вручну писати розробнику додатка.
libevent виглядав найбільш обіцяним, але однопоточним. Вирішив переробити те ж на libev (полегшена альтернатива libevent). Так народився nxweb. Головна початкова мета - це веб-програми на C, а не повнофункціональний сервер. Якщо вже розробляєш щось на C, значить хочеш, щоб воно працювало гранично швидко, а значить, і платформа повинна бути швидкісною. А то напишеш під mongoose, і, питається, заради чого старався, якщо він все на гальмах спустить.
Конкурувати у мене ні з ким мети не було. Хотілося наблизитися до G-WAN, як до еталону швидкості. Але варто було опублікувати проект, як тут же пішов потік листів. Стали всі мене підзадорювати. Ось і довелося ще кілька днів витратити, попихтіти, щоб G-WAN таки обігнати.
Обігнав я його чи ні, поки не ясно. На своєму компі я його обігнав. Як кажуть, на своїй території. Знову ж таки, напевно не у всіх режимах.
До речі, nginx дуже порадував. Швидкість його HTTP стека вища, ніж у G-WAN (якщо правильно його налаштувати, звичайно). Просто nginx не кешує файли в пам'яті, якщо його не просять. З nginx взагалі конкурувати безглуздо. Навіть дивує, як такий функціональний сервер забезпечує таку високу швидкодію. Я тут переконався, що елементарне використання sprintf замість strcat здатне посадити швидкодію на 5-10 тис. запитів в секунду. Тобто. бій йде вже за кожну зайву інструкцію CPU.
Q: Добре, розкажи про архітектуру nxweb коротко.
Yaroslav: Основний потік слухає сокет і розсовує коннекти по мережевих потоках (у кожного своя черга, щоб уникнути м'ютексів). Мережеві потоки (їх має сенс запускати по одному на ядро процесора) забезпечують HTTP протокол. Декодують запит, передають його модулю-обробнику. Воркери - річ опціональна, задумані виключно для підтримки повільних обробників, щоб вони не стопорили мережевий потік.
Зараз у модулів є коллбек, який викликається сервером перед входом в основний цикл. Також є файл main.c, який можна прибрати або замінити. Його функція - виключно сервісна: відкрити лог-файл і передати управління в _nxweb_main (). Він також вміє запускати демона. Причому подвійного: один демон стежить за тим, щоб другий (де власне і крутиться nxweb) не зупинився. Якщо внутрішня фонова служба падає, перший перезапускає її автоматично. При бажанні main.c можна замінити на будь-який інший. Головне, він повинен викликати _nxweb_main ().
Q: Чому кожен мережевий потік не робить accept цикл? Тоді OS зможе розподіляти коннекти і можна зробити кілька accept-ів паралельно на різних ядрах.
Yaroslav: Я пробував обидва варіанти, в т. ч. accept мережевим потоком без участі основного. Різниці в швидкодії не виявив, повернувся до єдиного акцептора, так як мені здалося, що це знижує відсоток помилок з'єднання. У nginx і G-WAN частина з'єднань підвисає, і навантаження не несе. Див. мій коментар щодо real concurrency на сторінці бенчмарок.
Q: Зрозуміло. Перша версія використовувала libev, але зараз у wiki написано, що залежності від libev немає. Що змінилося?
Yaroslav: Я написав свій libev. Це якраз те, що я мав на увазі під «попихтіти, щоб обігнати G-WAN». Він заточений під мої потреби і самостійним проектом не є. Іншими словами, я тепер працюю з epoll безпосередньо. nxweb абсолютно не переносимо. Він працює тільки на Linux та ще на ядрі > = 2.6.22. Я використовую Edge-Triggered epoll, який libev не підтримує через його непереносимість. Так, libev - супер-класна річ, я б на ньому і зупинився, але народ захотів швидше. Ось довелося помучитися.
Q: Саме Edge-Triggered дозволив отримати останній приріст?
Yaroslav: Не тільки. Ще довелося над кодом попрацювати. Зокрема, виключити sprintf, нова система резервування пам'яті і т. п. Є думка прикрутити все це назад до libev. Може і не гірше буде, але не знаю, чи зберуся. Зробити перенесений код у мене завдання не стоїть. Хостинг практично завжди на Linux. А libev - це зайва залежність, яку треба інсталювати, щоб зібрати nxweb. теж мінус.
Q: Ігор Сисоєв і автори libev стверджують, що у epoll є маса проблем. Ти вже стикався з ними?
Yaroslav: Є надія, що ці проблеми в сучасному ядрі вже вичищені. Все-таки багато років минуло. Я тестую на 2.6.32 і 3.0.0, з проблемами не стикався.
Q: Що щодо резервування пам'яті? Ти аллоціруєш якийсь великий пул, щоб робити менше сисколів?
Yaroslav: Free-list для коннекшенів і свій аналог obstack для формування відповідей.
Q: Ти використовуєш власну реалізацію парсера HTTP або брав щось готове?
Yaroslav: Реалізація власна. Безумовно, я не підтримую абсолютно всі нюанси HTTP протоколу. Хоча, наприклад, 100-continue або chunked-encoding у мене є. Знову ж таки, відвантаження статики - це справа модуля. Ядро парсить запит, викликає обробник, отримує відповідь, обертає його в HTTP і відправляє клієнту. Тобто. кожні там ETag і Range - це завдання додатка. Зараз мій модуль sendfile.c формує лише основні заголовки, хоча напевно скоро зроблю підтримку Range і If-Modified-Since.
Так, напевно є готові парсери, перевірені на помилки. Але, з іншого боку, у власному парсері я швидше помилку виловлю, якщо мені про неї повідомлять, ніж у чужому. Тут реально рахунок йде на CPU-інструкції. А всі самостійні проекти парсерів настільки ґрунтовні, що ні про яку продуктивність з ними і мови бути не може.
Q: З іншого боку, я б не дав нікому крім nginx слухати зовнішній порт бойового сервера. А за ним всі некоректні запити вже відфільтровані.
Yaroslav: На даний момент (версія 2.0) nxweb не дуже готовий до того, щоб виставляти його назовні. В основному тому, що реальні програми складаються не тільки з C-скриптів. Потрібно підв'язувати і Java, і статику і все інше. Якщо тільки мова не йде про реалізацію якихось чатів, де важливо тримати багато тисяч одночасних з'єднань.
Q: Є ряд бібліотек, які роблять в Сі userland потоки, одна з них libtask, використовується в mongrel2. У кожної корутини свій стек, тому бібліотека містить кілька інструкцій на асемблері для перемикання стека. Це дозволяє писати звичайний послідовний код, без колбеків типу on_request, on_success...
Yaroslav: Так, про корутини чув. Але якщо я вже написав все колбеками, то виграшу в продуктивності переходом на корутини я не отримаю. Тільки зайва пам'ять піде на зберігання шибок. Та ще й проблеми з gdb - теж не найприємніше. Тут справа смаку і звички. До корутинів треба звикати. Це особлива концепція.
Q: Істотний виграш може бути в підтримуваності коду обробників, коли люди захочуть не просто request-response, а, наприклад, sleep в середині або в базу сходити. Для простої банерокрутилки, звичайно, ніякого сенсу в них немає.
Yaroslav: Для сліпів і в базу ходити - якраз для цього я і передбачив воркерів. І перевірив насамперед. Якщо сконфігуровано 100 воркерів і кожен воркер робить sleep 1 сек, то сервер рівно і без запинок відпрацьовує 100 запитів на секунду. Ні G-WAN, ні nginx з цим не справляються. Вони або видають 3-4 запити в секунду, або тупо виснуть.
Хоча, при гострій необхідності ходити в базу, можна написати для цієї мети неблокуючий адаптер. З сервлета буде один виклик - поставити запит до БД в чергу і зареєструвати колбек. Після цього повернення і далі працює колбек. В принципі, тут для зручності написання можна подумати і про корутинів.
Q: Як би ти описав поточний статус nxweb? Стабільність, помилки?
Yaroslav: Ну, я вже прикрутив його в якості бекенду до сайту з досить великою відвідуваністю, тобто запустив у продакшн. Пройшов вже місяць, і жодного перезапуску/збою. Хоча, бекенд - це не найжорсткіше бойове хрещення.
Вважаю, що статус - альфа. Зокрема, далеко не всі функції nxweb ретельно перевірені. Наприклад, chunked request encoding. На тестових прикладах працює, але мало що спливе. Також неясно, якою буде стабільність при некоректній поведінці клієнта. Наприклад, запити з помилками можуть спровокувати помилку.
Ще є проблема на поточному етапі - я досить активно змінюю h-файли, структури даних тощо.
Q: Особисто мені як потенційному розробнику під nxweb потрібна можливість повністю керувати обробкою всіх запитів і товста бібліотека допоміжних функцій (це доречно у вигляді модулів), типу порівняй урл, sendfile, встав заголовок, розпарси кукі, запроксируй на бекенд. Саме так я уявляю собі програмування критичних ділянок на nxweb.
Yaroslav: Треба сказати, що це приблизно і є мій підхід. Створює базові функції, за допомогою яких можна налаштувати роботу сервера. Але конфігурувати на С, а не за допомогою якогось конфіг-файлу. Тому що створити аналог конфіга, який є у nginx, це завдання дуже тяжке.
Q: Наступне питання, швидше для галочки, що ти думаєш з приводу підтримки Windows?
Yaroslav: На 99% виключено. Я взагалі холодно дивлюся на переносимість коду. По суті, саме через турботи про переносимість внутрішній інтерфейс nginx (та й apache) так заморочений. Доводиться відмовлятися від сучасних технологій на догоду переносимості коду. Я поки хочу сконцентруватися тільки на Linux.
Q: В G-WAN є досить зручна фіча - автокомпіляція додатків. Звичайно, тут є маса відкритих питань, типу безпеки, прапорів компілятора, оновлення на льоту. Чи плануєш ти додати таку ж фічу в nxweb?
Yaroslav: Так, мені вона дуже сподобалася. Але не планую поки. Зайвий гемор, який ніяк не наближає мене до того, щоб почати повноцінно використовувати nxweb для своїх потреб.
Q: Добре, більш нагальне питання, в якій формі ти плануєш поширювати nxweb? Дерево вихідних? Бібліотека? Статична/динамічна?
Yaroslav: Поки тільки в тому, якому вже поширюю. Відкритий сховище, звідки можна завантажити вихідний і виконати make. На даний момент повна компіляція проекту займає секунди. Сенсу городити бібліотеки поки не бачу.
Q: Припустимо, я хочу написати свій hello world додаток. Мої дії?
Yaroslav: hello.c - це шаблон модуля. Далі, у Makefile є невеликі коментарі про підключення своїх додатків. Там же є спеціальні змінні SRC_MODULES і INC_MODULES. Крім того треба додати посилання на свій модуль в modules.c.
Згоден, що процес роботи з модулями треба якось краще облаштувати, особливо якщо їх стане більше.
Організаційно розробка модулів може виглядати так: робиш форк nxweb на bitbucket, або клонуєш nxweb куди-небудь ще. Робиш там свої розробки, коммитиш і т. п. Щоб оновити nxweb робиш hg pull .../nxweb
Q: Ще потрібна якась документація, опис можливих модулів. Приклади вирішення типових завдань.
Yaroslav: Поки є приклад hello.c, по ньому багато що зрозуміло. А в іншому, соррі, тільки вихідний код. На bitbucket є wiki, там якась частина документації.
Q: Далі - де спілкуватися зацікавленим розробникам. Розсилка/форум. Якесь місце для питань і відповідей, з історією і пошуком.
Yaroslav: Днями я створив дві гугл-групи: nxweb и nxweb-ru.
Q: Коли будуть стабільні версії, заморожування API?
Yaroslav: Ой, не знаю... Заморожування API нескоро. Хоча, треба сказати, між 1-ю і 2-ю версією зміни в модулях були мінімальні. При тому, що внутрішності сервера були всі перероблені.
Q: Це хороший знак. Які плани щодо нарощування бібліотеки корисностей для додатків? Конкретніше: логування, рушій шаблонів, робота з заголовками, куками.
Yaroslav: Заголовки і куки вже парсяться в таблиці і їх можна витягувати за іменами. Не знаю, що ще з ними можна зробити. access_log - поки не потрібен був, в принципі він не складний, проте істотно сповільнить роботу. Бібліотека корисностей для початку поповниться функціями проксирування, я думаю.
Q: access log якраз не потрібен, потрібно логувати програму.
Yaroslav: Логування є спільне. Так званий error_log, функція nxweb_log_error ("" ",...). Він флушиться після кожного рядка.
Q: Далі, за планами. Було б чудово конфігурувати кількість тредів автоматично. Я бачу тут два напрямки: 1) при запуску визначити кількість ядер і поставити стільки потоків, 2) породжувати нові потоки поки LA нижче заданого значення, ну і з верхнім обмеженням, звичайно.
Yaroslav: Автоконфігурація - велике питання. nxweb в принципі поки не конфігурується інакше як шляхом перекомпіляції. Чи є сенс робити один параметр автоматично конфігурованим, не знаю. Насправді нескладно запускати при старті будь-яку автоматично визначену кількість тредів. Трохи складніше дозапускати або зупиняти треди в процесі, хоча теж реалізовано.
З практичних цілей я поки бачу реалізацію проксирування. Насамперед на Java. Це те, з чим я працюю. Потім - SSL, кероване кешування. Можливо, інтерфейс до БД.
Зараз запит і відповідь повністю буфферизуються. Це може бути погано для деяких завдань (наприклад, upload файлу). Тому я думаю ввести в модулях поняття обробників частково отриманих даних, а також відправку даних по частинах.
Q: Проксирувати і SSL, як nginx вміє. Не зовні ставити nxweb.
Yaroslav: Якщо не ставити його зовні, то все перевагу швидкості втрачається. Я вчора протестував nxweb позаду nginx: 25 тис. запитів на секунду. Притому що сам він віддає 160 тис., та й nginx 130 тис. вміє. Це для поточної стабільної версії nginx 1.0, у якого немає keep-alive для бекендів. У версії 1.1 виходить 50 тис. запитів на секунду - набагато швидше, але, все одно, більш, ніж триразове уповільнення.
Q: Схоже що use case-и nxweb можна розділити на дві великі категорії, з різними потребами:
- Багато rps для банерів, топлайнів тощо. Тоді його треба ставити назовні, тут без варіантів
- Навантаження за запитами невелике, але дуже важливо віддавати відповідь максимально швидко, тому шматок програми пишеться на Сі. Тут цілком доречно стати за nginx для зручності адмінів.
Yaroslav: Вважаю, що з новими версіями nxweb з'являться і нові use case-и.
