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

Posted

技术标签:

【中文标题】我可以使用 http-kit 和 core.async 制作一个完全非阻塞的后端应用程序吗?【英文标题】:Can I make a fully non-blocking backend application with http-kit and core.async? 【发布时间】:2014-09-18 17:47:59 【问题描述】:

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

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


编辑:TL;DR

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

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

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

现在,具体来说:

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

如果我做对了(而且我不是专家,所以如果我的假设是错误的,请告诉我),这种 Web 应用程序的非阻塞模型的原则如下:

    让几个超快的操作系统线程处理所有 CPU 密集型计算;这些绝不能等待。 有很多“弱线程”处理 IO(数据库调用、Web 服务调用、休眠等);这些主要用于等待。 这是有益的,因为处理请求所花费的等待时间通常比计算时间高 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)))
     )))

正如 Tim Baldridge 在this video on 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 但似乎只是一个实验......我很好奇你最后选择了什么技术,谢谢!.. 我在这个问题上取得了一些进展(但是,我还没有实现这样的应用程序)。首先要检查的是所有(或大部分)数据库驱动程序、IO 客户端等本身是异步的。然后,您可以使用 core.async 或流形之类的库进行管道处理。至于 HTTP 路由/处理,可以通过在请求映射中添加响应通道来创建适配 httpkit 的 Ring 中间件,并将 Ring 中间件适配为异步的。这会更难,你应该检查你是否有这样的性能要求。 更新:现在有相应的库,例如 Yada github.com/juxt/yada 【参考方案1】:

使用异步方法,您可以在数据准备就绪时将其发送到客户端,而不是在整个准备过程中阻塞线程。

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

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

【讨论】:

以上是关于我可以使用 http-kit 和 core.async 制作一个完全非阻塞的后端应用程序吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 http-kit 和 Clojure 处理“房间”

我可以使用啥图表来显示 Html 事件和操作

我可以使用训练和测试数据进行插补吗?

我可以同时使用 DataContract 和 Serializable 吗?

我可以使用 dig 同时查找 NS 和 A

我可以同时使用急切和延迟加载吗?