Centrifugo v2 - masa depan server dan perpustakaan perpesanan waktu nyata untuk Go

Beberapa pembaca mungkin pernah mendengar tentang Centrifugo sebelumnya. Artikel ini akan fokus pada pengembangan versi kedua server dan pustaka waktu-nyata baru untuk bahasa Go yang mendasari itu.


Nama saya Alexander Emelin. Musim panas lalu, saya bergabung dengan tim Avito, di mana saya sekarang membantu mengembangkan backend messenger Avito. Pekerjaan baru, yang terkait langsung dengan pengiriman cepat pesan kepada pengguna, dan kolega baru menginspirasi saya untuk terus bekerja pada proyek Centrifugo open-source.



Singkatnya - ini adalah server yang bertugas menjaga koneksi konstan dari pengguna aplikasi Anda. Websocket atau SockJS polyfill digunakan sebagai transportasi, dapat, jika tidak mungkin untuk membuat koneksi Websocket, bekerja melalui Eventsource, streaming XHR, polling panjang dan transportasi berbasis HTTP lainnya. Klien berlangganan saluran di mana backend melalui Centrifuge API menerbitkan pesan baru saat mereka muncul - setelah itu pesan dikirimkan kepada pengguna yang berlangganan saluran tersebut. Dengan kata lain, ini adalah server PUB / SUB.



Saat ini, server digunakan dalam sejumlah besar proyek. Di antara mereka, misalnya, adalah beberapa proyek Mail.Ru (intranet, platform pelatihan Technopark / Technosphere, Pusat Sertifikasi, dll.), Dengan Centrifugo, sebuah karya dashboard yang indah di resepsi di kantor Moskow Badoo, dan 350 ribu pengguna secara bersamaan terhubung ke layanan spot.im ke centrifuge.


Beberapa tautan ke artikel sebelumnya di server dan aplikasinya bagi mereka yang pertama kali mendengar tentang proyek:



Saya mulai mengerjakan versi kedua pada Desember tahun lalu dan berlanjut hingga hari ini. Mari kita lihat apa yang terjadi. Saya menulis artikel ini tidak hanya untuk mempopulerkan proyek, tetapi juga untuk mendapatkan umpan balik yang lebih konstruktif sebelum rilis Centrifugo v2 - sekarang ada ruang untuk bermanuver dan perubahan mundur yang tidak kompatibel.


Pustaka waktu nyata untuk Go


Di komunitas Go, pertanyaan muncul dari waktu ke waktu - apakah ada alternatif selain socket.io on Go? Terkadang saya perhatikan bagaimana pengembang dalam menanggapi hal ini disarankan untuk melihat ke arah Centrifugo. Namun, Centrifugo adalah server yang di-host-sendiri, bukan perpustakaan - perbandingannya tidak adil. Saya juga telah ditanya beberapa kali apakah kode Centrifugo dapat digunakan kembali untuk menulis aplikasi real-time di Go. Dan jawabannya adalah: secara teori memungkinkan, tetapi saya tidak dapat menjamin kompatibilitas mundur API paket internal dengan risiko saya sendiri. Sudah jelas bahwa tidak ada alasan bagi siapa pun untuk mengambil risiko, dan forking juga merupakan opsi begitu-begitu. Plus, saya tidak akan mengatakan bahwa API untuk paket internal umumnya disiapkan untuk penggunaan seperti itu.


Oleh karena itu, salah satu tugas ambisius yang ingin saya selesaikan dalam proses mengerjakan versi kedua server adalah mencoba memisahkan inti server menjadi pustaka terpisah di Go. Saya percaya ini masuk akal, mengingat berapa banyak fitur yang dimiliki Centrifuge untuk disesuaikan dengan produksi. Ada banyak fitur yang tersedia di luar kotak untuk membantu membangun aplikasi real-time yang dapat diskalakan, menghilangkan kebutuhan pengembang untuk menulis solusi mereka sendiri. Saya menulis tentang fitur-fitur ini sebelumnya dan saya juga akan menguraikan beberapa dari mereka di bawah ini.


Saya akan mencoba untuk membenarkan satu lagi plus keberadaan perpustakaan seperti itu. Sebagian besar pengguna Centrifugo adalah pengembang yang menulis backend dalam bahasa / kerangka kerja dengan dukungan konkurensi yang buruk (mis. Django / Flask / Laravel / ...): bekerja dengan banyak koneksi yang gigih jika mungkin, dengan cara yang tidak jelas atau tidak efisien. Karenanya, tidak semua pengguna dapat membantu pengembangan server yang ditulis dalam Go (klise karena kurangnya pengetahuan bahasa). Oleh karena itu, bahkan komunitas pengembang Go yang sangat kecil di sekitar perpustakaan akan dapat membantu mengembangkan server Centrifugo yang menggunakannya.


Hasilnya adalah perpustakaan Centrifuge . Ini masih WIP, tetapi benar-benar semua fitur yang disebutkan dalam deskripsi tentang Github diimplementasikan dan berfungsi. Karena perpustakaan menyediakan API yang cukup kaya, sebelum menjamin kompatibilitas ke belakang, saya ingin mendengar tentang beberapa contoh sukses penggunaan dalam proyek nyata di Go. Belum ada. Serta tidak berhasil :). Tidak ada


Saya mengerti bahwa dengan memberi nama perpustakaan dengan cara yang sama seperti server, saya akan selalu menghadapi kebingungan. Tapi saya pikir ini adalah pilihan yang tepat, karena klien (seperti centrifuge-js, centrifuge-go) bekerja dengan perpustakaan Centrifuge dan server Centrifugo. Plus, nama tersebut sudah tertanam kuat di benak pengguna, dan saya tidak ingin kehilangan asosiasi ini. Namun, untuk sedikit lebih jelas, saya akan mengklarifikasi lagi:


  • Centrifuge - perpustakaan untuk bahasa Go,
  • Centrifugo adalah solusi turnkey, layanan terpisah, yang dalam versi 2 akan dibangun di perpustakaan Centrifuge.

Karena desainnya, Centrifugo (layanan yang berdiri sendiri yang tidak tahu apa-apa tentang backend Anda) mengasumsikan bahwa aliran pesan melalui transportasi real-time akan pergi dari server ke klien. Apa maksudmu Jika, misalnya, pengguna menulis pesan ke obrolan, maka pesan ini terlebih dahulu harus dikirim ke aplikasi backend (misalnya, AJAX di browser), divalidasi di sisi backend, disimpan ke database jika perlu, dan kemudian dikirim ke API Centrifuge. Perpustakaan menghapus batasan ini, memungkinkan Anda untuk mengatur pertukaran dua arah pesan asinkron antara server dan klien, serta panggilan RPC.



Mari kita lihat contoh sederhana: kita mengimplementasikan server kecil di Go menggunakan perpustakaan Centrifuge. Server akan menerima pesan dari klien browser melalui Websocket, klien akan memiliki bidang teks di mana Anda dapat mengarahkan pesan, tekan Enter - dan pesan akan dikirim ke semua pengguna yang berlangganan saluran. Yaitu, versi obrolan yang paling disederhanakan. Bagiku terasa lebih mudah untuk menempatkan ini dalam bentuk inti .


Anda dapat berjalan seperti biasa:


git clone https://gist.github.com/2f1a38ae2dcb21e2c5937328253c29bf.git cd 2f1a38ae2dcb21e2c5937328253c29bf go get -u github.com/centrifugal/centrifuge go run main.go 

Lalu buka http: // localhost: 8000 , buka beberapa tab browser.


Seperti yang Anda lihat, titik masuk ke logika bisnis aplikasi terjadi ketika menggantung fungsi On().Connect() :


 node.On().Connect(func(ctx context.Context, client *centrifuge.Client, e centrifuge.ConnectEvent) centrifuge.ConnectReply { client.On().Disconnect(func(e centrifuge.DisconnectEvent) centrifuge.DisconnectReply { log.Printf("client disconnected") return centrifuge.DisconnectReply{} }) log.Printf("client connected via %s", client.Transport().Name()) return centrifuge.ConnectReply{} }) 

Pendekatan berbasis panggilan balik bagi saya terasa paling nyaman untuk berinteraksi dengan perpustakaan. Plus, pendekatan yang serupa, hanya diketik dengan lemah, digunakan dalam implementasi server socket-io di Go . Jika tiba-tiba Anda memiliki pemikiran tentang bagaimana API dapat dilakukan dengan lebih idiomatis - Saya akan senang mendengarnya.


Ini adalah contoh yang sangat sederhana yang tidak menunjukkan semua fitur perpustakaan. Seseorang mungkin mencatat bahwa untuk keperluan seperti itu lebih mudah untuk mengambil perpustakaan untuk bekerja dengan Websocket. Misalnya, Gorilla Websocket. Ini sebenarnya begitu. Namun, bahkan dalam kasus ini, Anda harus menyalin sepotong kode server yang layak dari contoh di repositori Gorilla Websocket. Bagaimana jika:


  • Anda perlu mengatur skala aplikasi ke beberapa mesin,
  • atau Anda tidak perlu satu saluran umum, tetapi beberapa - dan pengguna dapat berlangganan dan berhenti berlangganan secara dinamis saat Anda menavigasi aplikasi Anda,
  • atau Anda harus bekerja ketika koneksi Websocket tidak dapat dibangun (tidak ada dukungan di browser klien, ada ekstensi browser, semacam proxy di jalan antara klien dan server memotong koneksi),
  • atau Anda perlu mengembalikan pesan yang terlewatkan oleh klien selama jeda singkat dalam koneksi Internet tanpa memuat basis data utama,
  • atau Anda perlu mengontrol otorisasi pengguna di saluran,
  • atau Anda perlu memutuskan koneksi permanen dari pengguna yang dinonaktifkan dalam aplikasi,
  • atau Anda memerlukan informasi tentang siapa yang saat ini berada di saluran atau acara yang telah dilanggani / langganan seseorang dari saluran tersebut,
  • atau apakah Anda memerlukan metrik dan pemantauan?

Pustaka Centrifuge dapat membantu Anda dengan ini - pada kenyataannya, itu mewarisi semua fitur dasar yang sebelumnya tersedia di Centrifugo. Contoh lainnya yang menunjukkan poin yang disebutkan di atas dapat ditemukan di Github .


Warisan kuat Centrifugo bisa menjadi minus, karena perpustakaan telah mengadopsi semua mekanisme server, yang cukup orisinal dan, mungkin, mungkin tampak tidak jelas atau kelebihan beban dengan fitur yang tidak perlu bagi seseorang. Saya mencoba mengatur kode sedemikian rupa sehingga fitur yang tidak digunakan tidak mempengaruhi kinerja keseluruhan.


Ada beberapa optimasi di perpustakaan yang memungkinkan penggunaan sumber daya yang lebih efisien. Ini menggabungkan beberapa pesan ke dalam satu bingkai Websocket untuk menghemat panggilan sistem Tulis atau, misalnya, menggunakan Gogoprotobuf untuk membuat serialisasi pesan Protobuf dan lainnya. Berbicara tentang Protobuf.


Protokol Binary Protobuf


Saya benar-benar ingin Centrifugo bekerja dengan data biner ( dan bukan hanya saya ), jadi di versi baru saya ingin menambahkan protokol biner selain yang sudah ada berdasarkan JSON. Sekarang seluruh protokol digambarkan sebagai skema Protobuf . Ini memungkinkan kami untuk membuatnya lebih terstruktur, untuk memikirkan kembali beberapa keputusan yang tidak jelas dalam protokol versi pertama.


Saya pikir Anda tidak perlu mengatakan untuk waktu yang lama apa keuntungan Protobuf atas JSON - kekompakan, kecepatan serialisasi, skema yang ketat. Ada kelemahan dalam bentuk keterbacaan, tetapi sekarang pengguna memiliki kesempatan untuk memutuskan apa yang lebih penting bagi mereka dalam situasi tertentu.


Secara umum, lalu lintas yang dihasilkan oleh protokol Centrifugo saat menggunakan Protobuf dan bukan JSON akan berkurang ~ 2 kali (tidak termasuk data aplikasi). Konsumsi CPU dalam tes beban sintetis saya menurun sama ~ 2 kali dibandingkan dengan JSON. Angka-angka ini sebenarnya berbicara sedikit tentang apa, dalam praktiknya, semuanya akan tergantung pada profil beban aplikasi tertentu.


Demi kepentingan, saya meluncurkan pada mesin dengan Debian 9.4 dan 32 Intelยฎ Xeonยฎ Platinum 8168 CPU @ 2.70GHz benchmark vCPU, yang memungkinkan kami untuk membandingkan bandwidth interaksi klien-server jika menggunakan protokol JSON dan protokol Protobuf. Ada 1.000 pelanggan untuk 1 saluran. Di saluran ini, pesan diterbitkan dalam 4 aliran dan dikirim ke semua pelanggan. Ukuran setiap pesan adalah 128 byte.


Hasil untuk JSON:


 $ go run main.go -s ws://localhost:8000/connection/websocket -n 1000 -ns 1000 -np 4 channel Starting benchmark [msgs=1000, msgsize=128, pubs=4, subs=1000] Centrifuge Pub/Sub stats: 265,900 msgs/sec ~ 32.46 MB/sec Pub stats: 278 msgs/sec ~ 34.85 KB/sec [1] 73 msgs/sec ~ 9.22 KB/sec (250 msgs) [2] 71 msgs/sec ~ 9.00 KB/sec (250 msgs) [3] 71 msgs/sec ~ 8.90 KB/sec (250 msgs) [4] 69 msgs/sec ~ 8.71 KB/sec (250 msgs) min 69 | avg 71 | max 73 | stddev 1 msgs Sub stats: 265,635 msgs/sec ~ 32.43 MB/sec [1] 273 msgs/sec ~ 34.16 KB/sec (1000 msgs) ... [1000] 277 msgs/sec ~ 34.67 KB/sec (1000 msgs) min 265 | avg 275 | max 278 | stddev 2 msgs 

Hasil untuk kasus Protobuf:


 $ go run main.go -s ws://localhost:8000/connection/websocket?format=protobuf -n 100000 -ns 1000 -np 4 channel Starting benchmark [msgs=100000, msgsize=128, pubs=4, subs=1000] Centrifuge Pub/Sub stats: 681,212 msgs/sec ~ 83.16 MB/sec Pub stats: 685 msgs/sec ~ 85.69 KB/sec [1] 172 msgs/sec ~ 21.57 KB/sec (25000 msgs) [2] 171 msgs/sec ~ 21.47 KB/sec (25000 msgs) [3] 171 msgs/sec ~ 21.42 KB/sec (25000 msgs) [4] 171 msgs/sec ~ 21.42 KB/sec (25000 msgs) min 171 | avg 171 | max 172 | stddev 0 msgs Sub stats: 680,531 msgs/sec ~ 83.07 MB/sec [1] 681 msgs/sec ~ 85.14 KB/sec (100000 msgs) ... [1000] 681 msgs/sec ~ 85.13 KB/sec (100000 msgs) min 680 | avg 680 | max 685 | stddev 1 msgs 

Anda mungkin memperhatikan bahwa throughput instalasi semacam itu lebih dari 2 kali lebih besar dalam hal Protobuf. Skrip klien dapat ditemukan di sini - ini adalah skrip benchmark Nats yang disesuaikan dengan realitas Centrifuge .


Perlu juga dicatat bahwa kinerja serialisasi JSON di server dapat "dipompa" menggunakan pendekatan yang sama seperti di gogoprotobuf - kumpulan buffer dan pembuatan kode - saat ini JSON diserialisasi dengan paket dari pustaka standar Go yang dibangun berdasarkan refleksi. Sebagai contoh, di Centrifugo, versi pertama JSON diserialisasi secara manual menggunakan perpustakaan yang menyediakan kumpulan buffer . Hal serupa dapat dilakukan di masa depan sebagai bagian dari versi kedua.


Perlu ditekankan bahwa protobuf juga dapat digunakan saat berkomunikasi dengan server dari browser. Klien javascript menggunakan pustaka protobuf.js untuk ini. Karena pustaka protobuf cukup berat, dan jumlah pengguna dalam format biner akan kecil, menggunakan webpack dan algoritme pengguncangnya, kami menghasilkan dua versi klien - satu dengan dukungan protokol JSON saja, dan yang lainnya dengan dukungan JSON dan protobuf. Untuk lingkungan lain di mana ukuran sumber daya tidak memainkan peran yang sangat penting, klien tidak dapat khawatir tentang pemisahan ini.


Token Web JSON (JWT)


Salah satu masalah dengan menggunakan server mandiri seperti Centrifugo adalah bahwa ia tidak tahu apa-apa tentang pengguna Anda dan metode otentikasi mereka, dan mekanisme sesi seperti apa yang digunakan backend Anda. Dan Anda perlu mengautentikasi koneksi entah bagaimana.


Untuk melakukan ini, dalam versi pertama Centrifuge, saat menghubungkan, tanda tangan SHA-256 HMAC digunakan, berdasarkan kunci rahasia yang hanya diketahui oleh backend dan Centrifuge. Ini memastikan bahwa ID Pengguna yang dikirimkan oleh klien benar-benar miliknya.


Mungkin transfer parameter koneksi yang benar dan pembuatan token adalah salah satu kesulitan utama dalam mengintegrasikan Centrifugo ke dalam proyek.


Ketika Centrifuge muncul, standar JWT belum begitu populer. Sekarang, beberapa tahun kemudian, perpustakaan untuk generasi JWT tersedia untuk sebagian besar bahasa populer . Ide utama JWT adalah apa yang dibutuhkan Centrifuge: konfirmasi keaslian data yang dikirimkan. Dalam HMAC versi kedua, tanda tangan yang dibuat secara manual memberi jalan bagi penggunaan JWT. Ini memungkinkan untuk menghapus kebutuhan akan dukungan untuk fungsi pembantu untuk pembuatan token yang benar di perpustakaan untuk berbagai bahasa.


Misalnya, dalam Python, token untuk menghubungkan ke Centrifugo dapat dihasilkan sebagai berikut:


 import jwt import time token = jwt.encode({"user": "42", "exp": int(time.time()) + 10*60}, "secret").decode() print(token) 

Penting untuk dicatat bahwa jika Anda menggunakan perpustakaan Centrifuge, Anda dapat mengotentikasi pengguna menggunakan metode Go asli - di dalam middleware. Contohnya ada di repositori.


GRPC


Selama pengembangan, saya mencoba GRPC streaming dua arah sebagai transportasi untuk komunikasi antara klien dan server (selain Websocket dan fallback SockJS berbasis HTTP). Apa yang bisa saya katakan? Dia bekerja. Namun, saya tidak menemukan skenario tunggal di mana streaming GRPC dua arah akan lebih baik daripada Websocket. Saya melihat terutama pada metrik server: lalu lintas yang dihasilkan melalui antarmuka jaringan, konsumsi CPU oleh server dengan sejumlah besar koneksi masuk, konsumsi memori per koneksi.


GRPC hilang dari Websocket dalam segala hal:


  • GRPC menghasilkan 20% lebih banyak lalu lintas dalam skenario yang sama,
  • GRPC mengkonsumsi 2-3 kali lebih banyak CPU (tergantung pada konfigurasi koneksi - semua berlangganan saluran yang berbeda atau semua berlangganan satu saluran),
  • GRPC mengkonsumsi 4 kali lebih banyak RAM per koneksi. Misalnya, pada koneksi 10k, server Websocket memakan 500 MB memori, dan GRPC - 2Gb.

Hasilnya cukup ... diharapkan. Secara umum, dalam GRPC, sebagai transportasi klien, saya tidak melihat banyak akal - dan menghapus kode dengan hati nurani yang jelas sampai, mungkin, waktu yang lebih baik.


Namun, GRPC sangat bagus untuk tujuan utamanya - untuk menghasilkan kode yang memungkinkan Anda melakukan panggilan RPC antar layanan menggunakan skema yang telah ditentukan. Oleh karena itu, selain API HTTP, Centrifuge sekarang juga akan memiliki dukungan API berbasis GRPC, misalnya, untuk menerbitkan pesan baru ke saluran dan metode API server lain yang tersedia.


Kesulitan dengan pelanggan


Perubahan yang dibuat dalam versi kedua, saya menghapus dukungan wajib perpustakaan untuk server API - menjadi lebih mudah untuk diintegrasikan di sisi server, namun, protokol klien dalam proyek diubah dan memiliki sejumlah fitur yang cukup. Ini membuat implementasi pelanggan cukup sulit. Untuk versi kedua, kami sekarang memiliki klien untuk Javascript yang berfungsi di browser, harus bekerja dengan NodeJS dan React-Native. Ada klien di Go dan dibangun berdasarkan dan pengikat proyek gomobile untuk iOS dan Android .


Untuk kebahagiaan total, tidak ada cukup perpustakaan asli untuk iOS dan Android. Untuk versi pertama Centrifugo, mereka dibeli oleh orang-orang dari komunitas open-source. Saya ingin percaya hal seperti ini akan terjadi sekarang.


Baru-baru ini saya mencoba keberuntungan saya dengan mengirimkan aplikasi untuk hibah MOSS dari Mozilla , bermaksud untuk berinvestasi dalam pengembangan klien, tetapi ditolak. Alasannya adalah komunitas yang kurang aktif di Github. Sayangnya, ini benar, tetapi seperti yang Anda lihat, saya mengambil beberapa langkah untuk memperbaiki situasi.



Kesimpulan


Saya tidak mengumumkan semua fitur yang akan muncul di Centrifugo v2 - sedikit lebih banyak informasi dalam masalah di Github . Rilis server belum terjadi, tetapi akan segera terjadi. Masih ada saat-saat yang belum selesai, termasuk kebutuhan untuk melengkapi dokumentasi. Prototipe dokumentasi dapat dilihat di sini . Jika Anda adalah pengguna Centrifugo, sekarang adalah waktu yang tepat untuk memengaruhi server versi kedua. Suatu saat ketika tidak begitu menakutkan untuk memecahkan sesuatu, untuk kemudian berbuat lebih baik. Bagi yang berminat: pengembangan terkonsentrasi di cabang c2 .


Sulit bagi saya untuk menilai berapa banyak permintaan perpustakaan Centrifuge yang mendasari Centrifugo v2 akan diminati. Saat ini, saya senang bahwa saya dapat membawanya ke keadaan saat ini. Indikator terpenting bagi saya sekarang adalah jawaban atas pertanyaan "apakah saya sendiri akan menggunakan perpustakaan ini dalam proyek pribadi saya?" Jawaban saya adalah ya. Sedang bekerja? Ya Oleh karena itu, saya percaya bahwa pengembang lain akan menghargainya.


PS Saya ingin mengucapkan terima kasih kepada orang-orang yang membantu pekerjaan dan saran - Dmitry Korolkov, Artemy Ryabinkov, Oleg Kuzmin. Akan ketat tanpa Anda.

Source: https://habr.com/ru/post/id416915/


All Articles