Хобрук: Ваш путь к мастерству в программировании

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

Мне интересно, можно ли собрать полностью неблокирующее внутреннее веб-приложение Clojure с http-kit.

(На самом деле мне подойдет любой HTTP-сервер, совместимый с кольцом; я упоминаю http-kit, потому что он утверждает, что имеет управляемая событиями неблокирующая модель).


РЕДАКТИРОВАТЬ: TL; DR

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

Создание системы, управляемой событиями, с преимуществом в производительности, заключающимся в том, что она неблокирующая (как в Node.js), возможно только в том случае, если все (скажем, большая часть) вашего ввода-вывода обрабатываются неблокирующим способом. с нуля. Это означает, что все ваши драйверы БД, HTTP-серверы и клиенты, веб-службы и т. Д. Должны в первую очередь предлагать асинхронный интерфейс. В частности:

  • если ваш драйвер базы данных предлагает синхронный интерфейс, нет способа сделать его неблокирующим. (Ваша цепочка заблокирована, восстановить ее невозможно). Если вы хотите неблокировать, вам нужно использовать что-то еще.
  • Утилиты координации высокого уровня, такие как core.async, не могут сделать систему неблокирующей. Они могут помочь вам управлять неблокирующим кодом, но не включайте его.
  • Если ваши драйверы ввода-вывода синхронны, вы можете использовать core.async, чтобы получить дизайн преимущества асинхронности, но вы не получите от этого преимущества в производительности. Ваши потоки по-прежнему будут тратить время на ожидание каждого ответа.

А теперь конкретно:

  • http-kit в качестве HTTP-сервера предлагает неблокирующий асинхронный интерфейс. Увидеть ниже.
  • Однако многие промежуточные программы Ring, поскольку они по сути синхронны, несовместимы с этим подходом. По сути, любое промежуточное программное обеспечение Ring, обновляющее возвращаемый ответ, не будет использоваться.

Если я понял это правильно (а я не эксперт, поэтому, пожалуйста, скажите мне, если я работаю с неправильными предположениями), принципы такой неблокирующей модели для веб-приложения следующие:

  1. Пусть несколько сверхбыстрых потоков ОС обрабатывают все вычислительные ресурсы с интенсивным использованием ЦП; эти никогда не должны ждать.
  2. Иметь много "слабых потоков", обрабатывающих ввод-вывод (вызовы базы данных, вызовы веб-служб, спящий режим и т. Д.); эти в основном предназначены для ожидания.
  3. Это выгодно, потому что время ожидания, затрачиваемое на обработку запроса, обычно на 2 (доступ к диску) - 5 (вызовы веб-служб) порядков превышает время вычислений.

Из того, что я видел, эта модель по умолчанию поддерживается в Play Framework (Scala) и Node.js (JavaScript) с утилитами на основе обещаний для программного управления асинхронностью.

Давайте попробуем сделать это в приложении Clojure на основе кольца с маршрутизацией 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 с использованием библиотеки 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. По промежуточного слоя Ring использует вызов функции для получения ответа, что делает его по существу синхронным. В более общем плане кажется, что обработка, управляемая событиями, несовместима с чисто функциональным программным интерфейсом, потому что запуск событий означает наличие побочных эффектов.

Я был бы рад узнать, есть ли библиотека Clojure, которая решает эту проблему.


  • как вы решили это в конце ?? ... У меня такое же требование, я хочу создать полное асинхронное приложение в clojure .. но кольцо ортогонально асинхронным шаблонам, пьедестал кажется многообещающим, но документация плохая и vertx не является идиоматическим для разработчиков clojure, а также несовместимо с кольцом, я пробую это github.com/ninjudd/ring- async, но, похоже, это просто эксперимент ... Мне любопытно, какую технологию вы выберете в конце, спасибо! .. 06.04.2015
  • Я добился некоторого прогресса в этом вопросе (однако я не реализовал такое приложение). Первое, что нужно проверить, это то, что все (или большинство) драйверов БД, клиентов ввода-вывода и т. Д. Сами асинхронны. Затем вы можете использовать библиотеку, такую ​​как core.async или коллектор, для сантехники. Что касается маршрутизации / обработки HTTP, вы можете создать промежуточное ПО кольца, которое адаптируется к httpkit, добавив канал ответа в карту запросов, и адаптировать промежуточное ПО кольца к асинхронному. Будет сложнее, вы должны проверить, есть ли у вас такие требования к производительности. 06.04.2015
  • Обновление: теперь для этого есть библиотеки, например Yada github.com/juxt/yada 05.10.2017

Ответы:


1

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

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

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

28.07.2014
Новые материалы

Учебные заметки JavaScript Object Oriented Labs
Вот моя седьмая неделя обучения программированию. После ruby ​​и его фреймворка rails я начал изучать самый популярный язык интерфейса — javascript. В отличие от ruby, javascript — это более..

Разбор строк запроса в vue.js
Иногда вам нужно получить данные из строк запроса, в этой статье показано, как это сделать. В жизни каждого дизайнера/разработчика наступает момент, когда им необходимо беспрепятственно..

Предсказание моей следующей любимой книги 📚 Благодаря данным Goodreads и машинному обучению 👨‍💻
«Если вы не любите читать, значит, вы не нашли нужную книгу». - J.K. Роулинг Эта статья сильно отличается от тех, к которым вы, возможно, привыкли . Мне очень понравилось поработать над..

Основы принципов S.O.L.I.D, Javascript, Git и NoSQL
каковы принципы S.O.L.I.D? Принципы SOLID призваны помочь разработчикам создавать надежные, удобные в сопровождении приложения. мы видим пять ключевых принципов. Принципы SOLID были разработаны..

Как настроить Selenium в проекте Angular
Угловой | Селен Как настроить Selenium в проекте Angular Держите свое приложение Angular и тесты Selenium в одной рабочей области и запускайте их с помощью Mocha. В этой статье мы..

Аргументы прогрессивного улучшения почти всегда упускают суть
В наши дни в кругах веб-разработчиков много болтают о Progressive Enhancement — PE, но на самом деле почти все аргументы с обеих сторон упускают самую фундаментальную причину, по которой PE..

Введение в Джанго Фреймворк
Схема «работать умно, а не усердно» В этой и последующих статьях я познакомлю вас с тем, что такое фреймворк Django и как создать свое первое приложение с помощью простых и понятных шагов, а..