我想知道是否可能使用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应用程序的原则如下:
- 使用一些超快的操作系统线程处理所有CPU密集计算; 这些绝不能等待。
- 有很多“弱线程”来处理IO(数据库调用,Web服务调用,睡眠等); 这些主要是用于等待。
- 这是有益的,因为请求处理中花费在等待上的时间通常比计算时间高2个(磁盘访问)到5个(Web服务调用)数量级。
让我们尝试在基于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库可以解决这个问题,我会很高兴知道。