
Halo, Habr! Kami di Badoo secara aktif
bekerja pada kinerja PHP , karena kami memiliki sistem yang cukup besar dalam bahasa ini dan masalah kinerja adalah masalah menghemat uang. Lebih dari sepuluh tahun yang lalu, kami membuat untuk PHP-FPM ini, yang pertama merupakan satu set tambalan untuk PHP, dan kemudian masuk ke pengiriman resmi.
Dalam beberapa tahun terakhir, PHP telah membuat langkah besar: pengumpul sampah telah meningkat, tingkat stabilitas telah meningkat - hari ini di PHP Anda dapat menulis setan dan skrip berumur panjang tanpa masalah khusus. Ini memungkinkan Spiral Scout untuk melangkah lebih jauh: RoadRunner, tidak seperti PHP-FPM, tidak menghapus memori di antara permintaan, yang memberikan peningkatan kinerja tambahan (meskipun pendekatan ini mempersulit proses pengembangan). Kami sekarang sedang bereksperimen dengan alat ini, tetapi kami belum memiliki hasil yang dapat dibagikan. Untuk menunggu mereka lebih menyenangkan, kami
menerbitkan terjemahan dari pengumuman RoadRunner dari Spiral Scout.Pendekatan dari artikel ini dekat dengan kami: ketika menyelesaikan masalah kami, kami juga paling sering menggunakan banyak PHP dan Go, mendapatkan keuntungan dari kedua bahasa dan tidak mengabaikan satu demi yang lain.
Selamat menikmati!
Selama sepuluh tahun terakhir, kami telah membuat aplikasi untuk
perusahaan Fortune 500 dan untuk bisnis dengan jumlah pemirsa tidak lebih dari 500 pengguna. Selama ini, teknisi kami mengembangkan backend terutama dalam PHP. Tapi dua tahun lalu, sesuatu yang sangat mempengaruhi tidak hanya kinerja produk kami, tetapi juga skalabilitasnya - kami memperkenalkan Golang (Go) ke tumpukan teknologi kami.
Hampir segera, kami menemukan bahwa Go memungkinkan kami untuk membuat aplikasi yang lebih besar dengan kinerja hingga 40 kali lebih tinggi. Dengan itu, kami dapat memperluas produk yang sudah ada yang ditulis dalam PHP, meningkatkannya melalui kombinasi keunggulan dari kedua bahasa.
Kami akan memberi tahu Anda bagaimana kombinasi Go dan PHP membantu menyelesaikan masalah pengembangan nyata dan bagaimana hal itu menjadi bagi kami alat yang dapat meringankan sebagian masalah yang terkait dengan model
PHP "sekarat" .
Lingkungan Pengembangan PHP Sehari-Hari Anda
Sebelum kita berbicara tentang bagaimana Go dapat menghidupkan model "sekarat" PHP, mari kita lihat lingkungan pengembangan PHP standar Anda.
Dalam kebanyakan kasus, Anda meluncurkan aplikasi menggunakan kombinasi server web nginx dan server PHP-FPM. Yang pertama menyajikan file statis dan mengarahkan permintaan khusus ke PHP-FPM, dan PHP-FPM sendiri mengeksekusi kode PHP. Mungkin Anda menggunakan bundel yang kurang populer dari Apache dan mod_php. Tetapi meskipun kerjanya sedikit berbeda, prinsip-prinsipnya sama.
Pertimbangkan bagaimana PHP-FPM mengeksekusi kode aplikasi. Ketika permintaan datang, PHP-FPM menginisialisasi proses PHP anak, dan meneruskan detail permintaan sebagai bagian dari kondisinya (_GET, _POST, _SERVER, dll.).
Keadaan tidak dapat berubah selama pelaksanaan skrip PHP, jadi Anda bisa mendapatkan satu set data input baru hanya dengan satu cara: dengan menghapus memori proses dan menginisialisasi lagi.
Model eksekusi ini memiliki banyak keunggulan. Anda tidak perlu terlalu khawatir tentang konsumsi memori, semua proses sepenuhnya terisolasi, dan jika salah satu dari mereka mati, itu akan dibuat ulang secara otomatis dan ini tidak akan mempengaruhi proses lainnya. Tetapi pendekatan ini juga memiliki kelemahan yang muncul ketika mencoba untuk skala aplikasi.
Kerugian dan inefisiensi dari lingkungan PHP biasa
Jika Anda terlibat dalam pengembangan profesional dalam PHP, maka Anda tahu di mana harus memulai proyek baru, dengan pilihan kerangka kerja. Ini adalah perpustakaan untuk injeksi ketergantungan, ORM, terjemahan, dan templat. Dan, tentu saja, semua input pengguna dapat dengan mudah ditempatkan di satu objek (Symfony / HttpFoundation atau PSR-7). Kerangka itu keren!
Tapi semuanya punya harga. Dalam kerangka kerja tingkat perusahaan apa pun, untuk memproses permintaan pengguna sederhana atau mengakses database, Anda harus mengunduh setidaknya lusinan file, membuat banyak kelas, dan memilah beberapa konfigurasi. Tetapi bagian terburuknya adalah setelah menyelesaikan setiap tugas, Anda harus mengatur ulang semuanya dan mulai lagi: semua kode yang baru Anda mulai menjadi tidak berguna, dengan itu Anda tidak akan lagi memproses permintaan lain. Beri tahu programmer mana pun yang menulis dalam bahasa lain tentang hal itu dan Anda akan melihat kebingungan di wajahnya.
Selama bertahun-tahun, para insinyur PHP telah mencari cara untuk mengatasi masalah ini, menggunakan metode pemuatan malas, mikroframe, perpustakaan yang dioptimalkan, cache, dll. Namun pada akhirnya, Anda masih harus mengatur ulang seluruh aplikasi dan mulai lagi, berulang-ulang.
(Catatan Penerjemah: masalah ini sebagian akan diselesaikan dengan munculnya preload di PHP 7.4)
Bisakah PHP menggunakan Go untuk bertahan lebih dari satu permintaan?
Anda dapat menulis skrip PHP yang akan hidup lebih dari beberapa menit (hingga berjam-jam atau berhari-hari): misalnya, tugas cron, parser CSV, pemecah antrian. Mereka semua bekerja berdasarkan satu skenario: mereka mengekstrak tugas, menyelesaikannya, menunggu yang berikutnya. Kode ini selalu ada dalam memori, menghemat milidetik yang berharga, karena banyak langkah tambahan diperlukan untuk mengunduh kerangka kerja dan aplikasi.
Tetapi mengembangkan skrip berumur panjang tidak sesederhana itu. Kesalahan apa pun benar-benar membunuh proses, diagnosis kebocoran memori menyebalkan, dan debugging menggunakan F5 tidak lagi mungkin.
Situasi membaik dengan rilis PHP 7: seorang pengumpul sampah yang andal muncul, menjadi lebih mudah untuk menangani kesalahan, dan ekstensi kernel sekarang dilindungi dari kebocoran. Benar, insinyur masih perlu hati-hati menangani memori dan mengingat tentang masalah status dalam kode (apakah ada bahasa di mana Anda dapat mengabaikan hal-hal ini?). Namun, di PHP 7, ada lebih sedikit kejutan.
Apakah mungkin untuk mengambil model untuk bekerja dengan skrip PHP berumur panjang, mengadaptasinya untuk tugas yang lebih sepele seperti memproses permintaan HTTP dan dengan demikian menyingkirkan kebutuhan untuk mengunduh semuanya dari awal dengan setiap permintaan?
Untuk mengatasi masalah ini, pertama-tama perlu untuk mengimplementasikan aplikasi server yang mampu menerima permintaan HTTP dan mengarahkan mereka satu per satu ke pekerja PHP, tanpa membunuhnya setiap saat.
Kami tahu bahwa kami dapat menulis server web dalam PHP murni (PHP-PM) atau menggunakan ekstensi-C (Swoole). Dan meskipun masing-masing metode memiliki kelebihannya sendiri, kedua opsi tidak cocok untuk kita - saya menginginkan sesuatu yang lebih. Bukan hanya server web yang dibutuhkan - kami berharap mendapatkan solusi yang dapat menyelamatkan kami dari masalah yang terkait dengan "awal yang sulit" dalam PHP, yang dapat dengan mudah diadaptasi dan diperluas untuk aplikasi tertentu. Artinya, kami membutuhkan server aplikasi.
Bisakah Go membantu dengan ini? Kami tahu itu bisa, karena bahasa ini mengkompilasi aplikasi menjadi file biner tunggal; itu adalah lintas platform; menggunakan model konkurensi sendiri, sangat elegan, dan perpustakaan untuk bekerja dengan HTTP; dan akhirnya, ribuan perpustakaan sumber terbuka dan integrasi akan tersedia bagi kami.
Kesulitan dalam menggabungkan dua bahasa pemrograman
Pertama-tama, perlu untuk menentukan bagaimana dua atau lebih aplikasi akan berkomunikasi satu sama lain.
Misalnya, dengan bantuan
perpustakaan yang sangat baik dari Alex Palaestras, dimungkinkan untuk menerapkan berbagi memori dengan proses PHP dan Go (mirip dengan mod_php di Apache). Tetapi perpustakaan ini memiliki fitur yang membatasi penggunaannya untuk menyelesaikan masalah kita.
Kami memutuskan untuk menggunakan pendekatan yang berbeda dan lebih umum: untuk membangun interaksi antar proses melalui soket / jalur pipa. Pendekatan ini selama beberapa dekade terakhir telah terbukti andal dan telah dioptimalkan dengan baik di tingkat sistem operasi.
Untuk mulai dengan, kami membuat protokol biner sederhana untuk bertukar data antara proses dan menangani kesalahan transmisi. Dalam bentuknya yang paling sederhana, protokol jenis ini mirip dengan
netstring dengan
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
encoding / biner .
Satu protokol tidak cukup bagi kami - dan kami menambahkan kemampuan untuk memanggil layanan
net / rpc langsung dari PHP . Kemudian, ini banyak membantu kami dalam pengembangan, karena kami dapat dengan mudah mengintegrasikan Go library ke aplikasi PHP. Hasil dari pekerjaan ini dapat dilihat, misalnya, di produk open-source kami
Goridge .
Distribusi tugas di antara beberapa pekerja PHP
Setelah menerapkan mekanisme interaksi, kami mulai berpikir tentang cara terbaik mentransfer tugas ke proses PHP. Ketika tugas tiba, server aplikasi harus memilih pekerja gratis untuk menyelesaikannya. Jika pekerja / proses diakhiri dengan kesalahan atau "mati", kami menyingkirkannya dan membuat yang baru sebagai balasannya. Dan jika pekerja / proses bekerja dengan sukses, kami mengembalikannya ke kumpulan pekerja yang tersedia untuk menyelesaikan tugas.

Kami menggunakan
saluran buffered untuk menyimpan kumpulan pekerja aktif, untuk menghapus pekerja βmatiβ yang tak terduga dari kumpulan, kami menambahkan mekanisme untuk melacak kesalahan dan keadaan pekerja.
Sebagai hasilnya, kami mendapatkan server PHP yang berfungsi yang mampu memproses permintaan yang disajikan dalam bentuk biner.
Agar aplikasi kami mulai berfungsi sebagai server web, saya harus memilih standar PHP yang dapat diandalkan untuk menyajikan permintaan HTTP yang masuk. Dalam kasus kami, kami cukup
mengonversi permintaan net / http dari Go ke format
PSR-7 sehingga itu kompatibel dengan sebagian besar kerangka kerja PHP yang tersedia saat ini.
Karena PSR-7 dianggap tidak dapat diubah (seseorang akan mengatakan bahwa secara teknis tidak demikian), pengembang harus menulis aplikasi yang, pada prinsipnya, tidak menangani permintaan sebagai entitas global. Ini berjalan dengan baik dengan konsep proses PHP berumur panjang. Implementasi akhir kami, yang belum menerima nama, tampak seperti ini:

Tugas pengujian pertama kami adalah backend API, yang secara berkala menyebabkan ledakan permintaan yang tidak terduga (jauh lebih sering daripada biasanya). Meskipun dalam kebanyakan kasus terdapat cukup fitur nginx, kami secara teratur menemukan kesalahan 502, karena kami tidak dapat menyeimbangkan sistem dengan cukup cepat untuk peningkatan beban yang diharapkan.
Untuk mengganti solusi ini, pada awal 2018, kami menggunakan server aplikasi PHP / Go pertama kami. Dan langsung mendapat efek luar biasa! Kami tidak hanya sepenuhnya menghilangkan kesalahan 502, tetapi juga mampu mengurangi jumlah server hingga dua pertiga, menghemat satu ton uang dan pil untuk sakit kepala bagi para insinyur dan manajer produk.
Pada pertengahan tahun, kami meningkatkan solusi kami, menerbitkannya di GitHub di bawah lisensi MIT, dan menamainya
RoadRunner , menekankan kecepatan dan efisiensinya yang luar biasa.
Bagaimana RoadRunner Dapat Meningkatkan Stack Pengembangan Anda
Penggunaan
RoadRunner memungkinkan kami untuk menggunakan Middleware net / http di sisi Go untuk melakukan verifikasi JWT sebelum permintaan masuk ke PHP, serta untuk memproses WebSockets dan status agregat global di Prometheus.
Berkat RPC bawaan, Anda dapat membuka API perpustakaan Go untuk PHP tanpa menulis pembungkus ekstensi. Lebih penting lagi, RoadRunner dapat menggunakan server baru selain HTTP. Contohnya termasuk menjalankan penangan
AWS Lambda di PHP, membuat
resolusi antrian yang kuat
, dan bahkan menambahkan
gRPC ke aplikasi kami.
Dengan bantuan komunitas PHP dan Go, kami meningkatkan stabilitas solusi, dalam beberapa tes kami meningkatkan kinerja aplikasi hingga 40 kali, meningkatkan alat debugging, mengimplementasikan integrasi dengan kerangka kerja Symfony dan menambahkan dukungan untuk HTTPS, HTTP / 2, plug-in dan PSR-17.
Kesimpulan
Beberapa masih terpikat oleh gagasan usang tentang PHP sebagai bahasa rumit yang lambat, hanya cocok untuk menulis plugin untuk WordPress. Orang-orang ini bahkan dapat mengatakan bahwa PHP memiliki batasan seperti itu: ketika aplikasi menjadi cukup besar, Anda harus memilih bahasa yang lebih "dewasa" dan menulis ulang basis kode yang telah terakumulasi selama bertahun-tahun.
Saya ingin menjawab semua ini: pikirkan lagi. Kami percaya bahwa hanya Anda sendiri yang menetapkan batasan untuk PHP. Anda dapat menghabiskan seluruh hidup Anda beralih dari satu bahasa ke bahasa lain, mencoba menemukan kombinasi sempurna dengan kebutuhan Anda, atau Anda dapat mulai menganggap bahasa sebagai alat. Kelemahan nyata dari bahasa seperti PHP sebenarnya bisa menjadi alasan keberhasilannya. Dan jika Anda menggabungkannya dengan bahasa lain seperti Go, maka Anda akan menciptakan produk yang jauh lebih kuat daripada jika Anda terbatas menggunakan satu bahasa saja.
Setelah bekerja dengan sekelompok Go dan PHP, kita dapat mengatakan bahwa kita mencintai mereka. Kami tidak berencana untuk mengorbankan satu demi yang lain - sebaliknya, kami akan mencari cara untuk mendapatkan lebih banyak manfaat dari tumpukan ganda ini.
UPD: Selamat datang di pencipta RoadRunner dan penulis bersama artikel asli - Lachezis