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

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

Hello, Habr! Kami aktif di Badoo bekerja pada prestasi PHP, kerana kita mempunyai sistem yang agak besar dalam bahasa ini dan isu prestasi adalah soal penjimatan wang. Lebih daripada sepuluh tahun yang lalu, kami mencipta PHP-FPM untuk ini, yang pada mulanya merupakan satu set patch untuk PHP, dan kemudiannya menjadi sebahagian daripada pengedaran rasmi.

Dalam beberapa tahun kebelakangan ini, PHP telah mencapai kemajuan yang besar: pengumpul sampah telah bertambah baik, tahap kestabilan telah meningkat - hari ini anda boleh menulis daemon dan skrip tahan lama dalam PHP tanpa sebarang masalah. Ini membolehkan Spiral Scout pergi lebih jauh: RoadRunner, tidak seperti PHP-FPM, tidak membersihkan memori antara permintaan, yang memberikan faedah prestasi tambahan (walaupun pendekatan ini merumitkan proses pembangunan). Kami sedang bereksperimen dengan alat ini, tetapi kami belum mempunyai sebarang hasil untuk dikongsi. Untuk menjadikannya lebih seronok menunggu mereka, Kami sedang menerbitkan terjemahan pengumuman RoadRunner daripada Spiral Scout.

Pendekatan dari artikel itu hampir kepada kami: apabila menyelesaikan masalah kami, kami juga paling kerap menggunakan gabungan PHP dan Go, mendapat manfaat kedua-dua bahasa dan tidak melepaskan satu demi satu yang lain.

Bergembiralah dengan!

Sepanjang sepuluh tahun yang lalu, kami telah mencipta permohonan untuk syarikat daripada senarai Fortune 500, dan untuk perniagaan yang mempunyai khalayak tidak lebih daripada 500 pengguna. Selama ini, jurutera kami membangunkan bahagian belakang terutamanya dalam PHP. Tetapi dua tahun lalu, sesuatu memberi impak yang besar bukan sahaja pada prestasi produk kami, tetapi juga pada kebolehskalaan mereka - kami memperkenalkan Golang (Go) kepada timbunan teknologi kami.

Hampir serta-merta, kami mendapati bahawa Go membenarkan kami membina aplikasi yang lebih besar dengan prestasi sehingga 40x lebih pantas. Dengan itu, kami dapat mengembangkan produk sedia ada yang ditulis dalam PHP, menambah baiknya dengan menggabungkan kelebihan kedua-dua bahasa.

Kami akan memberitahu anda bagaimana gabungan Go dan PHP membantu menyelesaikan masalah pembangunan sebenar dan bagaimana ia telah bertukar menjadi alat untuk kami yang boleh menghapuskan beberapa masalah yang berkaitan dengan Model mati PHP.

Persekitaran Pembangunan PHP Harian Anda

Sebelum kita bercakap tentang cara anda boleh menggunakan Go untuk menghidupkan semula model PHP yang hampir mati, mari kita lihat persekitaran pembangunan PHP standard anda.

Dalam kebanyakan kes, anda menjalankan aplikasi menggunakan gabungan pelayan web nginx dan pelayan PHP-FPM. Yang pertama menyajikan fail statik dan mengubah hala permintaan khusus kepada PHP-FPM, dan PHP-FPM sendiri melaksanakan kod PHP. Mungkin anda menggunakan gabungan yang kurang popular dari Apache dan mod_php. Tetapi walaupun ia berfungsi sedikit berbeza, prinsipnya adalah sama.

Mari lihat bagaimana PHP-FPM melaksanakan kod aplikasi. Apabila permintaan tiba, PHP-FPM memulakan proses PHP anak dan menyerahkan butiran permintaan sebagai sebahagian daripada keadaannya (_GET, _POST, _SERVER, dll.).

Keadaan tidak boleh berubah semasa pelaksanaan skrip PHP, jadi hanya ada satu cara untuk mendapatkan set data input baharu: dengan mengosongkan memori proses dan memulakannya semula.

Model pelaksanaan ini mempunyai banyak kelebihan. Anda tidak perlu terlalu risau tentang penggunaan memori, semua proses diasingkan sepenuhnya, dan jika salah satu daripadanya mati, ia akan dicipta semula secara automatik tanpa menjejaskan proses yang lain. Tetapi pendekatan ini juga mempunyai kelemahan yang muncul apabila cuba menskalakan aplikasi.

Kelemahan dan ketidakcekapan persekitaran PHP biasa

Jika anda terlibat dalam pembangunan profesional dalam PHP, maka anda tahu di mana untuk memulakan projek baharu - dengan memilih rangka kerja. Ia terdiri daripada perpustakaan untuk suntikan pergantungan, ORM, terjemahan dan templat. Dan sudah tentu, semua input pengguna boleh dimasukkan dengan mudah ke dalam satu objek (Symfony/HttpFoundation atau PSR-7). Rangka kerja adalah hebat!

Tetapi semuanya ada harganya. Dalam mana-mana rangka kerja peringkat perusahaan, untuk memproses permintaan pengguna yang mudah atau mengakses pangkalan data, anda perlu memuatkan sekurang-kurangnya berdozen fail, mencipta banyak kelas dan menghuraikan beberapa konfigurasi. Tetapi perkara yang paling teruk ialah selepas menyelesaikan setiap tugas, anda perlu menetapkan semula segala-galanya dan mula semula: semua kod yang baru anda mulakan menjadi tidak berguna, dengan bantuannya anda tidak akan memproses permintaan lain lagi. Beritahu ini kepada mana-mana pengaturcara yang menulis dalam bahasa lain, dan anda akan melihat kebingungan di wajahnya.

Jurutera PHP telah menghabiskan masa bertahun-tahun mencari cara untuk menyelesaikan masalah ini, menggunakan teknik pemuatan malas yang bijak, kerangka kerja mikro, perpustakaan yang dioptimumkan, cache, dll. Tetapi pada akhirnya, anda masih perlu menetapkan semula keseluruhan aplikasi dan mulakan semula, lagi dan lagi. (Nota penterjemah: masalah ini sebahagiannya akan diselesaikan dengan kemunculan Pramuat dalam PHP 7.4)

Bolehkah PHP dengan Go bertahan lebih daripada satu permintaan?

Anda boleh menulis skrip PHP yang akan bertahan lebih lama daripada beberapa minit (sehingga jam atau hari): contohnya, tugas cron, penghurai CSV, penghapus baris gilir. Mereka semua bekerja mengikut senario yang sama: mereka mendapatkan semula tugas, melaksanakannya, dan menunggu tugasan seterusnya. Kod itu berada dalam ingatan, menjimatkan milisaat berharga kerana banyak langkah tambahan diperlukan untuk memuatkan rangka kerja dan aplikasi.

Tetapi membangunkan skrip jangka panjang tidak begitu mudah. Sebarang ralat membunuh proses sepenuhnya, mendiagnosis kebocoran memori membuatkan anda gila, dan anda tidak lagi boleh menggunakan penyahpepijatan F5.

Keadaan telah bertambah baik dengan keluaran PHP 7: pengumpul sampah yang boleh dipercayai telah muncul, ia menjadi lebih mudah untuk menangani ralat, dan sambungan kernel kini dilindungi daripada kebocoran. Benar, jurutera masih perlu berhati-hati dengan ingatan dan menyedari isu keadaan dalam kod (adakah bahasa di mana kita tidak perlu risau tentang perkara ini?). Namun, dalam PHP 7, lebih sedikit kejutan menanti kami.

Adakah mungkin untuk mengambil model bekerja dengan skrip PHP tahan lama, menyesuaikannya dengan tugas yang lebih remeh seperti memproses permintaan HTTP, dan dengan itu menghapuskan keperluan untuk memuatkan segala-galanya dari awal untuk setiap permintaan?

Untuk menyelesaikan masalah ini, kami mula-mula perlu melaksanakan aplikasi pelayan yang boleh menerima permintaan HTTP dan memajukannya satu demi satu kepada pekerja PHP tanpa membunuhnya setiap kali.

Kami tahu bahawa kami boleh menulis pelayan web dalam PHP tulen (PHP-PM) atau menggunakan sambungan C (Swoole). Dan walaupun setiap kaedah mempunyai kelebihannya sendiri, kedua-dua pilihan tidak sesuai dengan kami - kami mahukan sesuatu yang lebih. Kami memerlukan lebih daripada sekadar pelayan web - kami berharap untuk mendapatkan penyelesaian yang boleh menyelamatkan kami daripada masalah yang berkaitan dengan "permulaan yang sukar" dalam PHP, yang pada masa yang sama boleh disesuaikan dan dikembangkan dengan mudah untuk aplikasi tertentu. Iaitu, kami memerlukan pelayan aplikasi.

Bolehkah Go membantu dengan ini? Kami tahu ia boleh kerana bahasa itu menyusun aplikasi ke dalam binari tunggal; ia adalah platform silang; menggunakan model pemprosesan selari (concurrency) dan perpustakaannya sendiri, sangat elegan untuk bekerja dengan HTTP; dan akhirnya, beribu-ribu perpustakaan dan penyepaduan sumber terbuka akan tersedia kepada kami.

Kesukaran menggabungkan dua bahasa pengaturcaraan

Langkah pertama ialah menentukan cara dua atau lebih aplikasi akan berkomunikasi antara satu sama lain.

Sebagai contoh, menggunakan perpustakaan yang indah Alex Palaestras boleh melaksanakan perkongsian memori antara proses PHP dan Go (serupa dengan mod_php dalam Apache). Tetapi perpustakaan ini mempunyai ciri yang mengehadkan penggunaannya untuk menyelesaikan masalah kami.

Kami memutuskan untuk menggunakan pendekatan lain yang lebih biasa: untuk membina interaksi antara proses melalui soket/talian paip. Pendekatan ini telah membuktikan kebolehpercayaannya sejak beberapa dekad yang lalu dan telah dioptimumkan dengan baik pada peringkat sistem pengendalian.

Sebagai permulaan, kami mencipta protokol binari mudah untuk bertukar-tukar data antara proses dan mengendalikan ralat penghantaran. Dalam bentuk yang paling mudah, jenis protokol ini serupa dengan tali jaring с pengepala paket saiz tetap (dalam kes kami 17 bait), yang mengandungi maklumat tentang jenis paket, saiznya dan topeng binari untuk menyemak integriti data.

Di sebelah PHP yang kami gunakan fungsi pek, dan di sebelah Pergi - perpustakaan pengekodan / binari.

Nampaknya satu protokol tidak mencukupi - jadi kami menambah keupayaan untuk memanggil Go services net/rpc terus daripada PHP. Ini kemudiannya banyak membantu kami dalam pembangunan, kerana kami boleh dengan mudah menyepadukan perpustakaan Go ke dalam aplikasi PHP. Hasil kerja ini boleh dilihat, sebagai contoh, dalam produk sumber terbuka kami yang lain Goridge.

Mengagihkan tugas merentasi berbilang pekerja PHP

Selepas melaksanakan mekanisme interaksi, kami mula berfikir tentang cara memindahkan tugas dengan paling cekap kepada proses PHP. Apabila tugas tiba, pelayan aplikasi mesti memilih pekerja percuma untuk menyelesaikannya. Jika pekerja/proses ditamatkan dengan ralat atau "mati", kami menyingkirkannya dan mencipta yang baharu untuk menggantikannya. Dan jika pekerja/proses telah berjaya diselesaikan, kami mengembalikannya kepada kumpulan pekerja yang tersedia untuk melaksanakan tugas.

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

Untuk menyimpan kumpulan pekerja aktif yang kami gunakan saluran penimbal, untuk mengalih keluar pekerja "mati" yang tidak dijangka daripada kumpulan, kami menambahkan mekanisme untuk menjejak ralat dan keadaan pekerja.

Akibatnya, kami menerima pelayan PHP yang berfungsi yang mampu memproses sebarang permintaan yang dikemukakan dalam bentuk binari.

Untuk membolehkan aplikasi kami berfungsi sebagai pelayan web, kami perlu memilih standard PHP yang boleh dipercayai untuk mewakili sebarang permintaan HTTP yang masuk. Dalam kes kami, kami hanya mengubah net/http permintaan daripada Pergi ke format PSR-7supaya ia serasi dengan kebanyakan rangka kerja PHP yang tersedia hari ini.

Oleh kerana PSR-7 dianggap tidak berubah (sesetengahnya akan mengatakan secara teknikal ia tidak), pembangun perlu menulis aplikasi yang pada asasnya tidak menganggap permintaan itu sebagai entiti global. Ini sesuai dengan konsep proses PHP jangka panjang. Pelaksanaan terakhir kami, yang belum dinamakan, kelihatan seperti ini:

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

Memperkenalkan RoadRunner - pelayan aplikasi PHP berprestasi tinggi

Tugas ujian pertama kami ialah bahagian belakang API, yang secara berkala mengalami letusan permintaan yang tidak dijangka (lebih kerap daripada biasa). Walaupun nginx mencukupi dalam kebanyakan kes, kami kerap menghadapi ralat 502 kerana kami tidak dapat mengimbangi sistem dengan cukup cepat untuk peningkatan beban yang dijangkakan.

Untuk menggantikan penyelesaian ini, kami menggunakan pelayan aplikasi PHP/Go pertama kami pada awal 2018. Dan serta-merta kami mendapat kesan yang luar biasa! Kami bukan sahaja menyingkirkan ralat 502 sepenuhnya, tetapi kami juga dapat mengurangkan bilangan pelayan sebanyak dua pertiga, menjimatkan banyak wang dan sakit kepala untuk jurutera dan pengurus produk.

Menjelang pertengahan tahun, kami telah menyempurnakan penyelesaian kami, menerbitkannya di GitHub di bawah lesen MIT, dan memanggilnya Roadrunner, dengan itu menekankan kelajuan dan kecekapannya yang luar biasa.

Bagaimana RoadRunner Boleh Meningkatkan Timbunan Pembangunan Anda

Permohonan Roadrunner membenarkan kami menggunakan Middleware net/http di sebelah Go untuk melaksanakan pengesahan JWT sebelum permintaan itu mencecah PHP, serta mengendalikan WebSockets dan pengagregatan keadaan global dalam Prometheus.

Terima kasih kepada RPC terbina dalam, anda boleh membuka API mana-mana perpustakaan Go untuk PHP tanpa menulis pembalut sambungan. Lebih penting lagi, RoadRunner boleh digunakan untuk menggunakan pelayan bukan HTTP baharu. Contohnya termasuk pelancaran pengendali dalam PHP AWS Lambda, mencipta penghapus baris gilir yang boleh dipercayai dan juga menambah gRPC kepada aplikasi kami.

Dengan bantuan komuniti PHP dan Go, kami telah meningkatkan kestabilan penyelesaian, meningkatkan prestasi aplikasi sehingga 40 kali ganda dalam beberapa ujian, menambah baik alat penyahpepijatan, melaksanakan penyepaduan dengan rangka kerja Symfony dan menambah sokongan untuk HTTPS, HTTP/ 2, pemalam dan PSR-17.

Kesimpulan

Sesetengah orang masih terperangkap dalam pandangan lapuk PHP sebagai bahasa yang perlahan dan menyusahkan hanya baik untuk menulis pemalam WordPress. Orang-orang ini mungkin mengatakan bahawa PHP mempunyai had: apabila aplikasi menjadi cukup besar, anda perlu memilih bahasa yang lebih "matang" dan menulis semula pangkalan kod yang telah terkumpul selama bertahun-tahun.

Untuk semua ini saya ingin menjawab: fikir semula. Kami percaya bahawa hanya anda boleh menetapkan sebarang sekatan untuk PHP. Anda boleh menghabiskan seluruh hidup anda melompat dari satu bahasa ke bahasa lain, cuba mencari padanan yang sesuai untuk keperluan anda, atau anda boleh mula menganggap bahasa sebagai alat. Kelemahan bahasa seperti PHP yang dilihat sebenarnya boleh menjadi sebab kejayaannya. Dan jika anda menggabungkannya dengan bahasa lain seperti Go, anda boleh mencipta produk yang lebih berkuasa berbanding jika anda terhad kepada satu bahasa sahaja.

Setelah bekerja dengan gabungan Go dan PHP, kami boleh mengatakan bahawa kami menyukainya. Kami tidak bercadang untuk mengorbankan satu untuk yang lain, tetapi mencari cara untuk mendapatkan lebih banyak nilai daripada dwi tindanan ini.

UPD: Kami mengalu-alukan pencipta RoadRunner dan pengarang bersama artikel asal - Lachesis

Sumber: www.habr.com

Tambah komen