Как стать автором
Обновить

Комментарии 13

Внутренний мир: Project Reactor

Эх, ожидал увидеть техническое внутреннее устройство реактора: как работает event loop и т.д. На эту совсем немного информации.

Еще вопрос, вы сталкивались с практическим применением back pressure? Видел много статей и везде одно и то же: смотрите как это круто, мы можем регулировать количество получаемых данных. И пример в стиле helloworld. Только в одном докладе (по моему с Джокера) видел хороший пример с server side events с котировками биржи. Но такое не так и часто встречается в приложениях. Короче, если есть реальные примеры практического применения back pressure, был бы рад почитать.

Сообщать источнику о необходимости тормознуть нагрузку - вполне разумный подход. Пример - балансировщик раздаёт запросы в цикле на все обработчики последовательно, но запросы обрабатываются за разное время, поэтому какому-то обработчику поплохеет и он либо упадёт, либо скажет балансировщику прекратить нагружать себя. Второй вариант явно лучше.

Другое дело, что вместо примитивнейшего анализа результата вызова функции разного рода реакторы предлагают сложную конструкцию из десятков описанных в статье интерфейсов и к ним какой-то сложной логики. Зачем нам такие предложения - непонятно, ну и статья ничего не проясняет.

либо скажет балансировщику прекратить нагружать себя

Так, а куда запросы денутся, которые балансировщику продолжают прилетать? Буферы тоже не резиновые. Если балансировщик скажет не слать запросы дальше по цепочке, то придем мы к тому в конечном счете, что пользователю скажут: эй, не работай пока, мы не вывозим. Так что ли? Выглядит странно. Это я не говорю уже о том, что через текущий http это непросто сделать.

Сообщать источнику о необходимости тормознуть нагрузку - вполне разумный подход

Подход прекрасный, интересно как это работает в жизни

Ну, если ресурсов и правда нету, то лучше и правда сказать части пользователей "эй, не работай пока, мы не вывозим", чем повиснуть сразу для всех пользователей.


И нет, сделать это через http довольно просто, даже код статуса соответствующий есть — 503 Service Unavailable.

даже код статуса соответствующий есть — 503 Service Unavailable

Это же не про реактивность. Я про то, что back pressure с бэка транслируется на фронт

Ну, если ресурсов и правда нету, то лучше и правда сказать части пользователей "эй, не работай пока, мы не вывозим", чем повиснуть сразу для всех пользователей.

Для пользователя это по сути одно и то же. Можно вот просто 503 отстреливать без всякой реактивности и back pressure

Без реактивности — можно, без back pressure — не получится

Вы рассуждаете про идеальную нагрузку. Но в реальности обработка различных запросов требует различного времени. Поэтому при циклическом распределении запросов по обработчикам будем иметь недогруженные и перегруженные единицы. Если перегруженная единица пропускает один-два хода, то недогруженные отлично справятся с этими перенесёнными на них запросами.

Вы на практике пробовали то, о чем рассказываете? Имею в виду пример с балансировщиком и reactor с back pressure?

Я не вижу смысла в реакторе, поэтому никогда с ним не работал.

Добрый день!

Очень интересно, десяток описанных интерфейсов - это интерфейсы из паттерна "Издатель-подписчик"? Собственно, там все на них построено)

А вообще, back pressure - это лишь одна из возможностей реактора (вообще, реактивных стримов). Главная особенность реактора, по мне - это управление потоками. У статьи не было цели рассказать о каких то преимуществах, потому ничего о "Зачем нам такие предложения" и не написано.

И все же, вот пример (абсолютно выдуманный из головы). У нас есть некий поток объектов. Нам нужно конвертировать его в сообщение, подгрузить два поля для этого сообщения по http, затем обновить его в бд и, наконец, кинуть некое уведомление и создать соответствующий эвент в кафке. Мы можем выполнить это все последовательно, но львиную долю всех этих действий можно выполнять параллельно. и на реакторе такой код будет примерно таким:

Код на реакторе
Flux.fromIterable(List.of())
		.flatMap(this::convert)
		.publishOn(Schedulers.boundedElastic())
		.flatMap(message -> {
			return Mono.zip(
					loadField1(message),
					loadField2(message)
			).flatMap(t -> {
				Object field1 = t.getT1();
				Object field2 = t.getT2();
				message.setf1(field1);
				message.setf2(field2);
				return update(message);
			}).then(Mono.when(
					sendNitification(message),
					createEvent(message)
			));
		});

Опишу некоторые подробности:

  1. Каждое сообщение обрабатывается асинхронно (каждое последующее сообщение не ждет окончания обработки предыдущего).

  2. Два поля подгружаются параллельно (http запросы).

  3. После подгрузки полей выполняется обновление в бд.

  4. После обновления в бд параллельно отправляется уведомление и создается событие.

Такой же код без реактора будет содержать кучу Future и каллбэков, и я даже представлять не хочу, как он будет выглядеть.

А если еще представить, что объекты приходят к нам, например, из кафки, то тут и открывается полезность back pressure: мы имеем возможность регулировать поток входящих объектов в зависимости от наших ресурсов. Но это уже в теории, на практике использовать back pressure мне не приходилось.

Спасибо за ответ с примером. Да, если есть цель задействовать функциональный стиль, тогда предложенные дополнения станут полезными. Правда непонятно, как могла бы выглядеть альтернатива в том же функциональном стиле, но спроектированная по другому. Возможно она была бы проще, хотя это, разумеется, лишь предположение.

С другой стороны, если не стремиться везде писать в функциональном стиле, ваш пример очень коротко мог бы быть записан в привычном императивном стиле, правда с передачей обработчиков при вызове метода, что кто-то может назвать продолжением функционального стиля. Но это все выглядит очень компактно и в реальности передаётся даже не ссылка на функцию, а полноценный объект со всеми свойственными ООП характеристиками. Компактность достигается за счёт стандартного синтаксиса типа this::myMethodName. Суммарное количество строк при такой реализации было бы, скорее всего, немного побольше, чем в приведённом вами примере, но разница состояла бы из одной фигурной скобки на строку, или из пустых строк между методами, что не напрягает психологически, но наоборот, вносит важные разделители, визуально отделяющие соответствующие части логики.

Собственно по вашему примеру - там всего два места ветвления потоков, поэтому вызовов с передачей this::myMethodName будет всего два, что очевидно совсем не захламляет код. Остальное будет очень близко к вами предложенному варианту.

В целом мы обсуждаем очередной вопрос стиля. Если цель состоит именно в обязательности функционального стиля, тогда вероятно вам нужен реактор, но если не нагружать разработчиков обязательностью исключительно стилистических формальностей, то реактор получается не нужным. Хотя я понимаю, что нередко желание писать именно в том стиле, который нравится, намного важнее для большинства разработчиков, нежели вопрос о включении в проект той или иной библиотеки. Для таких случаев, видимо, и должен существовать реактор.

Добрый день!

Что то мне подсказывает, что event loop в реакторе отсутствует, потому я про него ничего и не написал). В статье я описал общий принцип работы, в остальном - в реакторе присутствует куча реализаций Flux и Mono (описывать которые - дело скорее документации, чем статьи), да утилитные классы. Кстати, думал как раз следующим в netty покопаться - вот там event loop, кажется, есть.

Кстати, распределение нагрузки на балансировщике, предложенный комментатором выше, вполне себе хороший вариант при определенных условиях. Например, если запросы от одного пользователя должны приходить в одну реплику - такую стратегию мы использовать не сможем.

Что то мне подсказывает, что event loop в реакторе отсутствует, потому я про него ничего и не написал).

Возможно, и нету. Обычно про event loop пишут в статьях по WebFlux, но, может, действительно это все находится внутри netty. Там есть точно.

Кстати, распределение нагрузки на балансировщике, предложенный комментатором выше, вполне себе хороший вариант при определенных условиях.

Я уже выше написал вопрос про то, куда девать запросы, которые продолжают приходить балансировщику. Тут два варианта: 1) копить в буфере, будет работать на небольших скачках 2) как-то научиться передавать дальше по цепочке back pressure, тут не очень понятно что делать с фронтом. Оба решения не самые хорошие по ряду причин

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории