web-dev-qa-db-ja.com

http-kitとcore.asyncを使用して完全にノンブロッキングのバックエンドアプリケーションを作成できますか?

完全にノンブロッキングのClojureバックエンドWebアプリケーションをhttp-kitでまとめることができるかどうか疑問に思っています。

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


編集:TL; DR

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

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

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

今、具体的には:

  • hTTPサーバーとしてのhttp-kitは、ノンブロッキングの非同期インターフェースを提供します。下記参照。
  • ただし、多くのリングミドルウェアは本質的に同期しているため、このアプローチと互換性がありません。基本的に、返された応答を更新する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)))
     )))

のTimBaldridgeが示唆しているように、CPUを集中的に使用するconstruct-my-responseタスクはgo-ブロックで実行されますが、外部リソースの待機はthread-ブロックで実行されます。 /] 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))
     )))

これにより、非同期モデルを実際に採用できます。

これの問題は、リングミドルウェアとほとんど互換性がないことです。リングミドルウェアは、応答を取得するために関数呼び出しを使用します。これにより、本質的に同期されます。より一般的に言えば、イベントのトリガーは副作用があることを意味するため、イベント駆動型の処理は純粋な関数型プログラミングインターフェイスと互換性がないようです。

これに対処するClojureライブラリがあるかどうかを知ってうれしいです。

35

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

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

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

6
lorthos