Как ReactiveMongo реализован так, что считается неблокирующим?

Чтение документации о Play Framework и ReactiveMongo заставляет меня думать, что ReactiveMongo работает таким образом, что использует несколько потоков и никогда не блокирует.

Однако, похоже, что связь приложения Play с сервером Mongo должна произойти где-то в каком-то потоке. Как это реализовано? Ссылки на исходный код для Play, ReactiveMongo, Akka и т.д. Также были бы очень оценены.

Платформа Play содержит некоторую документацию об этом на этой странице о пулах потоков. Он начинается:

Структура воспроизведения - снизу вверх - асинхронная веб-фреймворк. Потоки обрабатываются асинхронно, используя итерации. Пулы потоков в Play настроены на использование меньше потоков, чем в традиционных веб-фреймворках, поскольку IO в play-core никогда не блокируется.

Затем он немного рассказывает об ReactiveMongo:

Наиболее распространенное место, которое типичное приложение Play будет блокировать, - это когда он разговаривает с базой данных. К сожалению, ни одна из основных баз данных не предоставляет асинхронные драйверы базы данных для JVM, поэтому для большинства баз данных единственным вариантом является использование блокировки ввода-вывода. A примечательным исключением является ReactiveMongo, драйвер для MongoDB, который использует библиотеку Plays Iteratee для общения с MongoDB.

Ниже приведена заметка об использовании Futures:

Обратите внимание, что у вас может возникнуть соблазн, поэтому вы можете заблокировать свой блокирующий код в Futures. Это не делает его не блокирующим, это просто означает, что блокировка произойдет в другом потоке. Вам все равно нужно убедиться, что пул потоков, который вы используете там, имеет достаточно потоков для обработки блокировки.

В документации Play есть аналогичная заметка на странице Обработка асинхронных результатов:

Вы не можете магически превратить синхронный IO в асинхронный, обернув его в Будущее. Если вы не можете изменить архитектуру приложений, чтобы избежать блокировки операций, в какой-то момент, что операция должна быть выполнена, и этот поток будет блокировать. Таким образом, помимо включения операции в будущее, необходимо настроить ее для запуска в отдельном контексте выполнения, который был настроен с достаточным количеством потоков для работы с ожидаемым concurrency.

В документации, по-видимому, говорится, что ReactiveMongo не блокирует, поэтому вам не нужно беспокоиться о том, что он поглощает много потоков в вашем пуле потоков. Но ReactiveMongo должен где-то общаться с сервером Mongo.

Как это сообщение реализовано так, что Mongo не использует потоки из пула потоков Play по умолчанию?

Еще раз ссылки на конкретные файлы в Play, ReactiveMongo, Akka и т.д., будет очень признателен.

Ответы

Ответ 1

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

ReactiveMongo "не использует потоки" в некотором смысле, что он не использует блокирующие операции ввода-вывода. Обычные объекты ввода-вывода Java, такие как java.io.InputStream, блокируются; это означает, что чтение из такого InputStream или запись в OutputStream блокирует поток, пока "другая сторона" не предоставит требуемые данные или не готова принять его. Для сетевой связи это означает, что потоки будут заблокированы.

Однако Java предоставляет NIO API, который поддерживает неблокирующий и асинхронный ввод-вывод. Я не хочу вдаваться в его детали прямо сейчас, но основная идея, естественно, заключается в том, что неблокирующий ввод-вывод позволяет не блокировать потоки, которые необходимо обменивать некоторыми данными с внешним миром: например, эти потоки могут опросить источник данных, чтобы проверить, имеются ли какие-либо данные, а если их нет, они возвращаются в пул потоков и могут использоваться для других задач. Конечно, там эти средства предоставляются базовой ОС.

Точные детали реализации неблокирующего ввода-вывода обычно скрываются внутри высокоуровневых библиотек, таких как Netty, потому что это не совсем удобно использовать. Netty (который в точности использует библиотека ReactiveMongo), например, обеспечивает хороший асинхронный API-интерфейс с обратным вызовом, который действительно прост в использовании, но также является мощным и выразительным, позволяющим создавать сложные приложения ввода-вывода с высокой пропускной способностью.

Итак, ReactiveMongo использует Netty для общения с сервером базы данных Mongo, а поскольку Netty представляет собой реализацию асинхронных сетевых операций ввода-вывода, ReactiveMongo действительно не нужно блокировать потоки в течение длительного времени.