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

时间:2014-07-27 10:27:46

标签: clojure nonblocking ring core.async http-kit

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

(实际上任何与Ring兼容的http服务器都没问题;我提到了http-kit,因为它claims有一个事件驱动的非阻塞模型。


编辑:TL; DR

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

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

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

现在,具体来说:

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

如果我做对了(我不是专家,所以请告诉我,如果我的工作是错误的假设),这种非阻塞模型的原则如下:

  1. 有一些超快的操作系统线程处理所有CPU密集型计算;这些永远不能等待
  2. 有很多“弱线程”处理IO(数据库调用,Web服务调用,休眠等);这些主要是等待
  3. 这是有益的,因为处理请求所花费的等待时间通常是2(磁盘访问)到5(Web服务调用)的数量级高于计算时间。
  4. 从我所看到的,默认情况下,Play Framework(Scala)和Node.js(JavaScript)平台支持此模型,并使用基于承诺的实用程序以编程方式管理异步。

    让我们尝试使用Compojure路由在基于Ring的clojure应用程序中执行此操作。我有一个通过调用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在{{3 (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中间件几乎不兼容。环中间件使用函数调用来获取响应,这使得它基本上是同步的。更一般地说,似乎事件驱动的处理与纯函数编程接口不兼容,因为触发事件意味着有副作用。

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

1 个答案:

答案 0 :(得分:6)

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

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

异步处理程序文档位于:http://http-kit.org/server.html#channel