0. Введение
todo
1. EDA. определение.
Первые упоминаний event-driven architecture уходят в 1970тые будет свое начало Интересно заметить что описание на wiki звучит: "Событие можно определить как «существенное изменение состояния»[1]. Например, когда покупатель приобретает автомобиль, состояние автомобиля изменяется с «продаваемого» на «проданный». Системная архитектура продавца автомобилей может рассматривать это изменение состояния как событие, создаваемое, публикуемое, определяемое и потребляемое различными приложениями в составе архитектуры."
Ничего не напоминает? использованием в определении "state", т.е. не так давно появившихся подходов state-management. Интересно что использование state-management'а появилось только сейчас.
Интересно еще отметить "по структуре более ориентированы на непредсказуемые и асинхронные окружения", т.е. это
Системы посторнные по EDA выделяются наличием:
- Генератор событий
- Канал событий
- Механизм обработки событий
- Последующее действие, управляемое событиями
на язык фронтенда это можно перевести как:
??? 1. action ??? 2. - ??? 3. handlers ??? 4. reactions
2. Немного истории
2.1 apache
Давным давно в далекой далекой галактике появился apache HTTP server (созданный в 1995 году), так как это первый проект apache foundation, его обычно называют просто apache. Важную роль в apache выполняет модуль MPM (Multi-Processing Module), который, грубо говоря, отвечает обработку нескольких запросов несколькими процессами. Один из наиболее частых режимов использования которого mpm_prefork. Суть его в том, что на 1 запрос обрабатывает отдельный процесс с одним потоком. В этом режиме может быть запущено определенное количество X
процессов, их количество, по большей части ограничивается RAM на сервере. И как вы думаете, что произойдет если придет X+1
запрос? Верно, он будет ждать пока освободится процесс.
TODO: либо demo, либо анимацию с примерами.
С одной стороны этот подход не преспособлен под большие назрузки, но у него есть и определенные плюсы о которых мы поговорим позже.
2.2 проблема C10k
В 1999 администратор популярного публичного FTP_сервера Simtel Ден Кегель обнаружил, что по аппаратным показателем средний узел его сети был готов держать 10k соединение, но программное обеспечение не могло выдать такой результат. [http://kegel.com/c10k.html]
2.3 nginx
NB: здесь, кстати, в 2002-2003 году начали вестись разговоры про SPA.
Но! через 5 лет после обозначения этой проблемы (в 2004 году) вышел первый релиз такого веб-сервера как nginx. nginx изначально был спроектирован на базе асинхронных неблокирующих event-driven алгоритмов. Каждый запрос помещается в event loop. В этом цикле события обрабатываются асинхронно, позволяя обрабатывать задачи в неблокирующей манере. Когда соединение закрывается оно удаляется из цикла.
https://www.eyerys.com/sites/default/files/nginx-apache-10kreqs.png
TODO: либо demo, либо анимацию с примерами.
А еще через 5 лет что появилось? Кто угадает?) Правильно платформа Node.js
2.4 libuv (Node.js)
В осонову платформы node.js легла библиотека libuv с реализацией event-loop. Что она на себя берет:
- во-первых, кроссплатформенность, так как она абстарагиерует работу с epoll kqueue и прочими.
- предоставляет API хендлеров и реквестов (к диску, сети, DNS резолв и пр.).
- собственно event loop (в дефолтной конфигурации один, в такой конфигурации его используется node.js, но так же можно создавать и несколько).
2.5 select poll epoll
Еще один важный момент измененный за это время это способ работы с "внешним миром для процессора", таким как файлы, сеть и т.д. В POSIX системах все является "файлом" будь то socket, file etc. И для работы с этим используются файловые дескрипторы. Изначать был select() он реализован еще в 1980ых годах когда сокеты назывались "сокетами Беркли" и никто, видимо, не продполагаю что в будущем появится сильная необходимость писать многопоточные приложения. более новый poll() стал более производительным при использовании многопоточности и на 1000 fd был быстрее select в ~40 раз. В epoll() стало еще лучше, нам сразу видно какие дескрипторы ждут, чтобы из них прочитали или в них записали данные, но здесь добавляется немного больше системных вызовов через poll() /TODO проверить, вот эта https://it.wikireading.ru/3323 статья все опровергает/и по сути, выйгрыш в производительности виден только на большом количестве "не активных" дескрипторов (порядка 10к). node.js в linux (простите, в gnu/linux) работает через epoll.
https://monkey.org/~provos/libevent/libevent-benchmark.jpg https://monkey.org/~provos/libevent/libevent-benchmark2.jpg
2.6 TCP соединения
Еще есть ньюанс что каждый браузер при открытии страницы делать разное количество TCP соединений и через разные промежутки это обрывает. Есть флаг keep-alive через который это можно более менее регульровать.
2.7 Demo libuv server
TODO пример реализации сервера на libuv:
int main() {
loop = uv_default_loop();
uv_tcp_t server;
uv_tcp_init(loop, &server);
uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);
uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
int r = uv_listen((uv_stream_t*) &server, DEFAULT_BACKLOG, on_new_connection);
if (r) {
fprintf(stderr, "Listen error %s\n", uv_strerror(r));
return 1;
}
return uv_run(loop, UV_RUN_DEFAULT);
}
3. event loop
3.1 анимированный представления
Про event loop очень много хороших графических представлений, как они работают и как отображаются. Возможно их было бы меньше будь у нас инструменты для отладки и мониторига состояния event-loop, так как сейчас они очень скудны. Но давайте рассмотрим пару интересных отсылок. https://www.youtube.com/watch?v=cCOL7MC4Pl0
3.2 реализации
3.2.1 libuv TODO
TODO: дать описание на основеоснове доки
http://docs.libuv.org/en/v1.x/_images/architecture.png
3.2.2 tokio
Сделаль сравнение с libuv
https://tokio.rs/docs/overview/
Как по мне, так там принципы очень похожи, единственная разница в "подстраивании под язык", libuv - C, tokio - rust. + в tokio из коробки идет реализаций future.
3.3 event loop в браузерах
В браузерах есть много отличных ньюансов, которые позволяют им быть гораздо проще. Во-первых, нет "внешнего мира" кроме как "брузерных абстракций".
The event loop is the mastermind that orchestrates: what JavaScript code gets executed when does it run when do layout and style get updated render: when do DOM changes get rendered It is formally specified in whatwg's HTML standard.
3.3.1 браузеры и отличие event loop от серверного.
3.3.2 event loop спецификация.
У whatwg есть описание как должен выглядеть event loop.
Но, соотвествено реализации в браузерах расходятся и
TODO расширить из https://github.com/atotic/event-loop
3.3.3 chromium
TODO из https://github.com/atotic/event-loop
3.4 puzlers
1. https://cdn.rawgit.com/atotic/event-loop/caa3cfd4/rendering-events.html
2. https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
3. пример из доклада арчибальда про click из браузера и из кода.
4. problems
Несмотря на плюсы которые нам предоставляет EDA она с собой несет и минусы. Самая основная проблема, это то, что event loop, в отличии от многопоточных подходов является своеобразным бутылочным горлышком. Все события выстраиваются в единую очередь и если один обработчик будет выполняться долго, то его будут ждать все остальные обработчики, т.е. на отдельное событие может повлиять события с ним не связанные. Для такое ситуации есть отдельный термин - отравление обработчика. Давайте это подробнее рассмотрим.
4.1 отравление обработчика
TODO: анимированное представление. если мы добавили в очередь тысячу событий, и 500ое будет выполняться 1 минуту, то у всех событий с 501 до 1000 задержка будет не меньше 1 минуты. Поэтому очень важно следить за тем, что именно представляют собой обработчики.
4.2 redos
Один из интереснейших примеров таких уязвимостей это Regular expression Denial of Service [ReDOS].
Суть ее заключается в том, что регулярные выражения по своей природе рекурсивны из-за чего можно найти определенную строку, которая, при разборе ее регуляркой, сможет "съесть" много ресурсов. Для того кто знаком со временной сложностью алгоритмов, "O" большое для разбора по регулярке - экспоненциально, т.е. O(c^n), что очень плохо. Если вас это не пугает, то вот вам картинка, на ней экспоненцианалое время оранжевое (из класических примеров хуже только факториальное) https://twitter.com/jsunderhood/status/1169947400804425732/photo/1
А вот и на эту тему науч статья) "Freezing the Web: A Study of ReDoS Vulnerabilities in JavaScript-based Web Servers" https://usenix.org/system/files/conference/usenixsecurity18/sec18-staicu.pdf
Но если вам скучно читать это полотно текста, то вот есть примеры: https://github.com/sola-da/ReDoS-vulnerabilities Эта подборка примеров ReDoS уязвимостей в популярных библиотеках.
Там есть и lodash, и moment, и underscore.string. Благо что большинство этих проблем поправлены. И вот разобор от snyk на эту тему - https://snyk.io/blog/redos-and-catastrophic-backtracking/ В ней все с примерами доходчиво объясняется.
4.3 Упадет воркер - потеряются все коннекты с воркера
Еще один важный ньанс, касающийся Node.js. Если окажется так, что один из обработчиков повлечет за собой падение приложения. То все события находящиеся в очереди будут потеряны.
Давайте разберем пример. Представим что у нас есть сервер принимающий финансовые транзакции по http. TODO возможно стоит придумать более интересный пример..
TODO Demo показать пример в консоле.
4.4 Нет инструментов для анализа того что присходит в eventloop
Это все усугубляется тем, что инструментов, которые дают возможность посмотреть что происходить в event loop у разработчиков практически нет. У Вас нет возможности сохранить dump событий и после поднятия приложения запустить их заново, это нужно делать только на стороне приложений которые общаются с нашим приложениям, т.е. всегда продумывать логику перепосылки и прочего.
4.4.1 Node.js
В Node.js не так давно велась разработка по предоставлению API для трассировки event loop TODO раскрыть детали: feature request: a way to inspect what's in the event loop https://github.com/nodejs/node/issues/1128 https://github.com/nodejs/node/issues/19063 https://github.com/nodejs/node/issues/19158 https://github.com/libuv/libuv/pull/1764 https://github.com/nodejs/node-report
4.5 Нет управляемой приоритизацией
как мы рассмотрели в libuv и с примерами в пазлерах, процесс обхода event loop является синхронной функцией, которая выполняет задачи по заранее заданной последовательности и это нельзя изменить. Но есть определенные хаки который рассмотрим чуть позже.
5. нахождение проблем
Как находить такие проблемы
5.1 devtools
В devtools основной инструмент для этого это timeline, на котором можно отслеживать время выполнения той или иной функции. TODO: demo с devtools в котором можно рассмотреть скрытые ньюансы.
5.2 Нахождение проблем, fuzzing
TODO: взять отсюда интересные моменты если они там есть http://people.cs.vt.edu/dongyoon/papers/EUROSYS-17-NodeFz.pdf
5.3 snyk
snyk. Следить за обновлениями и блабла Так же можно встроиться в CI. Показать пример.
5.4 добавление проверок в CI
TODO: статистика с devtools TODO: статистика со snyk
6. решение проблем
Какие же есть решения?
6.1 высвобождение основного потока
все handlers должны быть максимально "тонкие" все остальное выполнять вне основного потока.