37

http-kit を使用して、完全に非ブロッキングの Clojure バックエンド Web アプリケーションを作成できるかどうか疑問に思っています。

(実際には、Ring 互換の http サーバーであればどれでも問題ありません。http-kit について言及しているのは、イベント駆動型のノンブロッキング モデルがあると主張しているためです)。


編集:TL;DR

この質問は、非ブロッキング/非同期/イベント駆動型システムの性質について私が持っていたいくつかの誤解の兆候です。あなたが私と同じ場所にいる場合に備えて、いくつかの説明があります。

(Node.js のように) ノンブロッキングであるというパフォーマンス上の利点を備えたイベント駆動型システムを作成するには、IO のすべて (たとえば、ほとんど) がゼロからノンブロッキングの方法で処理される場合にのみ可能です。つまり、すべての DB ドライバー、HTTP サーバーとクライアント、Web サービスなどは、最初に非同期インターフェイスを提供する必要があります。特に:

  • データベース ドライバーが同期インターフェイスを提供している場合、それをノンブロッキングにする方法はありません。(あなたのスレッドはブロックされています。取得する方法はありません)。非ブロッキングが必要な場合は、別のものを使用する必要があります。
  • core.async のような高レベルの調整ユーティリティでは、システムをノンブロッキングにすることはできません。ノンブロッキング コードの管理には役立ちますが、有効にしないでください。
  • IO ドライバーが同期の場合、core.async を使用して非同期の設計の利点を得ることができますが、パフォーマンス上の利点は得られません。スレッドは、各応答を待機する時間を無駄にします。

さて、具体的には:

  • HTTP サーバーとしての http-kit は、非ブロッキングの非同期インターフェースを提供します。下記参照。
  • ただし、多くの Ring ミドルウェアは本質的に同期であるため、このアプローチと互換性がありません。基本的に、返された応答を更新する Ring ミドルウェアは使用できません。

私が正しく理解していれば (私は専門家ではないので、間違った仮定で作業している場合は教えてください)、このような Web アプリケーションのノンブロッキング モデルの原則は次のとおりです。

  1. いくつかの超高速 OS スレッドで、CPU を集中的に使用するすべてのコンピューティングを処理します。これらは決して待ってはいけません。
  2. 多くの「弱いスレッド」で IO (データベース呼び出し、Web サービス呼び出し、スリープなど) を処理します。これらは主に待機するためのものです。
  3. 要求の処理に費やされる待機時間は通常、計算時間よりも 2 (ディスク アクセス) から 5 (Web サービス呼び出し) 桁大きいため、これは有益です。

私が見た限りでは、このモデルはPlay Framework (Scala) およびNode.js (JavaScript) プラットフォームでデフォルトでサポートされており、非同期をプログラムで管理するための promise ベースのユーティリティが用意されています。

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 アプリケーションで非同期を管理する一般的に受け入れられている方法は、core.asyncライブラリを使用する CSP ベースであると思われますが、私はまったく問題ありません。したがって、上記のノンブロッキングの原則を採用したい場合は、次のように実装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)))
     )))

Core.async のこのビデオでTim Baldridge が示唆しているように、CPU を集中的に使用construct-my-responseするタスクは -block で実行されますがgo、外部リソースの待機は -block で行われます(38'55'')thread

しかし、それだけではアプリケーションをノンブロッキングにするのに十分ではありません。どのスレッドが私のルートを通過して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 ライブラリがあるかどうかを知りたいです。

4

1 に答える 1

6

非同期アプローチを使用すると、準備中ずっとスレッドをブロックするのではなく、準備ができたらクライアントにデータを送信できます。

http-kit の場合、ドキュメントに記載されている非同期ハンドラーを使用する必要があります。要求を適切な方法で非同期ハンドラーに委譲した後、core.async などを使用して好きなように実装できます。

非同期ハンドラーのドキュメントはこちら: http://http-kit.org/server.html#channel

于 2014-07-28T04:48:29.000 に答える