Нет блокировки ввода-вывода ВООБЩЕ
Прежде всего, если у вас нет какой-либо блокирующей операции, вам вообще не следует беспокоиться о том, сколько потоков нужно предоставить для управления параллелизмом. В этом случае у нас есть только один воркер, который обрабатывает все соединения асинхронно и неблокируя. И в этом случае мы можем легко масштабировать воркеров соединений, которые обрабатывают все соединения без конфликтов и когерентности (каждый воркер имеет свою собственную очередь полученных соединений, каждый воркер работает на своем процессоре), и мы можем лучше масштабировать приложение в этом случае ( ничего общего с дизайном).
Резюме: в этом случае вы управляете максимальным количеством веб-потоков так же, как и раньше, с помощью конфигурации приложения-контейнера (Tomcat, WebSphere и т. д.) или аналогичного в случае серверов без сервлетов, таких как Netty или гибридный Undertow. Преимущество - вы можете обрабатывать мууууууу больше запросов пользователей, но с тем же потреблением ресурсов.
Блокирующая база данных и неблокирующий веб-API (например, WebFlux через Netty).
В случае, если нам нужно как-то справиться с блокировкой ввода-вывода, для мгновенной связи с БД через блокировку JDBC, наиболее подходящий способ сохранить масштабируемость и эффективность вашего приложения, насколько это возможно, мы должны использовать выделенный пул потоков для ввода-вывода.
Требования к пулу потоков
Прежде всего, мы должны создать пул потоков с точно таким же количеством рабочих процессов, сколько доступно соединений в пуле соединений JDBC. Следовательно, у нас будет ровно такое же количество потоков, которые будут блокировать ожидание ответа, и мы используем наши ресурсы настолько эффективно, насколько это возможно, поэтому память для стека потоков не будет потребляться больше, чем это действительно необходимо (В других слово Поток на модель соединения).
Как настроить пул потоков в соответствии с размером пула соединений
Поскольку доступ к свойствам различается для конкретной базы данных и драйвера JDBC, мы всегда можем внедрить эту конфигурацию в конкретное свойство, что, в свою очередь, означает, что оно может быть настроено devops или системным администратором. Конфигурация Threadpool (в нашем примере это настройка планировщика Project Reactor 3) может выглядеть следующим образом:
@Configuration
public class ReactorJdbcSchedulerConfig {
@Value("my.awasome.scheduler-size")
int schedulerSize;
@Bean
public Scheduler jdbcScheduler() {
return Schedulers.fromExecutor(new ForkJoinPool(schedulerSize));
// similarly
// ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
// taskExecutor.setCorePoolSize(schedulerSize);
// taskExecutor.setMaxPoolSize(schedulerSize);
// taskExecutor.setQueueCapacity(schedulerSize);
// taskExecutor.initialize();
// return Schedulres.fromExecutor(taskExecutor);
}
}
...
@Autowire
Scheduler jdbcScheduler;
public Mono myJdbcInteractionIsolated(String id) {
return Mono.fromCallable(() -> jpaRepo.findById(id))
.subscribeOn(jdbcScheduler)
.publishOn(Schedulers.single());
}
...
Как можно заметить, с помощью этого метода мы можем делегировать нашу общую конфигурацию пула потоков внешней команде (например, системным администраторам) и позволять им управлять потреблением памяти, которая используется для созданных потоков Java.
Держите блокирующий пул потоков ввода-вывода только для работы ввода-вывода.
Этот оператор означает, что поток ввода-вывода должен быть только для операций, которые ожидают блокировки. В свою очередь, это означает, что после того, как поток дождался ответа, вы должны перенести обработку результата в другой поток.
Вот почему в приведенном выше фрагменте кода я поставил .publishOn
сразу после .subscribeOn
.
Подводя итог, с помощью этого метода мы можем позволить внешней команде управлять размером приложения, контролируя размер пула потоков и размер пула соединений соответственно. Вся обработка результатов будет выполняться в рамках одного потока и, следовательно, не будет избыточного, неконтролируемого потребления памяти.
Наконец, API блокировки (Spring MVC) и блокировка ввода-вывода (доступ к базе данных)
В этом случае реактивная парадигма вообще не нужна, так как вы не получаете от этого никакой прибыли. Прежде всего, реактивное программирование требует особого изменения мышления, особенно в понимании использования функциональных методов с реактивными библиотеками, такими как RxJava или Project Reactor. В свою очередь, для неподготовленных пользователей это усложняет задачу и вызывает больше вопросов "Что ****** здесь происходит???". Итак, в случае блокировки операций с обоих концов, вам следует дважды подумать, действительно ли вам здесь нужно реактивное программирование.
Кроме того, бесплатного волшебства не бывает. Реактивные расширения имеют большую внутреннюю сложность, и, используя все эти волшебные .map
, .flatMap
и т. д., вы можете потерять общую производительность и потребление памяти вместо того, чтобы выиграть, как в случае сквозного неблокирующего асинхронного взаимодействия.
Это означает, что старое доброе императивное программирование здесь будет более подходящим, и будет намного проще контролировать размер вашего приложения в памяти, используя старое доброе управление конфигурацией Tomcat.
03.01.2018