我能否使用http-kit和core.async创建完全非阻塞的后端应用程序?

37

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

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


编辑:简述

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

只有当所有(或大部分)你的IO从一开始就以非阻塞方式处理时,才能实现具有非阻塞性能优势的事件驱动系统(例如Node.js)。这意味着所有DB驱动程序、HTTP服务器和客户端、Web服务等都必须首先提供异步接口。特别是:

  • 如果你的数据库驱动程序提供同步接口,则无法使其非阻塞。(线程被阻塞,无法检索它)。如果你想要非阻塞,你需要使用其他东西。
  • 像core.async这样的高级协调工具不能使系统非阻塞。它们可以帮助你管理非阻塞代码,但不能启用它。
  • 如果你的IO驱动程序是同步的,你可以使用core.async来获得异步设计的好处,但你不会得到性能上的好处。你的线程仍然会浪费时间等待每个响应。
现在,具体来说:
  • http-kit作为HTTP服务器提供了非阻塞、异步的接口。请参见下文。
  • 然而,许多Ring中间件由于本质上是同步的,将无法与这种方法兼容。基本上,任何更新返回响应的Ring中间件都将无法使用。

如果我理解正确(我不是专家,所以请告诉我如果我的假设有误),一个非阻塞模型的Web应用程序的原则如下:
  1. 使用一些超快的操作系统线程处理所有CPU密集计算; 这些绝不能等待
  2. 有很多“弱线程”来处理IO(数据库调用,Web服务调用,睡眠等); 这些主要是用于等待
  3. 这是有益的,因为请求处理中花费在等待上的时间通常比计算时间高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块中执行,而等待外部资源则是在thread块中完成的,正如Tim Baldridge在这个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库可以解决这个问题,我会很高兴知道。

3
你最终是如何解决这个问题的?我有同样的要求,想在Clojure中构建一个完全异步的应用程序。但是Ring和异步模式正交,Pedestal看起来很有前途,但文档不太好,Vert.x也不符合Clojure开发人员的习惯,并且不兼容Ring。我尝试使用 https://github.com/ninjudd/ring-async ,但似乎只是一个实验。我很好奇你最终选择了哪种技术,谢谢! - user1050817
我在那个问题上取得了一些进展(但是,我还没有实现这样一个应用程序)。首先要检查的是所有(或大多数)的DB驱动程序、IO客户端等是否本身都是异步的。然后你可以使用像core.async或manifold这样的库来进行编程。至于HTTP路由/处理,你可以创建一个Ring中间件,通过向请求映射添加响应通道来适应httpkit,并将Ring中间件调整为异步。这会更难,你应该检查是否确实有这样的性能要求。 - Valentin Waeselynck
更新:现在��经有相关的库,例如Yada https://github.com/juxt/yada - Valentin Waeselynck
1个回答

6
采用异步方式,可以在数据准备好时向客户端发送数据,而不是在整个准备过程中阻塞一个线程。对于 http-kit,应该使用文档中描述的异步处理程序。在正确委派请求到异步处理程序之后,您可以使用core.async或其他方式自行实现。异步处理程序文档在此处:http://http-kit.org/server.html#channel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接