Salam, əziz dost! Bağlantıları itirmədən PHP-də demonun yenidən işə salınması js skriptinin yaradılması

). Bulud qrafik üzrə və ya API vasitəsilə müxtəlif PHP skriptlərini işlətmək üçün nəzərdə tutulub. Bir qayda olaraq, bu skriptlər növbələri emal edir və yük təxminən 100 server üzərində “yayılır”. Əvvəllər biz nəzarət məntiqinin necə həyata keçirildiyinə diqqət yetirmişdik ki, bu da yükün bu qədər sayda server arasında bərabər paylanmasına və cədvələ uyğun tapşırıqların yaradılmasına cavabdehdir. Bununla yanaşı, bizə PHP skriptlərimizi CLI-də işlədə və onların icra vəziyyətini izləyə biləcək bir demon yazmaq lazım idi.

O, bizim şirkətdəki bütün digər cinlər kimi, əvvəlcə C dilində yazılmışdır. Bununla belə, prosessor vaxtının əhəmiyyətli bir hissəsinin (təxminən 10%) mahiyyətcə boşa getməsi ilə qarşılaşdıq: tərcüməçini işə salmaq və çərçivəmizin "nüvəsini" yükləmək. Buna görə də, tərcüməçini və çərçivəmizi yalnız bir dəfə işə sala bilmək üçün PHP-də demonun yenidən yazılması qərara alındı. Biz onu Php adlandırdıq qaya syd (Phproxyd-ə bənzəyir - PHP Proxy Daemon, əvvəllər bizdə olan C demonu). O, fərdi sinifləri işə salmaq üçün sorğuları qəbul edir və hər sorğuda fork() edir, həmçinin hər bir qaçışın icra vəziyyəti haqqında məlumat verə bilir. Bu arxitektura bir çox cəhətdən Apache veb-server modelinə bənzəyir, bütün işəsalma bir dəfə “master”də həyata keçirildikdə və “uşaqlar” sorğunun işlənməsi ilə məşğul olurlar. Əlavə bonus olaraq biz CLI-də əməliyyat kodu keşini aktivləşdirmək imkanı əldə edirik, çünki bütün uşaqlar master proseslə eyni paylaşılan yaddaş sahəsini miras aldıqları üçün düzgün işləyəcək. Başlatma sorğusunu emal edərkən gecikmələri azaltmaq üçün siz fork() funksiyasını əvvəlcədən edə bilərsiniz (prefork modeli), lakin bizim vəziyyətimizdə fork() üçün gecikmələr təxminən 1 ms-dir ki, bu da bizə kifayət qədər uyğun gəlir.

Lakin kodu tez-tez yenilədiyimiz üçün bu demon da tez-tez yenidən işə salınmalıdır, əks halda ona yüklənmiş kod köhnələ bilər. Hər bir yenidən başlatma kimi bir çox səhvlə müşayiət olunacağından əlaqə peer tərəfindən sıfırlanır, o cümlədən son istifadəçilərə xidmətdən imtina (daemon təkcə bulud üçün deyil, həm də saytımızın bir hissəsi üçün faydalıdır), biz artıq qurulmuş əlaqələri itirmədən demonu yenidən işə salmağın yollarını axtarmaq qərarına gəldik. Bunu etmək üçün istifadə olunan bir məşhur texnika var zərif yenidən yükləmə demonlar üçün: fork-exec yerinə yetirilir və dinləmə yuvasından bir deskriptor uşağa ötürülür. Beləliklə, yeni bağlantılar demonun yeni versiyası tərəfindən qəbul edilir və köhnələr köhnə versiyadan istifadə edərək "dəyişdirilir".

Bu yazıda daha mürəkkəb bir seçimə baxacağıq. zərif yenidən yükləmə: Köhnə bağlantılar demonun yeni versiyası tərəfindən işlənməyə davam edəcək, bu bizim vəziyyətimizdə vacibdir, çünki əks halda köhnə kodu işlədəcək.

Nəzəriyyə Əvvəlcə düşünək: əldə etmək istədiyimiz şey mümkündürmü? Və əgər belədirsə, buna necə nail olmaq olar?

Demon POSIX-ə uyğun olan Linux-da işlədiyi üçün bizim üçün aşağıdakı seçimlər mövcuddur:

  • Bütün açıq fayllar və yuvalar açıq deskriptor nömrəsinə uyğun gələn nömrələrdir. Standart giriş, çıxış və səhv axını müvafiq olaraq 0, 1 və 2 deskriptorlarına malikdir.
  • Açıq fayl, yuva və boru arasında əhəmiyyətli fərqlər yoxdur (məsələn, siz həm oxumaq/yazmaq, həm də sistem zənglərindən sendto/recv istifadə edərək rozetkalarla işləyə bilərsiniz).
  • Fork() sistem çağırışı yerinə yetirildikdə, bütün açıq tutacaqlar onların nömrələrini və oxu/yazma mövqelərini (fayllarda) saxlayaraq miras alınır.
  • execve() sistem çağırışı yerinə yetirildikdə, bütün açıq tutacaqlar da miras alınır və əlavə olaraq prosesin PID-i saxlanılır və buna görə də onun uşaqlarına yaxınlıq olur.
  • Açıq proses deskriptorlarının siyahısı Linux-da /proc/self/fd ilə simvolik əlaqə olan /dev/fd kataloqundan əldə edilə bilər.
  • Beləliklə, bizim vəzifəmizin çox səy göstərmədən yerinə yetirilə biləcəyinə inanmaq üçün bütün əsaslarımız var. Beləliklə, başlayaq PHP üçün yamaqlar Təəssüf ki, işimizi çətinləşdirən kiçik bir detal var: PHP-də axınlar üçün fayl deskriptor nömrəsini əldə etmək və fayl deskriptorunu nömrə ilə açmaq mümkün deyil (əvəzində faylın surəti. deskriptor açıldı, bu daemonumuz üçün uyğun deyil, çünki yenidən başladıqda və uşaq proseslərini başlatarkən sızma yaratmamaq üçün açıq tutacaqları çox diqqətlə izləyirik).

    Birincisi, PHP koduna bir neçə kiçik yamaq edəcəyik ki, axından fd əldə etmək imkanı əlavə edək və fopen(php://fd/) dəstəyinin surətini açmadığından əmin olun (ikinci dəyişiklik). cari PHP davranışı ilə uyğun gəlmir, ona görə də əvəzinə yeni “ünvan” əlavə edə bilərsiniz, məsələn, php://fdraw/):

    Yamaq kodu

    fərq --git a/ext/standard/php_fopen_wrapper.c b/ext/standard/php_fopen_wrapper.c indeks f8d7bda..fee964c 100644 --- a/ext/standard/php_fopen_wrapper.c +++ b/ext_fopen_wrapper.c +++ b/ext_fopen_wrapper/phrap c @@ -24.6 +24.7 @@ #əgər_UNISTD_H varsa #include #endif +#include #include "php.h" #include "php_globals.h" @@ -296.11 +297.11 @@ php_stream * php_wstreamp_urp * php_wstreamp_urp (chapper_stream_urp) *path, ch "Fayl deskriptorları %d-dən kiçik mənfi olmayan ədədlər olmalıdır", dtablesize);


    Məntiqli olarsa, stream_get_meta_data() tərəfindən qaytarılan nəticəyə fd sahəsini əlavə etdik (məsələn, zlib axınları üçün fd sahəsi mövcud olmayacaq). Biz həmçinin ötürülən fayl deskriptorundan dup() çağırışını sadə yoxlama ilə əvəz etdik. Təəssüf ki, bu kod Windows-da modifikasiyalar olmadan işləməyəcək, çünki fcntl() çağırışı POSIX-ə xasdır, ona görə də tam patch digər ƏS-lər üçün əlavə kod filiallarını ehtiva etməlidir. Əvvəlcə kiçik bir server yazaq JSON formatında sorğuları qəbul edə və bir növ cavab verə biləcək. Məsələn, sorğuya daxil olan massivdəki elementlərin sayını qaytaracaq.

    Demon 31337 portunu dinləyir. Nəticə belə olmalıdır:

    $ telnet localhost 31337 127.0.0.1 cəhd edilir... Localhost-a qoşulub. Escape xarakteri "^]"-dir. ("hash":1) # istifadəçi girişi "Sorğun 1 açarı var idi" ("hash":1,"cnt":2) # istifadəçi girişi "Sorğun 2 açarı var idi"

    Biz portda dinləməyə başlamaq üçün stream_socket_server() və hansı tutacaqların oxumağa/yazmağa hazır olduğunu müəyyən etmək üçün stream_select() funksiyasından istifadə edəcəyik.

    Sadə icra kodu (Simple.php)