clojure - 我可以使用 http-kit 和 core.async 制作一个完全非阻塞的后端应用程序吗?

标签 clojure nonblocking ring core.async http-kit

我想知道是否有可能将一个完全非阻塞的 Clojure 后端 Web 应用程序与 http-kit 放在一起。

(实际上,任何与 Ring 兼容的 http 服务器对我来说都可以;我提到 http-kit 是因为它 claims 具有事件驱动的非阻塞模型)。

编辑:TL;博士

这个问题是我对非阻塞/异步/事件驱动系统的性质的一些误解的症状。如果你和我在同一个地方,这里有一些澄清。

只有当 的所有(比如,大部分)具有非阻塞性(如在 Node.js 中)的性能优势时,才有可能制作一个事件驱动系统。您的 IO 从头开始​​以非阻塞方式处理 .这意味着您所有的数据库驱动程序、HTTP 服务器和客户端、Web 服务等首先必须提供异步接口(interface)。 特别是:

  • 如果您的数据库驱动程序提供同步接口(interface),则无法使其非阻塞。 (您的线程被阻塞,无法检索它)。如果你想要非阻塞,你需要使用其他东西。
  • 像 core.async 这样的高级协调实用程序不能使系统无阻塞。它们可以帮助您管理非阻塞代码,但不要启用它。
  • 如果您的 IO 驱动程序是同步的,您可以使用 core.async 来获得异步的设计优势,但您不会获得它的性能优势。您的线程仍然会浪费时间等待每个响应。

  • 现在,具体来说:
  • http-kit 作为 HTTP 服务器提供了一个非阻塞的异步接口(interface)。见下文。
  • 然而,许多 Ring 中间件,因为它们本质上是同步的,将与这种方法不兼容。基本上,任何更新返回响应的 Ring 中间件都将无法使用。


  • 如果我做对了(而且我不是专家,所以请告诉我我是否在做错误的假设),这种用于 Web 应用程序的非阻塞模型的原则如下:
  • 让几个超快的操作系统线程处理所有 CPU 密集型计算;这些绝不能等待。
  • 有很多“弱线程”处理 IO(数据库调用、Web 服务调用、 sleep 等);这些主要是为了等待。
  • 这是有益的,因为处理请求所花费的等待时间通常比计算时间高 2(磁盘访问)到 5(Web 服务调用)数量级。

  • 据我所见,Play Framework 默认支持此模型。 (Scala) 和 Node.js (JavaScript) 平台,具有基于 Promise 的实用程序,用于以编程方式管理异步。

    让我们尝试在基于 Ring 的 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)))
         )))
    

    CPU 密集型 construct-my-response任务在 go 中执行-block 而等待外部资源是在 thread 中完成的-blocks,正如 Tim Baldridge 在 this video on core.async 中所建议的那样(38'55'')

    但这还不足以使我的应用程序无阻塞。无论什么线程通过我的路线,都会调用 my-handle函数,将是 等待 为了构建响应,对吗?

    使这个 HTTP 处理非阻塞是否有益(我相信),如果是这样,我该如何实现?

    编辑

    正如 codemomentum 指出的那样,非阻塞处理请求的缺失要素是使用 http-kit channel 。结合 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 中间件使用函数调用来获取响应,这使得它本质上是同步的。更一般地说,事件驱动处理似乎与纯函数式编程接口(interface)不兼容,因为触发事件意味着有副作用。

    我很高兴知道是否有一个 Clojure 库可以解决这个问题。

    最佳答案

    使用异步方法,您可以在数据准备好时将数据发送到客户端,而不是在线程准备好时一直阻塞。

    对于 http-kit,您应该使用文档中描述的异步处理程序。在以适当的方式将请求委托(delegate)给异步处理程序后,您可以根据自己的喜好使用 core.async 或其他方式实现它。

    异步处理程序文档在这里:http://http-kit.org/server.html#channel

    关于clojure - 我可以使用 http-kit 和 core.async 制作一个完全非阻塞的后端应用程序吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24980014/

    相关文章:

    windows - 在 Win32 中,有没有办法测试套接字是否是非阻塞的?

    python - 从多个子进程进行非阻塞读取(Python)

    php - Unix 套接字设置 O_NONBLOCK 以与 php-fpm get segmentfault 通信

    clojure - Ring 和 Compojure - 内容类型为 application/json 的 POST 请求不起作用

    clojure - 使用来自 ring.utils.io 的管道输入流来提供文件

    clojure - 如何在 Clojure 中链接函数调用?

    java - 树搜索保存执行状态

    clojure - 客观思考与功能思考

    clojure - lein 环自动刷新会覆盖请求正文吗?

    clojure - 使用clj-http客户端自动登录