Pengembangan Aplikasi PHP / Go Hibrid Menggunakan RoadRunner

Aplikasi PHP klasik adalah single-threaded, pemuatan yang berat (kecuali tentu saja Anda menulis di mikroframe) dan kematian proses yang tak terhindarkan setelah setiap permintaan ... Aplikasi semacam itu berat dan lambat, tetapi kami dapat memberikannya kehidupan kedua melalui hibridisasi. Untuk mempercepat - menjelekkan dan mengoptimalkan kebocoran memori untuk mencapai kinerja yang lebih baik - kami akan memperkenalkan server aplikasi PHP Golang RoadRunner kami sendiri untuk menambah fleksibilitas - kami menyederhanakan kode PHP, memperluas tumpukan dan berbagi tanggung jawab antara server dan aplikasi. Intinya, kita akan membuat aplikasi kita berfungsi seolah-olah kita sedang menulisnya di Jawa atau bahasa lain.

Berkat hibridisasi, aplikasi yang sebelumnya lambat berhenti menderita dari 502 kesalahan di bawah beban, waktu respons rata-rata untuk permintaan menurun, produktivitas meningkat, dan penyebaran dan perakitan menjadi lebih mudah karena penyatuan aplikasi dan menyingkirkan ikatan yang tidak perlu dalam bentuk nginx + php-fpm.


Anton Titov ( Lachezis ) adalah CTO dan salah satu pendiri SpiralScout LLC dengan 12 tahun pengalaman pengembangan komersial aktif di PHP. Selama beberapa tahun terakhir, ia telah secara aktif menerapkan Golang pada tumpukan pengembangan perusahaan. Anton berbicara tentang satu contoh di PHP Russia 2019 .

Siklus Hidup Aplikasi PHP


Secara skematis, perangkat aplikasi abstrak dengan kerangka kerja tertentu terlihat seperti ini.



Ketika kami mengirim permintaan ke suatu proses, itu terjadi:

  • inisialisasi proyek;
  • memuat pustaka, kerangka kerja, dan ORM yang dibagikan;
  • memuat perpustakaan yang diperlukan untuk proyek tertentu;
  • perutean;
  • permintaan routing ke pengontrol tertentu;
  • generasi respons.

Ini adalah prinsip pengoperasian aplikasi single-threaded klasik dengan titik masuk tunggal, yang setelah setiap eksekusi benar-benar hancur atau menghapus statusnya. Semua kode diturunkan dari memori, pekerja dibersihkan atau hanya mengatur ulang statusnya.

Memuat malas


Cara standar dan mudah untuk mempercepatnya adalah penerapan sistem pemuatan malas atau perpustakaan pemuatan sesuai permintaan.



Dengan memuat Malas, kami hanya meminta kode yang diperlukan.

Saat mengakses controller tertentu, hanya perpustakaan yang diperlukan yang akan dimuat ke dalam memori, diproses, dan kemudian dibongkar. Ini memungkinkan Anda untuk mengurangi waktu respons rata-rata proyek dan sangat memudahkan proses bekerja di server. Dalam semua kerangka kerja yang kami gunakan saat ini, prinsip lazy loading diimplementasikan.

Cache perhitungan sering


Metode ini lebih rumit dan aktif digunakan, misalnya, dalam kerangka kerja Symfony, mesin templat, skema ORM, dan perutean. Ini bukan caching seperti memcached atau Redis untuk data pengguna. Sistem ini menghangatkan bagian kode terlebih dahulu . Pada permintaan pertama, sistem menghasilkan kode atau file cache, dan pada permintaan berikutnya, perhitungan ini, yang diperlukan, misalnya, untuk mengkompilasi template, tidak akan lagi dilakukan.



Caching secara signifikan mempercepat aplikasi , tetapi pada saat yang sama mempersulitnya . Misalnya, ada masalah dengan membatalkan cache dan memperbarui aplikasi. Jangan bingung cache pengguna dengan cache aplikasi - dalam satu, data berubah dari waktu ke waktu, yang lain hanya ketika kode diperbarui.

Meminta pemrosesan


Ketika permintaan diterima dari server PHP-FPM eksternal, titik masuk permintaan dan inisialisasi akan cocok.

Ternyata permintaan klien adalah keadaan proses kami.

Satu-satunya cara untuk mengubah keadaan ini adalah dengan sepenuhnya menghancurkan pekerja dan memulai kembali dengan permintaan baru.



Ini adalah model klasik single-threaded dengan kelebihannya.

  • Semua pekerja di akhir permintaan mati.
  • Kebocoran memori, kondisi balapan, kebuntuan tidak melekat dalam PHP. Anda tidak bisa khawatir tentang itu.
  • Kode sederhana: kita menulis, memproses permintaan, mati, dan melanjutkan.

Di sisi lain, untuk setiap permintaan, kami memuat kerangka sepenuhnya, semua pustaka, melakukan beberapa perhitungan, mengkompilasi ulang templat. Dengan setiap permintaan dalam lingkaran kami menghasilkan banyak manipulasi dan pekerjaan yang tidak perlu.

Cara kerjanya di server


Kemungkinan besar, sekelompok nginx dan PHP akan berfungsi. Nginx akan berfungsi sebagai proksi terbalik: memberikan pengguna bagian dari statika, dan mendelegasikan sebagian dari permintaan kepada manajer proses PHP, PHP-FPM di bawah ini. Sudah manajer mengangkat pekerja terpisah untuk permintaan dan memprosesnya. Setelah itu, pekerja dihancurkan atau dibersihkan. Selanjutnya, seorang pekerja baru diciptakan untuk permintaan berikutnya.



Model seperti itu bekerja dengan stabil - aplikasi ini hampir tidak mungkin untuk dimatikan. Tetapi di bawah beban berat, jumlah pekerjaan untuk menginisialisasi dan menghancurkan pekerja mempengaruhi kinerja sistem, karena bahkan untuk permintaan GET yang sederhana, kita sering harus menarik banyak dependensi dan meningkatkan kembali koneksi basis data.

Mempercepat aplikasi


Bagaimana cara mempercepat aplikasi klasik setelah memperkenalkan cache dan pemuatan Malas? Opsi apa lagi yang ada?

Beralih ke bahasa itu sendiri .

  • Gunakan OPCache. Saya pikir tidak ada yang menjalankan PHP pada produksi tanpa OPCache diaktifkan?
  • Tunggu RFC: Preloading . Ini memungkinkan Anda untuk melakukan preload serangkaian file ke dalam mesin virtual.
  • JIT - secara serius mempercepat aplikasi pada tugas yang terikat CPU. Sayangnya, dengan tugas-tugas yang terkait dengan database, itu tidak akan banyak membantu.

Gunakan alternatif . Misalnya, mesin virtual HHVM dari Facebook. Ini mengeksekusi kode dalam lingkungan yang lebih optimal. Sayangnya, HHVM tidak sepenuhnya kompatibel dengan sintaks PHP. Sebagai alternatif, kompiler kPHP dari VK atau PeachPie, yang sepenuhnya mengubah kode menjadi .NET C #, adalah sebuah alternatif.

Sepenuhnya menulis ulang ke bahasa lain. Ini adalah opsi radikal - singkirkan sepenuhnya pemuatan kode di antara permintaan.

Anda dapat sepenuhnya menyimpan status aplikasi dalam memori , secara aktif menggunakan memori ini untuk bekerja, dan melupakan konsep pekerja yang sekarat dan sepenuhnya menghapus aplikasi di antara permintaan.

Untuk mencapai hal ini, kami memindahkan titik masuk, yang digunakan bersama dengan titik inisialisasi, jauh ke dalam aplikasi.

Mentransfer titik masuk - demonisasi


Ini membuat loop tak terbatas dalam aplikasi: permintaan masuk - jalankan melalui kerangka kerja - menghasilkan respons kepada pengguna. Ini adalah penghematan serius - semua bootstrap, semua inisialisasi kerangka kerja dilakukan hanya sekali, dan kemudian beberapa permintaan diproses oleh aplikasi.



Kami mengadaptasi aplikasi


Menariknya, kita dapat fokus hanya pada pengoptimalan bagian aplikasi yang akan berjalan di runtime : controllers, business logic. Dalam hal ini, Anda dapat meninggalkan model pemuatan Malas. Kami akan mengambil bagian dari proyek bootstrap ke awal - pada saat inisialisasi. Perhitungan awal: perutean, templat, pengaturan, skema ORM akan meningkatkan inisialisasi, tetapi di masa depan mereka akan menghemat waktu pemrosesan untuk satu permintaan spesifik.



Saya tidak merekomendasikan kompilasi template saat mengunduh pekerja, tetapi mengunduh, misalnya, semua konfigurasi berguna.

Bandingkan Model


Bandingkan model iblis (kiri) dan klasik.



Model yang di-demonized sejak saat proses dibuat hingga saat respons dikembalikan ke pengguna membutuhkan waktu lebih lama. Aplikasi klasik dioptimalkan untuk pembuatan, pemrosesan, dan penghancuran yang cepat.

Namun, semua permintaan berikutnya setelah melakukan pemanasan kode jauh lebih cepat. Kerangka kerja, aplikasi, wadah sudah ada dalam memori dan siap menerima permintaan dan merespons dengan cepat.

Masalah model berumur panjang


Terlepas dari kelebihannya, model ini memiliki sejumlah keterbatasan.

Memori bocor. Aplikasi ini terletak di memori untuk waktu yang sangat lama, dan jika Anda menggunakan "kurva" perpustakaan, dependensi yang salah atau keadaan global - memori akan mulai bocor. Pada titik tertentu, kesalahan fatal akan muncul yang akan merusak permintaan pengguna.

Masalahnya diselesaikan dengan dua cara.

  • Tulis kode yang akurat, gunakan perpustakaan yang terbukti.
  • Secara aktif memonitor pekerja. Jika Anda mencurigai bahwa memori bocor di dalam proses, secara proaktif ubahlah ke analog dengan batas bawah, yaitu, hanya ke salinan baru yang belum berhasil mengakumulasi memori yang tidak bersih.

Kebocoran data . Misalnya, jika selama permintaan masuk kami menyimpan pengguna sistem saat ini dalam beberapa variabel global dan lupa untuk mengatur ulang variabel ini setelah permintaan, maka ada kemungkinan bahwa pengguna sistem berikutnya secara tidak sengaja akan mendapatkan akses ke data yang seharusnya tidak ia lihat.

Masalahnya diselesaikan pada tingkat arsitektur aplikasi.

  • Jangan menyimpan pengguna aktif dalam konteks global. Semua data yang khusus untuk konteks permintaan dibuang dan dihapus sebelum permintaan berikutnya.
  • Tangani data sesi dengan cermat. Sesi dalam PHP - dengan pendekatan klasik, ini adalah objek global. Bungkus dengan benar sehingga pada permintaan berikutnya diatur ulang.

Manajemen sumber daya .

  • Pantau koneksi ke basis data. Jika aplikasi hang dalam memori selama satu atau dua bulan, maka koneksi terbuka kemungkinan besar akan ditutup dalam waktu ini: database akan diinstal ulang, reboot, atau firewall akan mengatur ulang koneksi. Pada tingkat kode, pertimbangkan untuk menyambung kembali, atau setelah setiap permintaan, setel ulang koneksi dan naikkan kembali pada permintaan berikutnya.
  • Hindari kunci file berumur panjang. Jika pekerja Anda menulis beberapa informasi ke file, tidak ada masalah. Tetapi jika file ini terbuka dan memiliki kunci di dalamnya, maka tidak ada proses lain di sistem Anda yang akan memiliki akses ke sana sampai kunci dilepaskan.


Jelajahi model yang berumur panjang


Pertimbangkan model pekerja yang berumur panjang - menjelekkan aplikasi - dan mencari cara untuk mengimplementasikannya.

Pendekatan non-blocking


Kami menggunakan PHP asinkron - kami memuat aplikasi satu kali ke dalam memori dan memproses permintaan HTTP yang masuk di dalam aplikasi. Sekarang aplikasi dan server adalah satu proses . Ketika permintaan datang, kami membuat coroutine terpisah atau dalam lingkaran acara kami memberikan janji, memprosesnya dan memberikannya kepada pengguna.



Keuntungan yang tidak dapat disangkal dari pendekatan ini adalah kinerja maksimum. Dimungkinkan juga untuk menggunakan alat yang menarik, misalnya, mengkonfigurasi WebSocket langsung pada aplikasi Anda .

Namun, pendekatan ini secara signifikan meningkatkan kompleksitas pembangunan . Penting untuk menginstal ELDO, ingatlah bahwa tidak semua driver basis data akan didukung, dan pustaka PDO dikecualikan.

Untuk memecahkan masalah dalam hal demonisasi dengan pendekatan non-blocking, Anda dapat menggunakan alat yang terkenal: ReactPHP , amphp dan Swoole - pengembangan yang menarik dalam bentuk ekstensi-C. Alat-alat ini bekerja dengan cepat, mereka memiliki komunitas yang bagus dan dokumentasi yang baik.

Pendekatan pemblokiran


Kami tidak memunculkan coroutine di dalam aplikasi, tetapi melakukannya dari luar.



Kami hanya mengambil beberapa proses aplikasi , seperti yang dilakukan oleh PHP-FPM. Alih-alih mengirimkan permintaan ini dalam bentuk kondisi proses, kami mengirimkannya dari luar dalam bentuk protokol atau pesan.

Kami menulis kode single-threaded yang sama yang kami tahu, kami menggunakan semua perpustakaan yang sama dan PDO yang sama. Semua kerja keras bekerja dengan soket, HTTP, dan alat-alat lain dilakukan di luar aplikasi PHP .

Dari minus: kita harus memantau memori dan ingat bahwa komunikasi antara dua proses yang berbeda tidak gratis , tetapi kita perlu mentransfer data. Ini akan membuat sedikit overhead.

Untuk mengatasi masalah tersebut, sudah ada alat PHP-RM yang ditulis dalam PHP. Di perpustakaan ReactPHP, ia memiliki integrasi dengan beberapa kerangka kerja . Namun, PHP-PM sangat lambat, itu kebocoran memori di tingkat server dan di bawah beban itu tidak menunjukkan pertumbuhan sebanyak PHP-FRM.

Kami menulis server aplikasi kami


Kami menulis server aplikasi kami , yang mirip dengan PHP-RM, tetapi ada lebih banyak fungsi. Apa yang kita inginkan dari server?

Gabungkan dengan kerangka kerja yang ada. Kami ingin memiliki integrasi yang fleksibel dengan hampir semua kerangka kerja di pasar. Saya tidak merasa ingin menulis alat yang hanya berfungsi dalam kasus tertentu.

Berbagai proses untuk server dan aplikasi . Kemungkinan reboot panas, sehingga ketika berkembang secara lokal, tekan F5 dan lihat kode baru yang diperbarui, serta dapat mengembangkannya secara individual.

Kecepatan dan stabilitas tinggi . Namun, kami sedang menulis server HTTP.

Mudah diperpanjang . Kami ingin menggunakan server tidak hanya sebagai HTTP-Server, tetapi juga untuk skenario individual seperti server antrian atau server gRPC.

Berfungsi di luar kotak sedapat mungkin: Windows, Linux, ARM CPU.

Kemampuan untuk menulis ekstensi multi-utas yang sangat cepat khusus untuk aplikasi kita.

Seperti yang sudah Anda pahami, kami akan menulis di Golang.

RoadRunner Server


Untuk membuat server PHP, Anda harus menyelesaikan 4 masalah utama:

  • Membangun komunikasi antara Golang dan proses PHP.
  • Manajemen proses: penciptaan, penghancuran, pemantauan pekerja.
  • Menyeimbangkan tugas - distribusi tugas yang efisien kepada pekerja. Karena kami menerapkan sistem yang memblokir pekerja individu untuk tugas masuk khusus tertentu, penting untuk membuat sistem yang akan dengan cepat mengatakan bahwa proses telah selesai bekerja dan siap untuk menerima tugas berikutnya.
  • Tumpukan HTTP - mengirim data permintaan HTTP ke pekerja. Ini adalah tugas sederhana untuk menulis titik masuk di mana pengguna mengirim permintaan, yang diteruskan ke PHP dan dikembalikan.

Varian interaksi antar proses


Pertama, mari kita selesaikan masalah komunikasi antara proses Golang dan PHP. Kami punya beberapa cara.

Embedding: menanamkan penerjemah PHP langsung di Golang. Ini dimungkinkan, tetapi membutuhkan perakitan PHP khusus, konfigurasi yang rumit, dan proses umum untuk server dan PHP. Seperti di go-php , misalnya, di mana penerjemah PHP terintegrasi ke Golang.

Memori Bersama - Penggunaan ruang memori bersama, tempat proses berbagi ruang ini . Butuh kerja keras. Saat bertukar data, Anda harus menyinkronkan keadaan secara manual dan jumlah kesalahan yang mungkin terjadi cukup besar. Memori Bersama juga tergantung pada OS.

Menulis protokol transport Anda - Goridge


Kami melewati jalur sederhana yang digunakan di hampir semua solusi pada sistem Linux - kami menggunakan protokol transport. Ini ditulis di atas PIPA standar dan SOCKET UNIX / TCP .

Ini memiliki kemampuan untuk mentransfer data di kedua arah, mendeteksi kesalahan, dan juga menandai permintaan dan menempatkan header pada mereka. Nuansa penting bagi kami adalah kemampuan untuk mengimplementasikan protokol tanpa ketergantungan baik di sisi PHP dan Golang - tanpa ekstensi-C dalam bahasa murni.

Seperti halnya protokol apa pun, yayasan adalah paket data. Dalam kasus kami, paket memiliki header tetap sebesar 17 byte.



Byte pertama dialokasikan untuk menentukan jenis paket. Ini bisa berupa aliran atau bendera yang menunjukkan jenis serialisasi data. Kemudian dua kali kami mengemas ukuran data ke Little Endian dan Big Endian. Kami menggunakan warisan ini untuk mendeteksi kesalahan transmisi. Jika kita melihat bahwa ukuran data yang dikemas dalam dua pesanan yang berbeda tidak cocok, kemungkinan besar kesalahan transfer data telah terjadi. Kemudian data ditransmisikan.



Dalam versi ketiga paket, kami akan menghilangkan warisan seperti itu, memperkenalkan pendekatan yang lebih klasik dengan checksum, dan juga menambahkan kemampuan untuk menggunakan protokol ini dengan proses PHP asinkron.

Untuk mengimplementasikan protokol di Golang dan PHP, kami menggunakan alat standar.

Pada Golang: pengkodean / perpustakaan biner dan perpustakaan io dan bersih untuk bekerja dengan pipa standar dan soket UNIX / TCP.

Dalam PHP: fungsi yang biasa digunakan untuk bekerja dengan paket data biner / membongkar dan ekstensi stream dan soket untuk pipa dan soket.

Efek samping yang menarik muncul selama implementasi. Kami mengintegrasikannya dengan pustaka Golang net / rpc standar, yang memungkinkan kami untuk memanggil kode layanan dari Golang langsung di aplikasi.

Kami menulis layanan:

//  sample type  struct{} // Hi returns greeting message. func (a *App) Hi(name string, r *string) error { *r = fmt.Sprintf("ll, %s!", name) return nil } 

Dengan sejumlah kecil kode, kami menyebutnya dari aplikasi:

 <?php use Spiral\Goridge; require "vendor/autoload.php"; $rpc = new Goridge\RPC( new Goridge\SocketRelay("127.0.0.1", 6001) ); echo $rpc->call("App.Hi", "Antony"); 

Manajer Proses PHP


Bagian selanjutnya dari server adalah manajemen pekerja PHP.


Pekerja adalah proses PHP yang selalu kami amati dari Golang. Kami mengumpulkan log kesalahannya dalam file STDERR, berkomunikasi dengan pekerja melalui protokol transport Goridge, dan mengumpulkan statistik tentang konsumsi memori, pelaksanaan tugas, dan pemblokiran.

Implementasinya sederhana - ini adalah fungsi standar os / exec, runtime, sync, atomic. Untuk menciptakan pekerja kami menggunakan Worker Factory .


Mengapa Pabrik Pekerja? Karena kami ingin berkomunikasi baik pada pipa standar maupun pada soket. Dalam hal ini, proses inisialisasi sedikit berbeda. Saat membuat pekerja yang berkomunikasi melalui pipa, kita dapat membuatnya segera dan mengirim data secara langsung. Dalam hal soket, Anda perlu membuat pekerja, tunggu sampai mencapai sistem, buat jabat tangan PID, dan baru kemudian terus bekerja.

Penyeimbang tugas


Bagian ketiga dari server adalah yang paling penting untuk kinerja.

Untuk implementasi, kami menggunakan fungsionalitas Golang standar - saluran buffered . Secara khusus, kami membuat beberapa pekerja dan menempatkan mereka di saluran ini sebagai tumpukan LIFO.

Setelah menerima tugas dari pengguna, kami mengirim permintaan ke tumpukan LIFO dan meminta pekerja gratis pertama yang dikeluarkan. Jika pekerja tidak dapat dialokasikan untuk waktu tertentu, maka pengguna menerima kesalahan dari jenis "Kesalahan Timeout". Jika pekerja dialokasikan - itu didapat dari tumpukan, diblokir, setelah itu menerima tugas dari pengguna.

Setelah tugas diproses, respons dikembalikan ke pengguna, dan pekerja berdiri di ujung tumpukan. Dia siap untuk melakukan tugas berikutnya lagi.

Jika kesalahan terjadi, maka pengguna akan menerima kesalahan, karena pekerja akan dimusnahkan. Kami meminta Worker Pool dan Worker Factory untuk membuat proses yang identik dan menggantinya di tumpukan. Ini memungkinkan sistem untuk bekerja bahkan jika terjadi kesalahan fatal dengan hanya menciptakan kembali pekerja dengan analogi dengan PHP-FPM.


Akibatnya, ternyata menerapkan sistem kecil yang bekerja sangat cepat - 200 ns untuk alokasi pekerja . Itu dapat bekerja bahkan jika terjadi kesalahan fatal. Setiap pekerja pada satu titik dalam proses waktu hanya satu tugas, yang memungkinkan kita untuk menggunakan pendekatan pemblokiran klasik .

Pemantauan proaktif


Bagian terpisah dari manajer proses dan penyeimbang tugas adalah sistem pemantauan proaktif.


Ini adalah sistem yang pernah melakukan jajak pendapat pekerja dan memonitor indikator: yang terlihat pada berapa banyak memori yang mereka konsumsi, berapa banyak mereka, apakah IDLE. Selain pelacakan, sistem memonitor kebocoran memori. Jika pekerja melebihi batas tertentu, kita akan melihatnya dan dengan hati-hati melepaskannya dari sistem sebelum terjadi kebocoran fatal.

Tumpukan HTTP


Bagian terakhir dan sederhana.

Bagaimana penerapannya:

  • memunculkan titik HTTP di sisi Golang;
  • kami menerima permintaan;
  • dikonversi ke format PSR-7;
  • kirim permintaan ke pekerja bebas pertama;
  • Buka paket permintaan ke objek PSR-7;
  • kami memproses;
  • kami menghasilkan jawabannya.

Untuk implementasi, kami menggunakan perpustakaan standar Golang NET / HTTP . Ini adalah perpustakaan terkenal dengan banyak ekstensi. Mampu bekerja baik melalui HTTPS dan melalui protokol HTTP / 2.

Di sisi PHP, kami menggunakan standar PSR-7 . Ini adalah kerangka kerja independen dengan banyak ekstensi dan Middlewares. PSR-7 tidak dapat diubah dalam desain , yang sangat cocok dengan konsep aplikasi yang berumur panjang dan menghindari kesalahan permintaan global.

Kedua struktur di Golang dan PSR-7 sama, yang secara signifikan menghemat waktu untuk memetakan permintaan dari satu bahasa ke bahasa lain.

Untuk memulai server membutuhkan ikatan minimum :

 http: address: 0.0.0.0:8080 workers: command: "php psr-worker.php" pool: numWorkers: 4 

Selain itu, dari versi 1.3.0 bagian terakhir dari konfigurasi dapat dihilangkan.

Unduh file biner server, letakkan di wadah Docker atau di folder proyek. Atau, secara global kita menulis file konfigurasi kecil yang menjelaskan pod mana yang akan kita dengarkan, pekerja mana yang merupakan titik masuk, dan berapa banyak yang dibutuhkan.

Di sisi PHP, kami menulis loop utama yang menerima permintaan PSR-7, memprosesnya, dan mengembalikan respons atau kesalahan kembali ke server.

 while ($req = $psr7->acceptRequest()) { try { $resp = new \Zend\Diactoros\Response(); $resp->getBody()->write("hello world"); $psr7->respond($resp); } catch (\Throwable $e) { $psr7->getWorker()->error((string)$e); } } 

Majelis Untuk mengimplementasikan server, kami memilih arsitektur dengan pendekatan komponen. Ini memungkinkan untuk merakit server untuk kebutuhan proyek, menambah atau menghapus setiap bagian tergantung pada persyaratan aplikasi.

 func main() { rr.Container.Register(env.ID, &env.Service{}) rr.Container.Register(rpc.ID, &rpc.Service{}) rr.Container.Register(http.ID, &http.Service{}) rr.Container.Register(static.ID, &static.Service{}) rr.Container.Register(limit.ID, &limit.Service{} // you can register additional commands using cmd.CLI rr.Execute() } 

Gunakan kasing


Pertimbangkan opsi untuk menggunakan server dan memodifikasi struktur. Untuk memulai, pertimbangkan jalur pipa klasik - server berfungsi dengan permintaan.

Modularitas


Server menerima permintaan ke titik HTTP dan meneruskannya melalui seperangkat Middleware, yang ditulis dalam Golang. Permintaan yang masuk dikonversi ke tugas yang dimengerti pekerja. Server memberikan tugas kepada pekerja dan mengembalikannya.



Pada saat yang sama, pekerja, menggunakan protokol Goridge, berkomunikasi dengan server, memonitor statusnya dan mentransfer data ke sana.

Middleware on Golang: Otorisasi


Ini adalah hal pertama yang harus dilakukan. Dalam aplikasi kami, kami menulis Middleware untuk memberi otorisasi kepada pengguna dengan token JWT . Middleware ditulis dengan cara yang sama untuk semua jenis otorisasi lainnya. Implementasi yang sangat dangkal dan sederhana adalah menulis Rate-Limiter atau Circuit-Breaker.



Otorisasi cepat . Jika permintaan tidak valid - jangan kirimkan ke aplikasi PHP dan jangan buang sumber daya untuk memproses tugas yang tidak berguna.

Pemantauan


Kasus penggunaan kedua. Kami dapat mengintegrasikan sistem pemantauan langsung ke Golang Middleware. Misalnya, Prometheus, untuk mengumpulkan statistik tentang kecepatan poin respons, jumlah kesalahan.



Anda juga dapat menggabungkan pemantauan dengan metrik khusus aplikasi (tersedia sebagai standar dengan 1.4.5). Misalnya, kami dapat mengirim jumlah permintaan ke database atau jumlah permintaan spesifik yang diproses ke server Golang, dan kemudian ke Prometheus.

Pelacakan dan Logging Terdistribusi


Kami menulis Middleware dengan manajer proses. Secara khusus, kita dapat terhubung ke sistem realtime untuk memantau log dan mengumpulkan semua log dalam satu database pusat , yang berguna saat menulis aplikasi terdistribusi.



Kami juga dapat menandai permintaan , memberikannya ID khusus dan meneruskan ID ini ke semua layanan hilir atau sistem komunikasi di antara mereka. Sebagai hasilnya, kita dapat membuat jejak terdistribusi dan melihat bagaimana log aplikasi berjalan.

Rekam riwayat kueri Anda


Ini adalah modul kecil yang mencatat semua permintaan yang masuk dan menyimpannya dalam database eksternal. Modul ini memungkinkan Anda untuk membuat permintaan replay di proyek dan menerapkan sistem pengujian otomatis, sistem pengujian beban, atau hanya memeriksa operasi API.



Bagaimana kami mengimplementasikan modul?

Kami memproses sebagian permintaan untuk Golang . Kami menulis Middleware di Golang dan kami dapat mengirim sebagian permintaan ke Handler, yang juga ditulis dalam Golang. Jika beberapa titik dalam aplikasi mengkhawatirkan dalam hal kinerja, kami menulis ulang ke Golang dan menyeret tumpukan dari satu bahasa ke bahasa lain.



Kami sedang menulis server WebSocket . Menerapkan server WebSocket atau server push notification menjadi tugas yang sepele.

  • Layanan golang di tingkat server.
  • Untuk komunikasi, kami menggunakan Goridge.
  • Lapisan layanan tipis dalam PHP.
  • Kami menerapkan server pemberitahuan.

Kami menerima permintaan dan meningkatkan koneksi WebSocket. Jika aplikasi perlu mengirim semacam pemberitahuan kepada pengguna, itu meluncurkan pesan ini melalui protokol RPC ke server WebSocket.



Kelola lingkungan PHP Anda. Saat membuat Worker Pool, RoadRunner memiliki kontrol penuh atas keadaan variabel lingkungan dan memungkinkan Anda untuk mengubahnya sesuka Anda. Jika kita menulis aplikasi terdistribusi besar, kita dapat menggunakan satu sumber data konfigurasi dan menghubungkannya sebagai sistem untuk mengonfigurasi lingkungan. Jika kami meningkatkan satu set layanan, semua layanan ini akan mengetuk satu sistem tunggal, mengkonfigurasi dan kemudian bekerja. Ini dapat sangat menyederhanakan penyebaran, serta menyingkirkan file .env.



Menariknya, variabel env yang tersedia di dalam pekerja tidak bersifat global dalam sistem. Ini sedikit meningkatkan keamanan wadah.

Integrasi perpustakaan Golang dalam PHP


Kami menggunakan opsi ini di situs resmi RoadRunner . Ini adalah integrasi dari database yang hampir lengkap dengan pencarian teks lengkap BleveSearch di dalam server.



Kami mengindeks halaman dokumentasi: kami menempatkannya di Bolt DB, setelah itu kami melakukan pencarian teks lengkap tanpa database nyata seperti MySQL, dan tanpa cluster pencarian seperti Elasticsearch. Hasilnya adalah proyek kecil di mana beberapa fungsi dalam PHP, tetapi pencarian di Golang.

Menerapkan Fungsi Lambda


Anda dapat melangkah lebih jauh dan sepenuhnya menghilangkan lapisan HTTP. Dalam hal ini, mengimplementasikan, misalnya, fungsi Lambda adalah tugas yang sederhana.



Untuk implementasi, kami menggunakan runtime standar AWS untuk fungsi Lambda. Kami menulis penjilidan kecil, sepenuhnya memotong server HTTP dan mengirim data dalam format biner ke para pekerja. Kami juga memiliki akses ke pengaturan lingkungan, yang memungkinkan kami untuk menulis fungsi yang dikonfigurasikan langsung dari panel admin Amazon.

Pekerja berada dalam memori selama seluruh proses, dan fungsi Lambda setelah permintaan awal tetap tersimpan dalam memori selama 15 menit. Pada saat ini, kode tidak memuat dan merespons dengan cepat. Dalam pengujian sintetik, kami menerima hingga 0,5 ms per satu permintaan masuk .

gRPC untuk PHP


Opsi yang lebih sulit adalah mengganti lapisan HTTP dengan lapisan gRPC. Paket ini tersedia di GitHub .


Kami dapat sepenuhnya mem-proksi semua permintaan Protobuf yang masuk ke aplikasi PHP bawahan, di sana permintaan itu dapat dibuka, diproses, dan dijawab kembali. Kita dapat menulis kode baik dalam PHP maupun di Golang, menggabungkan dan mentransfer fungsionalitas dari satu tumpukan ke tumpukan lainnya. Layanan ini mendukung Middleware. Baik aplikasi mandiri dan bersama dengan HTTP dapat bekerja.

Server antrian


Opsi terakhir dan paling menarik adalah penerapan server antrian .


Di sisi PHP, semua yang kita lakukan adalah mendapatkan muatan biner, membukanya, melakukan pekerjaannya, dan memberi tahu server tentang keberhasilannya. Di sisi Golang, kami sepenuhnya terlibat dalam mengelola koneksi dengan broker. Itu bisa RabbitMQ, Amazon SQS atau Beanstalk.

Di sisi Golang, kami menerapkan " shutdown anggun" pekerja. Kita dapat dengan indah menunggu implementasi "koneksi tahan lama" - jika koneksi dengan broker terputus, server menunggu sebentar menggunakan "strategi back-off", itu mengangkat koneksi dan aplikasi bahkan tidak menyadarinya.

Kami dapat memproses permintaan ini dalam PHP dan Golang, dan mengantri di kedua sisi:

  • dari PHP melalui protokol Goridge Goridge RPC;
  • dari Golang - berkomunikasi dengan perpustakaan SDK.

Jika payload jatuh, maka tidak seluruh Konsumen jatuh, tetapi hanya satu proses terpisah. Sistem segera mengangkatnya, tugas dikirim ke pekerja berikutnya. Ini memungkinkan Anda untuk melakukan tugas tanpa henti.

Kami mengimplementasikan salah satu broker secara langsung di memori server dan menggunakan fungsi Golang. Ini memungkinkan kita untuk menulis aplikasi menggunakan antrian sebelum memilih tumpukan akhir. Kami mengangkat aplikasi secara lokal, memulainya, dan kami memiliki antrian yang berfungsi di memori dan berperilaku seperti mereka akan berperilaku pada RabbitMQ, Amazon SQS atau Beanstalk.

Saat menggunakan dua bahasa dalam bundel hibrid semacam itu, perlu diingat bagaimana memisahkannya.

Pisahkan domain domain


Golang adalah bahasa multi-utas dan cepat yang cocok untuk menulis logika infrastruktur dan pemantauan pengguna dan logika otorisasi.

Ini juga berguna untuk mengimplementasikan driver khusus untuk mengakses sumber data - ini adalah antrian, misalnya, Kafka, Cassandra.

PHP adalah bahasa yang bagus untuk menulis logika bisnis.

Ini adalah sistem yang baik untuk rendering HTML, ORM dan bekerja dengan database.

Perbandingan alat


Beberapa bulan yang lalu di Habré membandingkan PHP-FPM, PHP-PM, React-PHP, Roadrunner dan alat-alat lainnya. Benchmark diadakan pada proyek dengan Symfony 4 nyata.

RoadRunner di bawah beban menunjukkan hasil yang baik dan unggul dari semua server. Dibandingkan dengan PHP-FPM, kinerjanya 6-8 kali lebih tinggi.


Dalam tolok ukur yang sama, RoadRunner tidak kehilangan satu permintaan, semuanya 100% berhasil. Sayangnya, React-PHP kehilangan 8-9 permintaan karena banyak - ini tidak dapat diterima. Kami ingin server tidak crash dan berfungsi secara stabil.


Sejak publikasi RoadRunner dalam akses publik di GitHub, kami telah menerima lebih dari 30.000 instalasi. Komunitas telah membantu kami menulis serangkaian ekstensi, perbaikan, dan yakin bahwa solusi tersebut memiliki hak untuk hidup.

RoadRunner bagus jika Anda ingin mempercepat aplikasi secara signifikan, tetapi belum siap untuk beralih ke PHP asinkron . Ini adalah kompromi yang akan membutuhkan sejumlah upaya, tetapi tidak sepenting menulis ulang dasar kode secara lengkap.

Ambil RoadRunner jika Anda ingin lebih mengontrol siklus hidup PHP , jika tidak ada cukup kemampuan PHP, misalnya, untuk sistem antrian atau Kafka, dan ketika pustaka Golang populer Anda memecahkan masalah Anda, yang tidak ada dalam PHP, dan menulis membutuhkan waktu, yang Anda tidak punya keduanya.

Ringkasan


Apa yang kami dapatkan dengan menulis server ini dan menggunakannya dalam infrastruktur produksi kami.

  • Mereka meningkatkan kecepatan reaksi poin aplikasi sebanyak 4 kali dibandingkan dengan PHP-FPM.
  • Benar-benar menyingkirkan 502 kesalahan di bawah pemuatan . Pada beban puncak, server hanya menunggu sedikit lebih lama dan merespons seolah-olah tidak ada beban.
  • Setelah mengoptimalkan kebocoran memori, pekerja bertahan dalam memori hingga 2 bulan . Ini membantu saat menulis aplikasi terdistribusi, karena semua permintaan antar layanan sudah di-cache di tingkat soket.
  • Kami menggunakan Keep-Alive. Ini secara signifikan mempercepat komunikasi antara sistem terdistribusi.
  • Di dalam infrastruktur nyata, kami meletakkan semuanya di Docker Alpine di Kubernetes . Sistem penyebaran dan pembangunan proyek sekarang lebih mudah. Yang diperlukan hanyalah membangun RoadRunner custom untuk proyek tersebut, memasukkannya ke dalam proyek Docker, mengisi gambar Docker, dan kemudian dengan tenang mengunggah pod kami ke Kubernetes.
  • Menurut waktu aktual dari salah satu proyek ke titik-titik individual yang tidak memiliki akses ke database, waktu respons rata - rata adalah 0,33 ms .

Konferensi profesional berikutnya untuk pengembang PHP, PHP Russia, hanya tahun depan. Untuk saat ini, kami menawarkan yang berikut:

  • Perhatikan GolangConf jika Anda tertarik pada bagian Go dan ingin tahu lebih detail atau dengar argumen yang mendukung pengalihan ke bahasa ini. Jika Anda siap untuk membagikan pengalaman Anda, silakan kirim abstrak .
  • Ambil bagian dalam HighLoad ++ di Moskow, jika semuanya penting bagi Anda yang terkait dengan kinerja tinggi, kirim laporan sebelum 7 September, atau pesan tiket.
  • Berlangganan buletin dan saluran telegram untuk menerima undangan ke PHP Russia 2020 lebih awal dari yang lain.

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


All Articles