Menambah Socket.io ke Node.js multi-threaded

Foto oleh Vidar Nordli-Mathisen pada Unsplash

Salah satu kelemahan Node adalah bahawa ia adalah satu threaded. Sudah tentu, ada cara di sekelilingnya - iaitu modul yang dipanggil kluster. Kluster membolehkan kami menyebarkan permohonan kami melalui pelbagai thread.

Walau bagaimanapun, sekarang masalah baru muncul sendiri. Lihat, kod kami yang sedang berjalan merentasi beberapa keadaan sebenarnya mempunyai beberapa kelemahan yang ketara. Salah seorang daripada mereka tidak mempunyai keadaan global.

Biasanya, dalam satu-satunya contoh, ini tidak akan menjadi kebimbangan. Bagi kami kini ia mengubah segala-galanya.

Mari lihat mengapa.

Jadi, apakah masalahnya?

Permohonan kami adalah sembang dalam talian yang mudah berjalan pada empat benang. Ini membolehkan pengguna untuk log masuk pada masa yang sama pada telefon dan komputer mereka.

Bayangkan kita mempunyai set soket sama seperti kita akan menetapkannya untuk satu benang. Dengan kata lain, kita kini mempunyai satu negara global yang besar dengan soket.

Apabila pengguna log masuk pada komputer mereka, laman web ini membuka sambungan dengan contoh Socket.io di pelayan kami. Soket disimpan di dalam keadaan benang # 3.

Kini, bayangkan pengguna pergi ke dapur untuk mengambil makanan ringan dan mengambil telefon mereka dengan mereka - secara semulajadi mahu menyimpan teks dengan rakan-rakan mereka dalam talian.

Telefon mereka menyambung kepada benang # 4, dan soket disimpan dalam keadaan benang.

Menghantar mesej dari telefon mereka akan melakukan pengguna tidak baik. Hanya orang dari thread # 3 akan dapat melihat mesej itu. Itu kerana soket yang disimpan pada benang # 3 tidak tersusun secara ajaib pada benang # 1, # 2 dan # 4 juga.

Cukup lucu, walaupun pengguna sendiri tidak akan melihat mesej mereka di komputer mereka apabila mereka kembali dari dapur.

Sudah tentu, apabila mereka menyegarkan laman web, kami boleh menghantar permintaan GET dan mengambil 50 mesej terakhir, tetapi kami tidak boleh mengatakan ia adalah cara 'dinamik', bolehkah kami?

Kenapa ini terjadi?

Menyebarkan pelayan kami ke atas pelbagai thread adalah dengan cara yang sama seperti mempunyai beberapa pelayan berasingan. Mereka tidak tahu tentang kewujudan masing-masing dan pastinya tidak berkongsi ingatan apa pun. Ini bermakna objek pada satu ketika tidak wujud pada yang lain.

Soket yang disimpan dalam benang # 3 tidak semestinya semua soket yang pengguna gunakan pada masa ini. Sekiranya rakan pengguna berada di benang yang berbeza, mereka tidak akan melihat mesej pengguna melainkan mereka menyegarkan semula laman web tersebut.

Sebaik-baiknya, kami ingin memaklumkan kejadian lain mengenai peristiwa untuk pengguna. Dengan cara ini, kami dapat memastikan bahawa setiap peranti yang bersambung menerima kemas kini secara langsung.

Penyelesaian

Kami boleh memaklumkan benang lain dengan menggunakan paradigma mesej terbitan / langganan Redis '(pubsub).

Redis adalah kedai struktur data sumber terbuka (stor berlesen BSD). Ia boleh digunakan sebagai pangkalan data, cache dan broker mesej.

Ini bermakna kita boleh menggunakan Redis untuk mempunyai peristiwa yang diedarkan di antara keadaan kita.

Perhatikan bahawa biasanya kita mungkin akan menyimpan keseluruhan struktur kita di dalam Redis. Walau bagaimanapun, kerana strukturnya tidak bersiri dan perlu disimpan "hidup" di dalam ingatan, kita akan menyimpan sebahagiannya pada setiap contoh.

Aliran

Mari kita fikirkan langkah-langkah di mana kita akan mengendalikan peristiwa yang akan datang.

  1. Acara yang dipanggil mesej datang ke salah satu soket kami - dengan cara ini, kita tidak perlu mendengar setiap peristiwa yang mungkin.
  2. Di dalam objek yang dihantar kepada penganjur peristiwa ini sebagai hujah, kita dapat mencari nama acara tersebut. Sebagai contoh, sendMessage - .on ('message', ({event}) => {}).
  3. Jika ada pengendali untuk nama ini, kami akan melaksanakannya.
  4. Pengendali boleh melaksanakan penghantaran dengan respon.
  5. Pengiriman menghantar acara respons kepada pubisub Redis kami. Dari sana ia akan dipancarkan kepada setiap contoh kami.
  6. Setiap contoh memancarkannya ke soket mereka, memastikan setiap pelanggan yang terhubung akan menerima acara tersebut.

Seolah rumit, saya tahu, tetapi beruang dengan saya.

Pelaksanaan

Inilah repositori dengan persekitaran sedia, supaya kita tidak perlu memasang dan menetapkan semuanya.

Pertama, kami akan menyediakan pelayan dengan Express.

Kami mencipta aplikasi Express, pelayan HTTP dan soket init.

Sekarang kita boleh memberi tumpuan kepada menambah soket.

Kami lulus contoh pelayan Socket.io untuk fungsi kami di mana kami menetapkan middlewares.

onAuth

Fungsi onAuth hanya meniru kebenaran kebenaran. Dalam kes kita, ia berasaskan token.

Secara peribadi, saya mungkin menggantikannya dengan JWT pada masa akan datang, tetapi ia tidak dikuatkuasakan dalam apa cara sekalipun.

Sekarang, mari beralih ke middleware onConnection.

onConnection

Di sini kita melihat bahawa kita mengambil id pengguna, yang telah ditetapkan pada middleware terdahulu, dan simpannya di soketState kami, dengan kunci menjadi id dan nilai menjadi pelbagai soket.

Seterusnya, kami mendengar acara mesej. Keseluruhan logik kami didasarkan pada itu - setiap peristiwa frontend yang menghantar kami akan dipanggil: mesej.

Nama acara akan dihantar di dalam objek argumen - seperti yang dinyatakan di atas.

Pengendali

Seperti yang dapat anda lihat diConnection, khususnya dalam pendengar untuk acara mesej, kami sedang mencari pengendali berdasarkan nama acara tersebut.

Pengendali kami adalah hanya objek di mana kunci adalah nama acara dan nilai adalah fungsi. Kami akan menggunakannya untuk mendengar peristiwa dan bertindak balas sewajarnya.

Juga, kemudian, kami akan menambah fungsi penghantaran dan menggunakannya untuk menghantar acara merentas keadaan.

SocketsState

Kita tahu antara muka negeri kita, tetapi kita masih belum melaksanakannya.

Kami menambah kaedah untuk menambah dan mengeluarkan soket, serta untuk memancarkan acara.

Fungsi tambahan memeriksa sama ada negara mempunyai harta yang bersamaan dengan id pengguna. Sekiranya itu berlaku, kami hanya menambahnya kepada array kami yang sedia ada. Jika tidak, kami buat array baharu terlebih dahulu.

Fungsi keluarkan juga memeriksa jika keadaan mempunyai id pengguna dalam sifatnya. Jika tidak - tidak ada apa-apa. Jika tidak, ia menapis array untuk mengeluarkan soket dari array. Kemudian jika array kosong, ia akan membuangnya dari keadaan, menetapkan harta itu kepada undefined.

Redis 'pubsub

Untuk mewujudkan pubsub kami, kami akan menggunakan pakej yang dipanggil node-redis-pubsub.

Menambah penghantaran

Ok, kini semua yang perlu dilakukan ialah menambah fungsi penghantaran ...

... dan tambahkan pendengar untuk outgoing_socket_message. Dengan cara ini, setiap kejadian menerima peristiwa itu dan menghantarnya kepada soket pengguna.

Membuatnya semua berbilang benang

Akhirnya, mari tambahkan kod yang diperlukan untuk pelayan kami menjadi multi-threaded.

Nota: Kita perlu membunuh pelabuhan, kerana setelah berhenti proses Nodemon kami dengan Ctrl + c, ia hanya digantung di sana.

Dengan sedikit tweaking, kami kini mempunyai soket kerja dalam semua keadaan. Akibatnya: pelayan jauh lebih cekap.

Terima kasih banyak untuk membaca!

Saya menghargai bahawa semua mungkin kelihatan hebat pada mulanya dan berat untuk mengambil semuanya sekaligus. Dengan itu, saya sangat menggalakkan anda untuk membaca kod itu secara keseluruhan dan merenungkannya secara keseluruhan.

Sekiranya anda mempunyai sebarang soalan atau komen, sila laporkan di bahagian komen di bawah atau hantar mesej kepada saya.

Lihat media sosial saya!

Sertai newsletter saya!

Originally diterbitkan di www.mcieslar.com pada 10 September 2018.