Мне интересно, можно ли собрать полностью неблокируемое веб-приложение с ww файлом w120 с http-kit.
(На самом деле любой Ring-совместимый http-сервер был бы в порядке, я упоминаю http-kit, потому что утверждает, чтобы иметь управляемый событиями, блокирующая модель).
EDIT: TL; DR
Этот вопрос является симптомом некоторых заблуждений, которые я имел о природе неблокирующих/асинхронных/управляемых событиями систем. Если вы находитесь на том же месте, что и я, вот некоторые пояснения.
Создание управляемой событиями системы с преимуществами производительности, которые она не блокирует (например, в Node.js), возможно, только если все (скажем, большинство) ваш IO обрабатывается в не- блокирующий путь с нуля. Это означает, что все ваши драйверы БД, серверы и клиенты HTTP, веб-сервисы и т.д. Должны предлагать асинхронный интерфейс в первую очередь. В частности:
- Если ваш драйвер базы данных предлагает синхронный интерфейс, нет способа сделать его неблокирующим. (Ваша нить заблокирована, ее невозможно восстановить). Если вы хотите не блокировать, вам нужно использовать что-то еще.
- Утилиты координации на высоком уровне, такие как core.async, не могут сделать систему неблокирующей. Они могут помочь вам управлять неблокирующим кодом, но не включать его.
- Если ваши драйверы ввода-вывода синхронны, вы можете использовать core.async, чтобы иметь преимущества асинхронности в дизайне, но вы не получите выгоды от его производительности. Ваши потоки все равно будут тратить время на ожидание каждого ответа.
Теперь, в частности:
- http-kit как HTTP-сервер предлагает неблокирующий асинхронный интерфейс. См. Ниже.
- Однако многие кольцевые средние, поскольку они по существу синхронны, не будут совместимы с этим подходом. В принципе, любое промежуточное ПО Ring, которое обновляет возвращаемый ответ, не будет использоваться.
Если я правильно понял (и я не эксперт, поэтому, пожалуйста, скажите мне, если я работаю над неправильными предположениями), принципы такой неблокирующей модели для веб-приложения следующие:
- Есть несколько сверхбыстрых потоков ОС, обрабатывающих все процессоры с интенсивным использованием процессора; они никогда не должны ждать.
- Имейте много "слабых потоков", обрабатывающих IO (вызовы базы данных, вызовы веб-сервиса, спальные и т.д.); они предназначены главным образом для ожидания.
- Это полезно, потому что время ожидания, затрачиваемое на обработку запроса, обычно равно 2 (доступ к диску) до 5 (вызовы веб-сервисов) на порядки выше, чем время вычисления.
Из того, что я видел, эта модель поддерживается по умолчанию в Play Framework (Scala) и Node.js (JavaScript), с программными утилитами с обещаниями для управления асинхронностью программно.
Попробуйте сделать это в приложении Clojure на основе Ring с маршрутизацией Compojure. У меня есть маршрут, который создает ответ, вызывая функцию my-handle
:
(defroutes my-routes
(GET "/my/url" req (my-handle req))
)
(def my-app (noir.util.middleware/app-handler [my-routes]))
(defn start-my-server! []
(http-kit/run-server my-app))
Кажется, что общепринятым способом управления асинхронностью в приложениях Clojure является CSP-based, с использованием библиотеки core.async, с которой я ' м совершенно нормально. Поэтому, если бы я хотел принять принципы неблокирования, перечисленные выше, я бы выполнил my-handle
следующим образом:
(require '[clojure.core.async :as a])
(defn my-handle [req]
(a/<!!
(a/go ; `go` makes channels calls asynchronous, so I'm not really waiting here
(let [my-db-resource (a/thread (fetch-my-db-resource)) ; `thread` will delegate the waiting to "weaker" threads
my-web-resource (a/thread (fetch-my-web-resource))]
(construct-my-response (a/<! my-db-resource)
(a/<! my-web-resource)))
)))
Задача с интенсивностью процессора construct-my-response
выполняется в go
-блоке, тогда как ожидания внешних ресурсов выполняются в thread
-блоках, как предложил Тим Балдридж в это видео на core.async (38'55 '')
Но этого недостаточно, чтобы мое приложение не блокировалось. Независимо от того, какая нить проходит через мой маршрут и вызовет функцию my-handle
, будет ждать для построения ответа, правильно?
Было бы полезно (как я полагаю) сделать эту обработку HTTP неблокирующей, если да, то как я могу ее достичь?
ИЗМЕНИТЬ
Как отметил codemomentum, недостающим компонентом для неблокирующей обработки запроса является использование каналов http-kit. В сочетании с core.async приведенный выше код будет выглядеть примерно так:
(defn my-handle! [req]
(http-kit/with-channel req channel
(a/go
(let [my-db-resource (a/thread (fetch-my-db-resource))
my-web-resource (a/thread (fetch-my-web-resource))
response (construct-my-response (a/<! my-db-resource)
(a/<! my-web-resource))]
(send! channel response)
(close channel))
)))
Это позволяет вам использовать асинхронную модель.
Проблема заключается в том, что она в значительной степени несовместима с промежуточным программным обеспечением Ring. Кольцевое промежуточное программное обеспечение использует вызов функции для получения ответа, что делает его по существу синхронным. В общем, кажется, что обработка, управляемая событиями, несовместима с чистым функциональным программным интерфейсом, потому что запуск событий означает наличие побочных эффектов.
Я был бы рад узнать, есть ли библиотека Clojure, которая обращается к этому.