Mono.just и нереактивный пружинный пыльник

Недавно я начал работать с Spring Boot в новом проекте, который имеет следующий код (упрощенный):

@RestController
@RequestMapping("/users")
public class MyController {
  @Autowired 
  UserService service;
  @GetMapping 
  public Mono<ResponseEntity<User>> getUser() {

     // note, the non-reactive call is wrapped into Mono.just
     return Mono.just(new ResponseEntity<User>(service.getUser(), ...);
  }
}
// both service and dao (below) are non reactive
@Service
public class UserService {
   @Autowired
   UserDao dao; 
   User getUser() {
     // some logic (everything is in memory, nothing is io-bound)
     return dao.getUser();
   }
}
@Repository
public class UserDao {
   public User getUser() {
      // call the non-reactive relational db and get the user
      // it takes 99% of the time I believe
      // return this user;
   }
}

Я никогда не использовал реактивный подход, но я понимаю, что под капотом это приложение весенней загрузки должно запускать netty и использовать webflux. Я использовал базу данных в качестве примера операции, связанной с вводом-выводом, но иногда в коде также присутствуют вызовы удаленных служб (REST), и они не являются реактивными... Я считаю, что эти операции с БД/вызовы удаленного отдыха занимают около 99% времени выполнения, поэтому это делает приложение хорошим кандидатом для реактивной реализации.

Однако меня смущает текущая ситуация с кодом:

  • Контроллер реактивный
  • Служба/DAO/Удаленные вызовы не

Мой вопрос: допустимо ли такое использование смеси нереактивных и реактивных подходов или это неправильно?

Насколько я понимаю, если я хочу использовать реактивный подход веб-потока, весь мой код должен быть реактивным вплоть до вызова базы данных/удаленного отдыха (включая их, конечно), в противном случае я эффективно сохраню потоки цикла событий. netty занята, и я не могу этого сделать... Так что в двух словах мне это кажется неправильным, но я не уверен, что на самом деле происходит в этом коде. Я правильно понимаю или я что-то упускаю?

Вы правы, обычно вам нужно использовать полную реактивность до тех пор, пока вы не выполните ввод-вывод. Но базовые службы не обязательно должны быть реактивными. Что меня щекочет в вашем вопросе, так это следующее: sometimes in the code there are also calls to remote services (REST), and also they're not reactive. При этом совершенно не имеет значения, какая парадигма использует базовые сервисы. Что наиболее важно, так это то, что все нечисто действия ЦП являются «действительными» с точки зрения реактивности: либо с использованием неблокирующих драйверов, либо, как сказал @K.Nicholas, путем переноса в «реактивный» совместимый блок кода.

Vincent C. 27.06.2024 16:43

Еще один вопрос, ты не можешь полностью реагировать? Есть ли у вас какие-либо ограничения? Если да, то вы можете использовать реактивный драйвер и просто обернуть нереактивный код в парадигму реактивного стиля, запланировать его для Schedulers.boundedElastic() и вернуть результат метода Mono.subscribe(). Обратите внимание, что я бы рекомендовал полностью использовать реактивность, это должно быть транзитивное решение или для очень конкретных случаев использования, о которых я не знаю.

Vincent C. 27.06.2024 16:46

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

Mark Bramnik 27.06.2024 17:24

По сути, вы можете обернуть свои нереактивные вызовы Mono.fromCallable(() -> nonReactive()) следующей документацией реактора. Таким образом, вы по-прежнему можете использовать реактивную парадигму в каждой из ваших точек входа, сохраняя при этом нереактивный драйвер для вашей БД. Я также рекомендовал бы вам принять шаблон стратегии при выпуске реактивных драйверов (если вообще когда-либо) для вашей конкретной базы данных, чтобы вы могли легко переключаться с реактивного на нереактивный режим.

Vincent C. 27.06.2024 17:31

Отстой, эта ссылка не приведет вас к правильной документации.

K.Nicholas 27.06.2024 19:55
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
5
138
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

These DB operations/remote rest calls I believe take like 99% of the execution time, so this makes the application to be a good candidate for reactive implementation.

У вас все равно будет то же время выполнения. Реактивные фреймворки не предназначены для сокращения времени выполнения. https://projectreactor.io/docs/core/release/reference/#getting-started-introducing-reactor.

Reactor — это полностью неблокирующая основа реактивного программирования для JVM с эффективным управлением спросом (в форме управления «противодавлением»).

Это ваш вариант использования?

The controller is reactive

Действительно? Вы сделали его реактивным с помощью Mono::just, но используете нереактивные аннотации.

The service / DAO / Remote Calls are not

Почему бы их тоже не поменять? Если вам нужно их обернуть, проверьте документацию. С.1. Как завершить синхронный блокирующий вызов? .

In my understanding if I want to go with reactive web-flux approach, all my code must be reactive all the way down to the database/remote rest call (including them of course), otherwise I'll effectively keep the event-loop threads of netty busy and this is something I can't do... So it seems wrong to me in a nutshell, But I'm not sure what happens actually in this code. Is my understanding correct or I'm missing something?

Да. Всю дорогу вниз. Цель — создать flow и передать его клиенту. Когда клиент запрашивает результат, flow выполняется в рамках реактивной структуры.

Когда платформа встречает блокирующий вызов, например DB или REST, она выполняет вызов и переназначает поток для выполнения другого параллельного запроса. Когда блокирующий вызов возвращает результат, поток перенаправляется обратно на исходный запрос. С помощью Apache Tomcat, использующего системные потоки, вы можете обрабатывать от 1 до 2 тысяч одновременных запросов с очень точным расчетом времени. Из коробки 384. С WebFlux от 30 до 50 тысяч, если правильно делать. У вас так много пользователей, что они будут стабильно выполнять 30 тысяч одновременных запросов в секунду?

Он также выполнен в стиле functional парадигмы программирования. Редко кто-то пишет хороший код, используя эту парадигму. Я только что исправил блок кода, который занимал 20 строк, просто чтобы добавить на карту кучу листовых узлов. Заменил на пару forEach петель.

Развлекайтесь в этой кроличьей норе. Людям нравятся новые вещи, потому что они экзотичные и увлекательные. Подождите, пока вам не понадобится поддерживать чей-то код, который на самом деле не понимает структуру и ее цель. Functional просто уродлив и WebFlux очень запутан.

PS> Для иллюстрации: Mono::just сначала выполнит код, а затем построит поток с результатом. Он просто помещает результат в Mono, так что на самом деле вы не сделали ничего реактивного. Поэтому, если вы поместите вызов REST в Mono.just, он вызовет конечную точку перед возвратом потока клиенту. Основная реактивная ловушка для новичков.

Спасибо К.Николас. Назвав контроллер реактивным, я имел в виду, что использование абстракции Mono указало мне на то, что под капотом он работает netty и использует webflux.... Я не думаю, что могу просто использовать реактивный подход, потому что фактическая база данных, которую мы используем, не поддерживает реактивный способ работы (нет реактивного драйвера). Поэтому я хотел убедиться, что мое понимание правильное, а такой способ проектирования кода неправильный. Лично я думаю то же самое, что и вы, но у меня не было большого опыта работы с реактивным кодом...

Mark Bramnik 27.06.2024 17:32

Да, не беспокойся. Мне потребовалось некоторое время, чтобы разобраться, что происходит с реактивом. Тем временем Oracle завершила и опубликовала виртуальные потоки, поэтому, если вы действительно хотите обрабатывать тысячи одновременных запросов, для этого специально созданы API Async Servlet и виртуальные потоки. Если вы все равно его используете, лучше понимать его как двухэтапный процесс. Первый — построить поток для клиента, второй — клиент его выполняет. Ловушки заключаются в следующем: 1) выполнение кода во время сборки, как в Mono.just 2) слишком усердная попытка сделать все функциональным, когда следует использовать простую старую Java внутри map.

K.Nicholas 27.06.2024 19:45

@MarkBramnik Если у вас нет драйвера реактивной базы данных, не тратьте время на Webflux. Просто обновитесь до Java 21 и вместо этого используйте виртуальные потоки. Примечание: вам нужен не только ХОРОШИЙ драйвер базы данных, совместимый с Spring, вам также могут понадобиться другие компоненты для его поддержки, такие как сам Spring, Hibernate и т. д.

SledgeHammer 28.06.2024 07:46

@MarkBramnik Т.е. если бы вы решили использовать SQL, а затем захотели использовать Webflux, вы бы возненавидели жизнь. Существует драйвер Reactive SQL, но он чрезвычайно примитивен, Hibernate его тоже не поддерживает, поэтому, если вы хотите сделать что-то большее, чем плоские таблицы 1:1, вам придется ВСЕ это перевернуть вручную. Дочерние таблицы, связи и т. д. По сути, единственные БД, с которыми я бы рекомендовал использовать Webflux, — это Mongo или Elastic.

SledgeHammer 28.06.2024 07:51

@SledgeHammer - Честно говоря, я думаю точно так же :))) Я постараюсь изо всех сил выяснить, как получилось, что проект начал использовать реактивный подход. Я знаю о проекте и, конечно, предпочел бы его использовать, но, поскольку я являюсь частью большой организации, мне потребуется время, чтобы принять Java 21... Спасибо за ваше мнение

Mark Bramnik 28.06.2024 11:01

@MarkBramnik, «это займет время», к сожалению, не вариант для Webflux. Mono.Callable — это обходной путь для некоторых блокирующих вызовов ОС здесь и там. Он не предназначен для создания целого приложения. Если у вас нет встроенной реактивной поддержки сверху вниз для ВСЕХ ваших зависимостей, выполняющих ввод-вывод, вам не следует использовать Webflux. Одна из первых вещей, которую вам следует сделать при выборе Webflux, — это убедиться, что у вас есть реактивная поддержка всех операций ввода-вывода.

SledgeHammer 28.06.2024 15:48

@MarkBramnik В качестве примечания: различные команды Spring намекнули, что они не собираются вкладывать значительные усилия в Webflux. Реляционный драйвер, как я уже упоминал, не может делать ничего, кроме 1:1, и руководитель фактически сказал, что он не собирается поддерживать 1:M и M:M в ближайшее время. Hibernate Reactive также не поддерживает Spring, только Vert.x. При этом, если бы я запускал новый сервис Mongo/Elastic/HTTP, я бы все равно выбрал Webflux. В противном случае ВЦ.

SledgeHammer 28.06.2024 15:52

Да, спасибо — я вижу, что традиционные реляционные базы данных все еще не готовы к реагированию :) Придется разбираться, почему были приняты такие решения. Спасибо за разъяснения о будущем webflux,

Mark Bramnik 28.06.2024 15:57

Другие вопросы по теме