Tervehdys, rakas ystävä! Daemonin käynnistäminen uudelleen PHP:ssä menettämättä yhteyksiä siihen js-komentosarjan luominen

). Pilvi on suunniteltu suorittamaan erilaisia ​​PHP-skriptejä aikataulussa tai API:n kautta. Yleensä nämä komentosarjat käsittelevät jonoja, ja kuorma "hajautuu" noin 100 palvelimelle. Aiemmin keskityimme siihen, miten ohjauslogiikka on toteutettu, mikä vastaa kuormituksen tasaisesta jakautumisesta niin monen palvelimen kesken ja tehtävien generoimisesta aikataulun mukaan. Mutta tämän lisäksi meidän piti kirjoittaa daemon, joka pystyisi ajamaan PHP-skriptejämme CLI:ssä ja valvomaan niiden suorittamisen tilaa.

Hän on alun perin kirjoitettu C-kielellä, kuten kaikki muutkin demonit yhtiössämme. Kuitenkin kohtasimme sen tosiasian, että huomattava osa prosessorin ajasta (noin 10 %) meni olennaisesti hukkaan: tulkin käynnistäminen ja puitteemme "ytimen" lataaminen. Siksi, jotta tulkin ja kehyksen alustus vain kerran, demoni päätettiin kirjoittaa uudelleen PHP:ssä. Kutsuimme sitä Php rock syd (samanlainen kuin Phproxyd - PHP Proxy Daemon, C-daemon, joka meillä oli aiemmin). Se hyväksyy pyynnöt suorittaa yksittäisiä luokkia ja tekee fork():n jokaiselle pyynnölle, ja pystyy myös raportoimaan kunkin ajon suoritustilan. Tämä arkkitehtuuri on monella tapaa samanlainen kuin Apache-verkkopalvelinmalli, jossa kaikki alustus tehdään kerran "masterissa" ja "lapset" ovat mukana pyynnön käsittelyssä. Lisäbonuksena saamme mahdollisuuden ottaa käyttöön opcode-välimuisti CLI:ssä, mikä toimii oikein, koska kaikki lapset perivät saman jaetun muistialueen kuin pääprosessi. Viiveiden vähentämiseksi käynnistyspyynnön käsittelyssä voit tehdä fork():n etukäteen (prefork-malli), mutta meidän tapauksessamme fork():n viiveet ovat noin 1 ms, mikä sopii meille varsin hyvin.

Koska kuitenkin päivitämme koodia melko usein, tämä demoni on myös käynnistettävä uudelleen usein, muuten siihen ladattava koodi saattaa vanhentua. Koska jokaiseen uudelleenkäynnistykseen liittyisi paljon virheitä, kuten yhteyden nollaus vertaisohjelmalla, mukaan lukien palvelun kieltäminen loppukäyttäjiltä (daemon on hyödyllinen ei vain pilvelle, vaan myös osalle sivustoamme), päätimme etsiä tapoja käynnistää demoni uudelleen menettämättä jo muodostettuja yhteyksiä. On olemassa yksi suosittu tekniikka, jota käytetään tekemään siro uudelleenlataus demoneille: fork-exec on tehty ja kuunteluliittimen kuvaaja välitetään lapselle. Siten demonin uusi versio hyväksyy uudet yhteydet ja vanhat "muokataan" vanhalla versiolla.

Tässä artikkelissa tarkastellaan monimutkaisempaa vaihtoehtoa. siro uudelleenlataus: Vanhoja yhteyksiä käsittelee edelleen demonin uusi versio, mikä on meidän tapauksessamme tärkeää, koska muuten se ajaa vanhaa koodia.

Teoria Mietitään ensin: onko se, mitä haluamme saada, mahdollista? Ja jos on, miten tämä saavutetaan?

Koska demoni toimii Linuxissa, joka on POSIX-yhteensopiva, meillä on käytettävissämme seuraavat vaihtoehdot:

  • Kaikki avoimet tiedostot ja pistokkeet ovat numeroita, jotka vastaavat avoimen kuvaajan numeroa. Vakiotulon, -lähdön ja virhevirran kuvaajat ovat 0, 1 ja 2.
  • Avoimen tiedoston, socketin ja putken välillä ei ole merkittäviä eroja (voit esimerkiksi työskennellä sockettien kanssa käyttämällä sekä luku/kirjoitus- että sendto/recv from system -kutsuja).
  • Kun fork()-järjestelmäkutsu suoritetaan, kaikki avoimet kahvat peritään säilyttäen niiden numerot ja luku-/kirjoituspaikat (tiedostoissa).
  • Kun execve()-järjestelmäkutsu suoritetaan, myös kaikki avoimet kahvat periytyvät, ja lisäksi prosessin PID säilyy ja siten affiniteetti sen lapsiin.
  • Luettelo avoimen prosessin kuvaajista on saatavilla /dev/fd-hakemistosta, joka Linuxissa on symbolilinkki hakemistoon /proc/self/fd.
  • Näin ollen meillä on täysi syy uskoa, että tehtävämme voidaan suorittaa ja ilman suuria ponnistuksia. Joten aloitetaan PHP Patches Valitettavasti yksi pieni yksityiskohta vaikeuttaa työtämme: PHP:ssä ei ole mahdollista saada tiedostojen kuvaajan numeroa ja avata tiedostokuvaajaa numerolla (sen sijaan kopio tiedostokuvauksesta). avataan, mikä ei sovi demonillemme, koska valvomme avoimia kahvoja erittäin huolellisesti, jotta emme aiheuta vuotoja uudelleenkäynnistyksen aikana ja lapsiprosesseja käynnistettäessä).

    Ensin teemme pari pientä korjausta PHP-koodiin lisätäksemme mahdollisuuden saada fd virrasta ja varmistamaan, että fopen(php://fd/) ei avaa kopiota kahvasta (toinen muutos ei ole yhteensopiva nykyisen PHP-käyttäytymisen kanssa, joten voit lisätä sen sijaan uuden "osoitteen", esimerkiksi php://fdraw/):

    Patch koodi

    diff --git a/ext/standard/php_fopen_wrapper.c b/ext/standard/php_fopen_wrapper.c index f8d7bda..fee964c 100644 --- a/ext/standard/php_fopen_wrapper.c +++ b/ext/standard/php_fopen_wrapper. c @@ -24.6 +24.7 @@ #jos HAVE_UNISTD_H #sisällytä #endif +#sisällytä #sisällytä "php.h" #sisällytä "php_globals.h" @@ -296.11 +297.11 @@ php_stream * php_stream_url_wrap_php(php_wrap_wrap *polku, ch "Tiedostojen kuvaimien tulee olla ei-negatiivisia lukuja, jotka ovat pienempiä kuin %d", dtablesize);


    Lisäsimme fd-kentän stream_get_meta_data():n palauttamaan tulokseen, jos se on järkevää (esimerkiksi zlib-virroissa fd-kenttää ei ole). Korvasimme myös läpäisyn tiedostokuvaajan dup()-kutsun sen yksinkertaisella tarkistuksella. Valitettavasti tämä koodi ei toimi ilman muutoksia Windowsissa, koska fcntl()-kutsu on POSIX-spesifinen, joten täyden korjaustiedoston on sisällettävä ylimääräisiä koodihaaroja muille käyttöjärjestelmille. Daemon ilman uudelleenkäynnistysmahdollisuutta. Kirjoita ensin pieni palvelin joka pystyy hyväksymään pyyntöjä JSON-muodossa ja antamaan jonkinlaisen vastauksen. Se esimerkiksi palauttaa pyynnössä tulleen taulukon elementtien määrän.

    Daemon kuuntelee porttia 31337. Tuloksen pitäisi olla jotain tällaista:

    $ telnet localhost 31337 Yritetään 127.0.0.1... Yhdistetty localhostiin. Pakomerkki on "^]". ("hash":1) # käyttäjän syöttö "Pyynnössä oli 1 avainta" ("hash":1,"cnt":2) # käyttäjän syöttö "Pyynnössä oli 2 avainta"

    Käytämme stream_socket_server():tä aloittaaksemme kuuntelun portissa ja stream_select():tä määrittääksemme, mitkä kahvat ovat valmiita lukemaan/kirjoittamaan.

    Yksinkertaisin toteutuskoodi (Simple.php)