Pertanyaan Dapatkah saya membuat aplikasi backend yang sepenuhnya non-blocking dengan http-kit dan core.async?


Saya ingin tahu apakah mungkin untuk mengumpulkan aplikasi web backend Clojure sepenuhnya tanpa pemblokiran dengan http-kit.

(Sebenarnya server http yang kompatibel dengan Ring akan baik-baik saja oleh saya; Saya menyebutkan http-kit karena itu klaim untuk memiliki model event-driven, non-blocking).


EDIT: TL; DR

Pertanyaan ini adalah gejala dari beberapa kesalahpahaman yang saya miliki tentang sifat sistem yang tidak memblokir / asynchronous / event-driven. Jika Anda berada di tempat yang sama seperti saya, berikut adalah beberapa klarifikasi.

Membuat sistem yang digerakkan oleh peristiwa dengan manfaat kinerja karena tidak memblokir (seperti di Node.js) hanya mungkin jika semua (misalnya, sebagian besar) dari IO Anda ditangani secara non-blocking dari bawah ke atas. Ini berarti bahwa semua driver DB Anda, server HTTP dan klien, layanan Web dll harus menawarkan antarmuka asinkron di tempat pertama. Khususnya:

  • jika driver database Anda menawarkan antarmuka sinkron, tidak ada cara untuk membuatnya non-blocking. (Thread Anda diblokir, tidak ada cara untuk mengambilnya). Jika Anda ingin tidak memblokir, Anda perlu menggunakan yang lain.
  • Utilitas koordinasi tingkat tinggi seperti core.async tidak dapat membuat sistem non-blocking. Mereka dapat membantu Anda mengelola kode non-pemblokiran, tetapi jangan aktifkan.
  • Jika driver IO Anda sinkron, Anda bisa gunakan core.async untuk memiliki Desain manfaat asynchrony, tetapi Anda tidak akan mendapatkan manfaat kinerja itu. Benang Anda masih akan membuang waktu menunggu setiap tanggapan.

Sekarang, secara spesifik:

  • http-kit sebagai server HTTP menawarkan antarmuka non-blocking, asynchronous. Lihat di bawah.
  • Namun, banyak Ring middlewares, karena mereka pada dasarnya sinkron, tidak akan kompatibel dengan pendekatan ini. Pada dasarnya, setiap middleware Ring yang memperbarui respons yang dikembalikan tidak akan bisa digunakan.

Jika saya sudah benar (dan saya bukan ahli, jadi tolong beritahu saya jika saya bekerja pada asumsi yang salah), prinsip-prinsip seperti model non-blocking untuk aplikasi web adalah sebagai berikut:

  1. Memiliki beberapa utas OS super cepat menangani semua komputasi intensif CPU; ini tidak boleh menunggu.
  2. Memiliki banyak "benang lemah" menangani IO (panggilan basis data, panggilan layanan-web, tidur, dll.); ini sebagian besar dimaksudkan untuk menunggu.
  3. Ini bermanfaat karena waktu tunggu yang dihabiskan untuk menangani permintaan biasanya 2 (akses disk) ke 5 (panggilan layanan web) dengan jumlah yang lebih besar daripada waktu komputasi.

Dari apa yang saya lihat, model ini didukung oleh default pada Mainkan Kerangka Kerja (Scala) dan Node.js (JavaScript) platform, dengan utilitas berbasis janji untuk mengelola asynchrony secara terprogram.

Mari coba lakukan ini di aplikasi clojure berbasis Ring, dengan perutean Compojure. Saya memiliki rute yang membangun respon dengan memanggil my-handlefungsi:

(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))

Tampaknya cara yang umum diterima untuk mengelola asynchrony dalam aplikasi Clojure adalah berbasis CSP, dengan penggunaan core.async perpustakaan, dengan mana saya baik-baik saja. Jadi jika saya ingin merangkul prinsip-prinsip non-blocking yang tercantum di atas, saya akan menerapkan my-handle cara ini :

(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-intensif construct-my-response tugas dilakukan dalam go-block sedangkan menunggu sumber daya eksternal dilakukan di thread-block, seperti yang disarankan oleh Tim Baldridge di video ini di core.async (38'55 '')

Tetapi itu tidak cukup untuk membuat aplikasi saya non-blocking. Benang apa pun melewati rute saya dan akan memanggil my-handle fungsi, akan menunggu untuk respon yang akan dibangun, bukan?

Apakah menguntungkan (seperti yang saya percayai) untuk membuat HTTP ini menangani non-blocking juga, jika demikian bagaimana saya bisa mencapainya?


EDIT

Seperti yang ditunjukkan oleh codemomentum, bahan yang hilang untuk penanganan yang tidak memblokir adalah dengan menggunakan saluran http-kit. Dalam hubungannya dengan core.async, kode di atas akan menjadi seperti ini:

(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))
     )))

Ini memungkinkan Anda menggunakan model asynchronous.

Masalah dengan ini adalah bahwa itu cukup banyak tidak kompatibel dengan middleware Ring. Sebuah middleware Ring menggunakan panggilan fungsi untuk mendapatkan respons, yang membuatnya sinkron secara esensial. Secara umum, tampaknya penanganan berbasis kejadian tidak kompatibel dengan antarmuka pemrograman fungsional murni, karena memicu peristiwa berarti memiliki efek samping.

Saya akan senang mengetahui jika ada perpustakaan Clojure yang membahas ini.


32
2017-07-27 10:27


asal


Jawaban:


Dengan pendekatan asinkron Anda dapat mengirim data ke klien ketika sudah siap, alih-alih memblokir utas sepanjang waktu yang dipersiapkan.

Untuk http-kit, Anda harus menggunakan penangan async yang dijelaskan dalam dokumentasi. Setelah mendelegasikan permintaan ke penangan async dengan cara yang tepat, Anda dapat menerapkannya namun Anda suka menggunakan core.async atau yang lainnya.

Dokumentasi Async Handler ada di sini: http://http-kit.org/server.html#channel


6
2017-07-28 04:48