RoadRunner: Ang PHP ay hindi binuo para mamatay, o Golang to the rescue

RoadRunner: Ang PHP ay hindi binuo para mamatay, o Golang to the rescue

Hello, Habr! Active kami sa Badoo nagtatrabaho sa pagganap ng PHP, dahil mayroon kaming isang medyo malaking sistema sa wikang ito at ang isyu ng pagganap ay isang bagay ng pag-save ng pera. Mahigit sampung taon na ang nakalilipas, lumikha kami ng PHP-FPM para dito, na noong una ay isang set ng mga patch para sa PHP, at kalaunan ay naging bahagi ng opisyal na pamamahagi.

Sa mga nagdaang taon, ang PHP ay gumawa ng mahusay na pag-unlad: ang tagakolekta ng basura ay bumuti, ang antas ng katatagan ay tumaas - ngayon maaari kang sumulat ng mga daemon at mahabang buhay na mga script sa PHP nang walang anumang mga problema. Pinahintulutan nito ang Spiral Scout na pumunta nang higit pa: Ang RoadRunner, hindi tulad ng PHP-FPM, ay hindi naglilinis ng memorya sa pagitan ng mga kahilingan, na nagbibigay ng karagdagang mga benepisyo sa pagganap (bagaman ang diskarteng ito ay nagpapalubha sa proseso ng pag-unlad). Kasalukuyan kaming nag-eeksperimento sa tool na ito, ngunit wala pa kaming anumang mga resulta na ibabahagi. Para maging mas masaya ang paghihintay sa kanila, Naglalathala kami ng pagsasalin ng anunsyo ng RoadRunner mula sa Spiral Scout.

Ang diskarte mula sa artikulo ay malapit sa amin: kapag nilulutas ang aming mga problema, madalas din kaming gumagamit ng kumbinasyon ng PHP at Go, na nakakakuha ng mga benepisyo ng parehong wika at hindi sumusuko sa isa pabor sa isa pa.

Enjoy!

Sa nakalipas na sampung taon, gumawa kami ng mga aplikasyon para sa mga kumpanya mula sa listahan Fortune 500, at para sa mga negosyong may audience na hindi hihigit sa 500 user. Sa lahat ng oras na ito, binuo ng aming mga inhinyero ang backend pangunahin sa PHP. Ngunit dalawang taon na ang nakalipas, may isang bagay na gumawa ng malaking epekto hindi lamang sa pagganap ng aming mga produkto, kundi pati na rin sa kanilang scalability - ipinakilala namin ang Golang (Go) sa aming stack ng teknolohiya.

Halos kaagad, natuklasan namin na pinayagan kami ni Go na bumuo ng mas malalaking application na may hanggang 40x na mas mabilis na performance. Sa pamamagitan nito, nagawa naming palawakin ang mga umiiral nang produkto na nakasulat sa PHP, pinahusay ang mga ito sa pamamagitan ng pagsasama-sama ng mga pakinabang ng parehong wika.

Sasabihin namin sa iyo kung paano nakakatulong ang kumbinasyon ng Go at PHP na malutas ang mga tunay na problema sa pag-unlad at kung paano ito naging isang tool para sa amin na maaaring mag-alis ng ilan sa mga problemang nauugnay sa PHP namamatay na modelo.

Ang iyong Pang-araw-araw na Kapaligiran sa Pag-unlad ng PHP

Bago natin pag-usapan kung paano mo magagamit ang Go para buhayin ang namamatay na modelo ng PHP, tingnan natin ang iyong karaniwang kapaligiran sa pagbuo ng PHP.

Sa karamihan ng mga kaso, pinapatakbo mo ang application gamit ang kumbinasyon ng nginx web server at PHP-FPM server. Ang una ay naghahatid ng mga static na file at nagre-redirect ng mga partikular na kahilingan sa PHP-FPM, at ang PHP-FPM mismo ang nagpapatupad ng PHP code. Marahil ay gumagamit ka ng hindi gaanong sikat na kumbinasyon mula sa Apache at mod_php. Ngunit kahit na ito ay gumagana nang medyo naiiba, ang mga prinsipyo ay pareho.

Tingnan natin kung paano ini-execute ng PHP-FPM ang application code. Kapag dumating ang isang kahilingan, sinisimulan ng PHP-FPM ang proseso ng child PHP at ipinapasa ang mga detalye ng kahilingan bilang bahagi ng estado nito (_GET, _POST, _SERVER, atbp.).

Ang estado ay hindi maaaring magbago sa panahon ng pagpapatupad ng isang PHP script, kaya mayroon lamang isang paraan upang makakuha ng isang bagong set ng input data: sa pamamagitan ng pag-clear sa memorya ng proseso at muling pagsisimula nito.

Ang modelo ng pagpapatupad na ito ay may maraming mga pakinabang. Hindi mo kailangang mag-alala nang husto tungkol sa pagkonsumo ng memorya, ang lahat ng mga proseso ay ganap na nakahiwalay, at kung ang isa sa mga ito ay mamatay, awtomatiko itong muling gagawa nang hindi naaapektuhan ang iba pang mga proseso. Ngunit ang diskarte na ito ay mayroon ding mga disadvantages na lumilitaw kapag sinusubukang i-scale ang application.

Mga disadvantages at inefficiencies ng isang regular na kapaligiran ng PHP

Kung ikaw ay nakikibahagi sa propesyonal na pag-unlad sa PHP, alam mo kung saan magsisimula ng bagong proyekto - sa pamamagitan ng pagpili ng isang balangkas. Binubuo ito ng mga library para sa dependency injection, mga ORM, mga pagsasalin at mga template. At siyempre, lahat ng user input ay maaaring maginhawang ilagay sa isang bagay (Symfony/HttpFoundation o PSR-7). Ang mga framework ay cool!

Ngunit ang lahat ay may sariling presyo. Sa anumang balangkas sa antas ng enterprise, upang maproseso ang isang simpleng kahilingan ng user o ma-access ang isang database, kakailanganin mong mag-load ng hindi bababa sa dose-dosenang mga file, lumikha ng maraming klase at mag-parse ng ilang mga pagsasaayos. Ngunit ang pinakamasamang bagay ay na pagkatapos makumpleto ang bawat gawain kakailanganin mong i-reset ang lahat at magsimulang muli: ang lahat ng code na iyong sinimulan ay magiging walang silbi, sa tulong nito ay hindi ka na magpoproseso ng isa pang kahilingan. Sabihin ito sa sinumang programmer na nagsusulat sa anumang ibang wika, at makikita mo ang pagkalito sa kanyang mukha.

Ang mga inhinyero ng PHP ay gumugol ng maraming taon na naghahanap ng mga paraan upang malutas ang problemang ito, gamit ang matalinong lazy loading techniques, microframeworks, optimized na library, cache, atbp. Ngunit sa huli, kailangan mo pa ring i-reset ang buong application at magsimulang muli, muli at muli. (Tala ng tagasalin: ang problemang ito ay bahagyang malulutas sa pagdating ng preload sa PHP 7.4)

Maaari bang mabuhay ang PHP kasama ang Go ng higit sa isang kahilingan?

Posibleng magsulat ng mga script ng PHP na tatagal nang mas mahaba kaysa sa ilang minuto (hanggang sa mga oras o araw): halimbawa, mga cron task, CSV parsers, queue busters. Lahat sila ay gumagana ayon sa parehong senaryo: kinukuha nila ang isang gawain, isinasagawa ito, at naghihintay para sa susunod. Ang code ay namamalagi sa memorya, na nagse-save ng mahalagang millisecond dahil maraming karagdagang hakbang ang kinakailangan upang mai-load ang framework at application.

Ngunit ang pagbuo ng pangmatagalang script ay hindi ganoon kadali. Ang anumang error ay ganap na pumapatay sa proseso, ang pag-diagnose ng memory leaks ay nababaliw sa iyo, at hindi mo na magagamit ang F5 debugging.

Ang sitwasyon ay bumuti sa paglabas ng PHP 7: lumitaw ang isang maaasahang kolektor ng basura, naging mas madaling pangasiwaan ang mga error, at ang mga kernel extension ay protektado na ngayon mula sa mga tagas. Totoo, kailangan pa rin ng mga inhinyero na maging maingat sa memorya at magkaroon ng kamalayan sa mga isyu ng estado sa code (mayroon bang wika kung saan hindi natin kailangang mag-alala tungkol sa mga bagay na ito?). Gayunpaman, sa PHP 7, mas kaunting mga sorpresa ang naghihintay sa amin.

Posible bang kunin ang modelo ng pagtatrabaho sa mga script ng PHP na matagal nang nabubuhay, iakma ito sa mas walang kabuluhang mga gawain tulad ng pagproseso ng mga kahilingan sa HTTP, at sa gayon ay maalis ang pangangailangan na i-load ang lahat mula sa simula para sa bawat kahilingan?

Upang malutas ang problemang ito, kailangan muna naming magpatupad ng server application na maaaring tumanggap ng mga kahilingan sa HTTP at ipasa ang mga ito nang paisa-isa sa manggagawang PHP nang hindi ito pinapatay sa bawat oras.

Alam namin na maaari kaming magsulat ng isang web server sa purong PHP (PHP-PM) o gamit ang C extension (Swoole). At kahit na ang bawat pamamaraan ay may sariling mga merito, ang parehong mga pagpipilian ay hindi nababagay sa amin - gusto namin ng higit pa. Kailangan namin ng higit pa sa isang web server - umaasa kaming makakuha ng solusyon na makakapagligtas sa amin mula sa mga problemang nauugnay sa "hard start" sa PHP, na sa parehong oras ay madaling iakma at mapalawak para sa mga partikular na application. Ibig sabihin, kailangan namin ng application server.

Maaari bang tumulong si Go dito? Alam namin na maaari ito dahil ang wika ay nag-compile ng mga aplikasyon sa iisang binary; ito ay cross-platform; gumagamit ng sarili nitong, napaka-eleganteng, parallel processing model (concurrency) at library para sa pagtatrabaho sa HTTP; at sa wakas, libu-libong open-source na library at integration ang magiging available sa amin.

Mga kahirapan sa pagsasama-sama ng dalawang programming language

Ang unang hakbang ay upang matukoy kung paano makipag-ugnayan ang dalawa o higit pang mga application sa isa't isa.

Halimbawa, ang paggamit kahanga-hangang aklatan Maaaring ipatupad ni Alex Palaestras ang pagbabahagi ng memorya sa pagitan ng mga proseso ng PHP at Go (katulad ng mod_php sa Apache). Ngunit ang library na ito ay may mga tampok na naglilimita sa paggamit nito para sa paglutas ng aming problema.

Nagpasya kaming gumamit ng isa pang, mas karaniwan, na diskarte: upang bumuo ng pakikipag-ugnayan sa pagitan ng mga proseso sa pamamagitan ng mga socket/pipeline. Napatunayan ng diskarteng ito ang pagiging maaasahan nito sa nakalipas na mga dekada at mahusay na na-optimize sa antas ng operating system.

Upang magsimula, lumikha kami ng isang simpleng binary protocol para sa pagpapalitan ng data sa pagitan ng mga proseso at paghawak ng mga error sa paghahatid. Sa pinakasimpleng anyo nito, ang ganitong uri ng protocol ay katulad ng netstring с nakapirming laki ng packet header (sa aming kaso 17 bytes), na naglalaman ng impormasyon tungkol sa uri ng packet, laki nito at isang binary mask upang suriin ang integridad ng data.

Sa panig ng PHP na ginamit namin pack function, at sa gilid ng Go - isang library encoding/binary.

Tila sa amin na ang isang protocol ay hindi sapat - kaya nagdagdag kami ng kakayahang tumawag Go services net/rpc nang direkta mula sa PHP. Malaki ang naitulong nito sa amin sa paglaon sa pag-unlad, dahil madali naming maisasama ang mga aklatan ng Go sa mga PHP application. Ang resulta ng gawaing ito ay makikita, halimbawa, sa aming iba pang open-source na produkto bangin.

Pamamahagi ng mga gawain sa maraming manggagawa sa PHP

Pagkatapos ipatupad ang mekanismo ng pakikipag-ugnayan, nagsimula kaming mag-isip tungkol sa kung paano pinakamabisang ilipat ang mga gawain sa mga proseso ng PHP. Kapag dumating ang isang gawain, ang application server ay dapat pumili ng isang libreng manggagawa upang makumpleto ito. Kung ang isang manggagawa/proseso ay magwawakas nang may error o "namatay," aalisin namin ito at gagawa kami ng bago upang palitan ito. At kung matagumpay na nakumpleto ang manggagawa/proseso, ibinabalik namin ito sa grupo ng mga manggagawang magagamit upang magsagawa ng mga gawain.

RoadRunner: Ang PHP ay hindi binuo para mamatay, o Golang to the rescue

Upang mag-imbak ng isang pool ng mga aktibong manggagawa na ginamit namin buffered channel, upang alisin ang mga hindi inaasahang "patay" na manggagawa mula sa pool, nagdagdag kami ng mekanismo para sa mga error sa pagsubaybay at mga estado ng manggagawa.

Bilang resulta, nakatanggap kami ng gumaganang PHP server na may kakayahang magproseso ng anumang mga kahilingang ipinakita sa binary form.

Upang gumana ang aming application bilang isang web server, kailangan naming pumili ng maaasahang pamantayan ng PHP upang kumatawan sa anumang mga papasok na kahilingan sa HTTP. Sa aming kaso kami lang ibahin ang anyo net/http kahilingan mula sa Pumunta sa format PSR-7upang ito ay katugma sa karamihan ng mga PHP framework na magagamit ngayon.

Dahil ang PSR-7 ay itinuturing na hindi nababago (ang ilan ay magsasabi sa teknikal na ito ay hindi), ang mga developer ay kailangang magsulat ng mga application na hindi pangunahing itinuturing ang kahilingan bilang isang pandaigdigang entity. Tamang-tama ito sa konsepto ng pangmatagalang proseso ng PHP. Ang aming huling pagpapatupad, na hindi pa pinangalanan, ay ganito ang hitsura:

RoadRunner: Ang PHP ay hindi binuo para mamatay, o Golang to the rescue

Ipinapakilala ang RoadRunner - mataas na pagganap ng PHP application server

Ang aming unang pagsubok na gawain ay ang API backend, na pana-panahong nakaranas ng mga hindi inaasahang pagsabog ng mga kahilingan (mas madalas kaysa karaniwan). Bagama't sapat ang nginx sa karamihan ng mga kaso, regular kaming nakakaranas ng 502 na mga error dahil hindi namin mabalanse nang mabilis ang system para sa inaasahang pagtaas ng load.

Para palitan ang solusyong ito, inilagay namin ang aming unang PHP/Go application server noong unang bahagi ng 2018. At agad kaming nakakuha ng hindi kapani-paniwalang epekto! Hindi lamang namin ganap na naalis ang 502 error, ngunit nagawa rin naming bawasan ang bilang ng mga server ng dalawang-katlo, na nakakatipid ng maraming pera at sakit ng ulo para sa mga inhinyero at tagapamahala ng produkto.

Sa kalagitnaan ng taon, naperpekto na namin ang aming solusyon, nai-publish ito sa GitHub sa ilalim ng lisensya ng MIT, at tinawag itong RoadRunner, sa gayon ay binibigyang-diin ang hindi kapani-paniwalang bilis at kahusayan nito.

Paano Mapapahusay ng RoadRunner ang Iyong Development Stack

Application RoadRunner pinahintulutan kaming gumamit ng Middleware net/http sa gilid ng Go upang magsagawa ng pag-verify ng JWT bago pa man maabot ng kahilingan ang PHP, gayundin ang pangasiwaan ang WebSockets at global na pagsasama-sama ng estado sa Prometheus.

Salamat sa built-in na RPC, maaari mong buksan ang API ng anumang mga library ng Go para sa PHP nang hindi sumusulat ng mga extension wrapper. Higit sa lahat, magagamit ang RoadRunner para mag-deploy ng mga bagong non-HTTP server. Kasama sa mga halimbawa ang paglulunsad ng mga humahawak sa PHP AWS Lambda, paglikha ng maaasahang queue busters at kahit na pagdaragdag gRPC sa aming mga aplikasyon.

Sa tulong ng mga komunidad ng PHP at Go, pinataas namin ang katatagan ng solusyon, pinataas ang performance ng application nang hanggang 40 beses sa ilang pagsubok, pinahusay na mga tool sa pag-debug, ipinatupad ang pagsasama sa Symfony framework, at nagdagdag ng suporta para sa HTTPS, HTTP/ 2, mga plugin, at PSR-17.

Konklusyon

Ang ilang mga tao ay nahuli pa rin sa hindi napapanahong pagtingin sa PHP bilang isang mabagal, masalimuot na wika na mahusay lamang para sa pagsusulat ng mga plugin ng WordPress. Maaaring sabihin pa ng mga taong ito na may limitasyon ang PHP: kapag lumaki na ang application, kailangan mong pumili ng mas "mature" na wika at muling isulat ang code base na naipon sa loob ng maraming taon.

Sa lahat ng ito ay nais kong sagutin: isipin muli. Naniniwala kami na ikaw lang ang makakapagtakda ng anumang mga paghihigpit para sa PHP. Maaari mong gugulin ang iyong buong buhay sa pagtalon mula sa isang wika patungo sa isa pa, sinusubukang mahanap ang perpektong tugma para sa iyong mga pangangailangan, o maaari mong simulan na isipin ang mga wika bilang mga tool. Ang mga nakikitang mga pagkukulang ng isang wika tulad ng PHP ay maaaring aktwal na mga dahilan para sa tagumpay nito. At kung isasama mo ito sa isa pang wika tulad ng Go, maaari kang lumikha ng mas makapangyarihang mga produkto kaysa sa kung limitado ka sa isang wika lamang.

Dahil nagtrabaho sa kumbinasyon ng Go at PHP, masasabi nating mahal natin sila. Hindi namin planong isakripisyo ang isa para sa isa, ngunit sa halip ay maghanap ng mga paraan upang makakuha ng higit pang halaga mula sa dalawahang stack na ito.

UPD: Tinatanggap namin ang lumikha ng RoadRunner at co-author ng orihinal na artikulo - Lachesis

Pinagmulan: www.habr.com

Magdagdag ng komento