RoadRunner: PHP tidak dibuat untuk mati, atau Golang untuk menyelamatkan

RoadRunner: PHP tidak dibuat untuk mati, atau Golang untuk menyelamatkan

Hai Habr! Kami aktif di Badoo mengerjakan kinerja PHP, karena kami memiliki sistem yang cukup besar dalam bahasa ini dan masalah kinerja adalah masalah penghematan uang. Lebih dari sepuluh tahun yang lalu, kami membuat PHP-FPM untuk ini, yang pada awalnya merupakan kumpulan patch untuk PHP, dan kemudian memasuki distribusi resmi.

Dalam beberapa tahun terakhir, PHP telah membuat kemajuan besar: pengumpul sampah telah ditingkatkan, tingkat stabilitas telah meningkat - hari ini Anda dapat menulis daemon dan skrip berumur panjang di PHP tanpa masalah. Hal ini memungkinkan Spiral Scout untuk melangkah lebih jauh: RoadRunner, tidak seperti PHP-FPM, tidak membersihkan memori di antara permintaan, yang memberikan peningkatan kinerja tambahan (walaupun pendekatan ini mempersulit proses pengembangan). Kami sedang bereksperimen dengan alat ini, namun kami belum mendapatkan hasil apa pun untuk dibagikan. Untuk membuat menunggu mereka lebih menyenangkan, kami menerbitkan terjemahan pengumuman RoadRunner dari Spiral Scout.

Pendekatan dari artikel ini dekat dengan kita: ketika memecahkan masalah kita, kita juga paling sering menggunakan sekumpulan PHP dan Go, mendapatkan manfaat dari kedua bahasa dan tidak mengabaikan satu bahasa demi yang lain.

Menikmati!

Dalam sepuluh tahun terakhir, kami telah membuat aplikasi untuk perusahaan dari daftar Keberuntungan 500, dan untuk bisnis dengan audiens tidak lebih dari 500 pengguna. Selama ini, teknisi kami telah mengembangkan backend terutama di PHP. Namun dua tahun lalu, ada sesuatu yang berdampak besar tidak hanya pada kinerja produk kami, namun juga pada skalabilitasnya - kami memperkenalkan Golang (Go) ke dalam rangkaian teknologi kami.

Kami segera mengetahui bahwa Go memungkinkan kami membangun aplikasi yang lebih besar dengan peningkatan kinerja hingga 40x. Dengannya, kami dapat memperluas produk yang sudah ada yang ditulis dalam PHP, menyempurnakannya dengan menggabungkan keunggulan kedua bahasa tersebut.

Kami akan memberi tahu Anda bagaimana kombinasi Go dan PHP membantu memecahkan masalah pengembangan nyata dan bagaimana hal itu berubah menjadi alat bagi kami yang dapat menghilangkan beberapa masalah yang terkait dengan Model sekarat PHP.

Lingkungan pengembangan PHP harian Anda

Sebelum kita berbicara tentang bagaimana Anda dapat menggunakan Go untuk menghidupkan kembali model PHP yang sekarat, mari kita lihat lingkungan pengembangan PHP default Anda.

Dalam kebanyakan kasus, Anda menjalankan aplikasi menggunakan kombinasi server web nginx dan server PHP-FPM. Yang pertama menyajikan file statis dan mengalihkan permintaan spesifik ke PHP-FPM, sementara PHP-FPM sendiri mengeksekusi kode PHP. Anda mungkin menggunakan kombinasi Apache dan mod_php yang kurang populer. Meskipun cara kerjanya sedikit berbeda, prinsipnya tetap sama.

Mari kita lihat bagaimana PHP-FPM mengeksekusi kode aplikasi. Ketika permintaan masuk, PHP-FPM menginisialisasi proses anak PHP dan meneruskan rincian permintaan sebagai bagian dari statusnya (_GET, _POST, _SERVER, dll.).

Status tidak dapat diubah selama eksekusi skrip PHP, jadi hanya ada satu cara untuk mendapatkan kumpulan data masukan baru: dengan mengosongkan memori proses dan menginisialisasi ulang.

Model eksekusi ini memiliki banyak keunggulan. Anda tidak perlu terlalu khawatir tentang konsumsi memori, semua proses diisolasi sepenuhnya, dan jika salah satunya "mati", maka proses tersebut akan dibuat ulang secara otomatis dan tidak akan memengaruhi proses lainnya. Namun pendekatan ini juga memiliki kelemahan yang muncul saat mencoba menskalakan aplikasi.

Kekurangan dan Inefisiensi Lingkungan PHP Reguler

Jika Anda seorang pengembang PHP profesional, maka Anda tahu di mana harus memulai proyek baru - dengan pilihan kerangka kerja. Ini terdiri dari perpustakaan injeksi ketergantungan, ORM, terjemahan dan templat. Dan, tentu saja, semua masukan pengguna dapat dengan mudah dimasukkan ke dalam satu objek (Symfony/HttpFoundation atau PSR-7). Kerangka kerja itu keren!

Tapi semuanya ada harganya. Dalam kerangka kerja tingkat perusahaan mana pun, untuk memproses permintaan pengguna sederhana atau akses ke database, Anda harus memuat setidaknya lusinan file, membuat banyak kelas, dan mengurai beberapa konfigurasi. Namun yang terburuk adalah setelah menyelesaikan setiap tugas, Anda harus mengatur ulang semuanya dan memulai kembali: semua kode yang baru saja Anda mulai menjadi tidak berguna, dengan bantuannya Anda tidak akan lagi memproses permintaan lain. Katakan hal ini kepada programmer mana pun yang menulis dalam bahasa lain, dan Anda akan melihat kebingungan di wajahnya.

Insinyur PHP telah mencari cara untuk memecahkan masalah ini selama bertahun-tahun, menggunakan teknik pemuatan lambat yang cerdas, kerangka mikro, perpustakaan yang dioptimalkan, cache, dll. Namun pada akhirnya, Anda masih harus mengatur ulang seluruh aplikasi dan memulai dari awal, lagi dan lagi . (Catatan Penerjemah: masalah ini sebagian akan terpecahkan dengan munculnya preload dalam PHP 7.4)

Bisakah PHP dengan Go bertahan lebih dari satu permintaan?

Dimungkinkan untuk menulis skrip PHP yang berumur lebih dari beberapa menit (hingga berjam-jam atau berhari-hari): misalnya, tugas cron, parser CSV, pemutus antrian. Mereka semua bekerja berdasarkan skenario yang sama: mengambil tugas, menjalankannya, dan menunggu tugas berikutnya. Kode selalu tersimpan di memori, menghemat milidetik yang berharga karena ada banyak langkah tambahan yang diperlukan untuk memuat kerangka kerja dan aplikasi.

Namun mengembangkan skrip yang berumur panjang tidaklah mudah. Kesalahan apa pun akan menghentikan proses sepenuhnya, mendiagnosis kebocoran memori sangat menyebalkan, dan proses debug F5 tidak dapat dilakukan lagi.

Situasi telah membaik dengan dirilisnya PHP 7: pengumpul sampah yang andal telah muncul, penanganan kesalahan menjadi lebih mudah, dan ekstensi kernel kini anti bocor. Benar, para insinyur masih perlu berhati-hati dengan memori dan mewaspadai masalah status dalam kode (adakah bahasa yang dapat mengabaikan hal-hal ini?). Namun, PHP 7 memiliki lebih sedikit kejutan bagi kami.

Apakah mungkin untuk mengambil model bekerja dengan skrip PHP yang berumur panjang, mengadaptasinya ke tugas-tugas yang lebih sepele seperti memproses permintaan HTTP, dan dengan demikian menghilangkan kebutuhan untuk memuat semuanya dari awal dengan setiap permintaan?

Untuk mengatasi masalah ini, pertama-tama kita perlu mengimplementasikan aplikasi server yang dapat menerima permintaan HTTP dan mengarahkannya satu per satu ke pekerja PHP tanpa mematikannya setiap saat.

Kami tahu bahwa kami dapat menulis server web dalam PHP murni (PHP-PM) atau menggunakan ekstensi C (Swoole). Dan meskipun setiap metode memiliki kelebihannya masing-masing, kedua opsi tersebut tidak cocok untuk kami - kami menginginkan sesuatu yang lebih. Kami membutuhkan lebih dari sekedar server web - kami berharap mendapatkan solusi yang dapat menyelamatkan kami dari masalah yang terkait dengan “permulaan yang sulit” di PHP, yang pada saat yang sama dapat dengan mudah diadaptasi dan diperluas untuk aplikasi tertentu. Artinya, kami membutuhkan server aplikasi.

Bisakah Go membantu dalam hal ini? Kami tahu hal ini bisa terjadi karena bahasa tersebut mengkompilasi aplikasi ke dalam biner tunggal; itu lintas platform; menggunakan model pemrosesan paralel (konkurensi) miliknya sendiri yang sangat elegan dan perpustakaan untuk bekerja dengan HTTP; dan terakhir, ribuan perpustakaan dan integrasi sumber terbuka akan tersedia bagi kita.

Kesulitan Menggabungkan Dua Bahasa Pemrograman

Pertama-tama, penting untuk menentukan bagaimana dua atau lebih aplikasi akan berkomunikasi satu sama lain.

Misalnya menggunakan perpustakaan yang sangat baik Alex Palaestras, dimungkinkan untuk berbagi memori antara proses PHP dan Go (mirip dengan mod_php di Apache). Namun perpustakaan ini memiliki fitur yang membatasi penggunaannya untuk menyelesaikan masalah kita.

Kami memutuskan untuk menggunakan pendekatan lain yang lebih umum: membangun interaksi antar proses melalui soket/jalur pipa. Pendekatan ini telah terbukti dapat diandalkan selama beberapa dekade terakhir dan telah dioptimalkan dengan baik pada tingkat sistem operasi.

Untuk memulainya, kami membuat protokol biner sederhana untuk pertukaran data antar proses dan menangani kesalahan transmisi. Dalam bentuknya yang paling sederhana, jenis protokol ini mirip dengan tali jaring с header paket ukuran tetap (dalam kasus kami 17 byte), yang berisi informasi tentang jenis paket, ukurannya dan topeng biner untuk memeriksa integritas data.

Di sisi PHP kami menggunakan fungsi paket, dan di sisi Go, perpustakaan pengkodean/biner.

Bagi kami, satu protokol tampaknya tidak cukup - dan kami menambahkan kemampuan untuk menelepon layanan net/rpc go langsung dari PHP. Nantinya, ini banyak membantu kami dalam pengembangan, karena kami dapat dengan mudah mengintegrasikan perpustakaan Go ke dalam aplikasi PHP. Hasil karya ini dapat dilihat misalnya pada produk open-source kami yang lain Goridge.

Mendistribusikan tugas ke beberapa pekerja PHP

Setelah menerapkan mekanisme interaksi, kami mulai memikirkan cara paling efisien untuk mentransfer tugas ke proses PHP. Ketika tugas tiba, server aplikasi harus memilih pekerja bebas untuk melaksanakannya. Jika pekerja/proses keluar dengan kesalahan atau "mati", kami membuangnya dan membuat yang baru untuk menggantikannya. Dan jika pekerja/proses telah berhasil diselesaikan, kami mengembalikannya ke kumpulan pekerja yang tersedia untuk melakukan tugas.

RoadRunner: PHP tidak dibuat untuk mati, atau Golang untuk menyelamatkan

Untuk menyimpan kumpulan pekerja aktif, kami menggunakan saluran yang di-buffer, untuk menghapus pekerja yang tiba-tiba “mati” dari kumpulan, kami menambahkan mekanisme untuk melacak kesalahan dan status pekerja.

Hasilnya, kami mendapatkan server PHP yang berfungsi yang mampu memproses permintaan apa pun yang disajikan dalam bentuk biner.

Agar aplikasi kita mulai berfungsi sebagai server web, kita harus memilih standar PHP yang andal untuk mewakili setiap permintaan HTTP yang masuk. Dalam kasus kami, kami hanya mengubah permintaan net/http dari Buka format PSR-7sehingga kompatibel dengan sebagian besar framework PHP yang ada saat ini.

Karena PSR-7 dianggap tidak dapat diubah (beberapa orang akan mengatakan secara teknis tidak), pengembang harus menulis aplikasi yang pada prinsipnya tidak memperlakukan permintaan tersebut sebagai entitas global. Ini sangat cocok dengan konsep proses PHP yang berumur panjang. Implementasi akhir kami, yang belum diberi nama, tampak seperti ini:

RoadRunner: PHP tidak dibuat untuk mati, atau Golang untuk menyelamatkan

Memperkenalkan RoadRunner - server aplikasi PHP berkinerja tinggi

Tugas pengujian pertama kami adalah backend API, yang secara berkala menghasilkan permintaan yang tidak dapat diprediksi (lebih sering dari biasanya). Meskipun nginx cukup memadai dalam banyak kasus, kami sering menemukan kesalahan 502 karena kami tidak dapat menyeimbangkan sistem dengan cukup cepat untuk peningkatan beban yang diharapkan.

Untuk menggantikan solusi ini, kami menerapkan server aplikasi PHP/Go pertama kami pada awal tahun 2018. Dan langsung mendapat efek yang luar biasa! Kami tidak hanya menghilangkan kesalahan 502 sepenuhnya, namun kami juga mampu mengurangi jumlah server hingga dua pertiganya, sehingga menghemat banyak uang dan obat sakit kepala bagi para insinyur dan manajer produk.

Pada pertengahan tahun, kami telah menyempurnakan solusi kami, menerbitkannya di GitHub di bawah lisensi MIT dan menamainya RoadRunner, sehingga menekankan kecepatan dan efisiensinya yang luar biasa.

Bagaimana RoadRunner dapat meningkatkan tumpukan pengembangan Anda

Aplikasi RoadRunner memungkinkan kami menggunakan Middleware net/http di sisi Go untuk melakukan verifikasi JWT sebelum permintaan mencapai PHP, serta menangani WebSockets dan status agregat secara global di Prometheus.

Berkat RPC bawaan, Anda dapat membuka API perpustakaan Go apa pun untuk PHP tanpa menulis pembungkus ekstensi. Lebih penting lagi, dengan RoadRunner Anda dapat menerapkan server non-HTTP baru. Contohnya termasuk menjalankan handler di PHP AWS Lambda, membuat pemecah antrian yang andal, dan bahkan menambahkan gRPC ke aplikasi kita.

Dengan bantuan komunitas PHP dan Go, kami meningkatkan stabilitas solusi, meningkatkan kinerja aplikasi hingga 40 kali lipat dalam beberapa pengujian, meningkatkan alat debugging, menerapkan integrasi dengan kerangka kerja Symfony, dan menambahkan dukungan untuk HTTPS, HTTP/2, plugin, dan PSR-17.

Kesimpulan

Beberapa orang masih terjebak dalam gagasan kuno tentang PHP sebagai bahasa yang lambat dan berat yang hanya bagus untuk menulis plugin untuk WordPress. Orang-orang ini bahkan mungkin mengatakan bahwa PHP memiliki keterbatasan: ketika aplikasi menjadi cukup besar, Anda harus memilih bahasa yang lebih “matang” dan menulis ulang basis kode yang terakumulasi selama bertahun-tahun.

Untuk semua ini saya ingin menjawab: pikirkan lagi. Kami percaya bahwa hanya Anda yang menetapkan batasan apa pun untuk PHP. Anda dapat menghabiskan seluruh hidup Anda untuk beralih dari satu bahasa ke bahasa lain, mencoba menemukan bahasa yang paling sesuai dengan kebutuhan Anda, atau Anda dapat mulai menganggap bahasa sebagai alat. Kelemahan bahasa seperti PHP mungkin sebenarnya menjadi alasan keberhasilannya. Dan jika Anda menggabungkannya dengan bahasa lain seperti Go, maka Anda akan menciptakan produk yang jauh lebih hebat dibandingkan jika Anda dibatasi hanya menggunakan satu bahasa saja.

Setelah bekerja dengan banyak Go dan PHP, kami dapat mengatakan bahwa kami menyukainya. Kami tidak berencana mengorbankan satu demi satu - sebaliknya, kami akan mencari cara untuk mendapatkan nilai lebih dari tumpukan ganda ini.

UPD: kami menyambut pencipta RoadRunner dan rekan penulis artikel asli - Lachesis

Sumber: www.habr.com

Tambah komentar