Подтвердить что ты не робот

Могу ли я сделать полностью неблокирующее бэкэнд-приложение с помощью http-kit и core.async?

Мне интересно, можно ли собрать полностью неблокируемое веб-приложение с 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, которая обращается к этому.

4b9b3361

Ответ 1

С помощью асинхронного подхода вы можете отправлять данные клиенту, когда он готов, а не блокирует поток, все время его готовят.

Для http-kit вы должны использовать обработчик async, описанный в документации. После делегирования запроса обработчику async надлежащим образом вы можете реализовать его, как вам нравится, используя core.async или что-то еще.

Документация Async Handler приведена здесь: http://http-kit.org/server.html#channel