Основы реактивного программирования
на Java. Project Reactor
Вступление
Реактивное
программирование —
это парадигма асинхронного программирования, связанная с потоками данных
и распространением изменений. Это означает, что становится возможным легко выражать
статические или динамические потоки данных через используемый язык
программирования.
А теперь
подробнее, но в контексте Java.
Обычно код,
написанный на Java, является блокирующим. Другими словами, текущий поток исполнения
останавливается до тех пор, пока текущая команда не будет выполнена. Звучит
неплохо, если всё происходит за приемлемое время.
А что, если
мы обращаемся к стороннему сервису, который отказывается отвечать или делает
это чересчур долго? Правильно, вынести исполнение этого блока кода в отдельный
поток.
Но тогда мы
столкнёмся с другой проблемой – неэффективным использованием ресурсов, ведь
каждый поток имеет свой стек вызовов, который занимает память, а также
необходимо переключать контекст между тредами.
Таким образом мы подошли к концепции асинхронности, которую легче всего для понимания описать примером:
- однопоточность – я ставлю яйцо вариться и устанавливаю таймер, кладу хлеб в тостер и запускаю другой таймер, а когда время выйдет — я буду есть. В асинхронном режиме мне не нужно ждать завершения задачи, чтобы начать еще одну.
- многопоточность – я нанимаю двух
поваров, чтобы они сварили для меня яйцо и поджарили хлеб. Они могут делать это
одновременно, и один не должен ждать другого, чтобы начать.
Reactive streams / Project Reactor
Реактивные потоки в Java представляют собой спецификацию асинхронной обработки данных и стоят на 4 столбах-интерфейсах:
- Publisher
- Subscriber
- Subscription
- Processor
Реализацией же этой спецификации является Project Reactor, состоящий из нескольких компонентов:
- Core – реактивные основы для приложений и сред, а также реактивные расширения, основанные на API с типами Mono (1 элемент) и Flux (n элементов)
- Reactor Netty – неблокирующие TCP/UDP/HTTP клиенты и серверы на основе Netty
Принцип работы
В основе
подхода лежит идея разделения компонентов на 2 типа: источник событий (Publisher) и обработчик событий (Subscriber). Subscriber подписывается на
события, которые создаёт Publisher, а затем каким-то образом их обрабатывает.
По сути, это
паттерн Observer с надстроенными поверх возможностями и особенностями.
Общение
между Publisher и Subscriber происходит через объект Subscription.
Subcriber
может регулировать скорость поставки сообщений от Publisher (backpressure), а
также отменять подписку.
Publisher’ы
можно объединять в цепочки и комбинировать разными способами.
Пара слов
о Flux и Mono
В Reactor есть два типа
Publisher: Flux<T> и
Mono<T>
Получить
поток можно несколькими способами:
А результат его выполнения:
В качестве аналогии можно привести конвейер, по которому
перемещаются элементы. Операторы же по очереди проводят с ними манипуляции.
Следующие элементы в цепочке вызываются под капотом при
помощи метода onNext().
Что касается исключений, они являются терминальными. Это
значит, что поток сразу завершает свою работу.
Заключение
Использование реактивного подхода не делает код производительнее,
но позволяет управлять ресурсами более эффективно, а также улучшает
масштабируемость системы.
А Project Reactor делает управление асинхронными потоками очень лаконичным
благодаря функциональному стилю без кучи коллбэков в виде анонимных методов.