- Дългото запитване в PHP държи HTTP заявките отворени, което може да блокира оскъдните PHP работници и да навреди на мащабируемостта, ако е имплементирано с наивни блокиращи цикли.
- Съвременните подходи, управлявани от събития, внимателната конфигурация и оптимизации като пакетиране, кеширане и ограничаване на скоростта могат да смекчат много проблеми с дългите анкети.
- Алтернативи като WebSockets, SSE и управлявани платформи в реално време често осигуряват по-добра производителност за сценарии с висока паралелност, отколкото дългите ползи в сурово PHP.
Ако някога сте се опитвали да внедрите поведение в „реално време“ в класическо PHP приложение, вероятно сте се сблъсквали с дълго запитване и странните му проблеми с производителността. Това, което звучи като прост трик – да се държи HTTP заявка отворена, докато няма нещо ново за изпращане – изведнъж повдига неудобни въпроси относно блокираните PHP процеси, паметта на сървъра, времето за изчакване и как, за бога, се предполага, че се мащабира отвъд шепа потребители.
Това ръководство разглежда задълбочено проблема с дългите запитвания в PHP, защо се държи по начина, по който се държи, кои пречки са наистина важни и какво можете да направите по въпроса. Ще разгледаме колко дълго работят анкетите, защо наивният... sleep()Имплементацията, базирана на .png, често е по-лоша от краткото запитване, как съвременните цикли на събития и уеб сървърите могат да помогнат и кои оптимизации си заслужават усилията. По пътя ще сравним дългото запитване с WebSockets и Server-Sent Events, ще засегнем сигурността и обработката на грешки и ще разгледаме кога трябва да изоставите PHP за нещо като Node.js или управлявана платформа в реално време.
Какво е дълго анкетиране и как всъщност работи?
В основата си, дългото запитване е просто HTTP, където сървърът умишлено забавя отговора, докато има нещо интересно да каже. Вместо клиентът да „блъска“ сървъра на всеки няколко секунди с въпрос „има ли нещо ново?“, той изпраща заявка, която сървърът държи отворена, като отговаря само когато са готови нови данни или е изтекло времето за изчакване.
Типичният дълъг цикъл на заявка/отговор за анкетиране изглежда така: Браузърът (или който и да е клиент) отправя HTTP заявка към специална крайна точка, сървърът приема заявката, но не изпраща веднага отговор и TCP връзката остава отворена, докато сървърът чака нови събития или данни.
Ако се появи някакво събитие – чат съобщение, известие, актуализация на игра – сървърът незабавно отговаря на чакащия клиент. Клиентът обработва този полезен товар и, което е най-важно, веднага изпраща нова заявка за дълго запитване, така че винаги (или почти винаги) има една отворена връзка, чакаща следващата актуализация.
Ако нищо не се случи за известно време, сървърът евентуално изключва заявката след конфигуриран лимит, отговаряйки или с празни данни, или с полезен товар „няма актуализация“. Клиентът все още изпраща нова заявка, когато получи този отговор за изтичане на времето, поддържайки дълготрайното поведение чрез повтарящи се HTTP повиквания, а не чрез една безкрайна връзка.
Така че, въпреки че хората говорят за „постоянна“ връзка с дълго запитване, технически това е цикъл от сравнително дългоживеещи HTTP заявки, а не един безкраен поток. Тази фина разлика е от голямо значение за PHP, защото всяка от тези заявки обикновено е обвързана с един PHP работен процес или нишка за целия си жизнен цикъл.
Дълго запитване срещу кратко запитване в PHP
Краткото запитване е по-простият, старомоден модел: клиентът пита сървъра за нови данни по фиксиран график и сървърът отговаря незабавно. Може да удариш /notifications На всеки 3-5 секунди проверявайте бързо базата данни и изпращайте долу всичко ново.
Този подход е лесен за разсъждение, но е ужасно разточителен, когато повечето анкети не връщат „нищо ново“. Изгаряте процесор, заявки към базата данни и мрежови ресурси с постоянен поток от предимно празни отговори, а потребителят все още може да вижда закъснения между случващото се събитие и следващата планирана анкета.
Дългото запитване обръща модела: по-малко HTTP заявки, но всяка една оцелява много по-дълго, докато сървърът чака събитие. На теория това намалява HTTP натоварването и подобрява възприеманата бързина, защото потребителите получават актуализации веднага щом се случат, вместо да чакат следващия интервал на анкетиране.
Уловката в PHP е, че наивната крайна точка с дълго запитване просто блокира работника за времето на чакане. Ако държите връзката отворена за 300 секунди, използвайки цикъл с sleep(), вие обвързвате PHP процес или нишка, които иначе биха могли да обслужват много бързи заявки за кратки анкети в един и същ времеви период.
Когато сравните двете под натоварване, лошо имплементирана крайна точка с дълго запитване всъщност може да обработва много по-малко едновременни клиенти от краткото запитване. Например, PHP-FPM пул, който може лесно да обслужва хиляди малки 5-секундни анкети, може да спре да работи, ако всеки потребител заема по един worker за по 300 секунди.
Защо класическият PHP модел на дълго запитване е проблематичен
Много често срещана PHP рецепта за дълги ползи изглежда приблизително така: „increase max_execution_time, затворете сесията, след което изпълнете цикъл с sleep() докато проверява за нови данни“. Концептуално е лесно: спирате PHP да изтича твърде рано, освобождавате заключването на сесията, за да могат да се изпълняват други заявки от същия потребител, и след това проверявате за нови съобщения в цикъл до ~300 секунди.
Вътре в този цикъл, вашият код може заявка към базата данни или да проверява кеша в паметта при всяка итерация, като прави пауза за секунда или около това с sleep(1) за да се избегне претоварване на бекенда. Ако откриете ново известие, излизате от цикъла, изпращате отговора и прекратявате скрипта; ако достигнете времевия лимит без нови данни, изпращате обратно празен масив или някакъв маркер „няма актуализации“.
От страна на клиента, малко JavaScript (обикновено чрез $.ajax() в jQuery или fetch() (в обикновен JS) многократно извиква тази крайна точка. След като браузърът получи някакъв отговор (данни или празен), той изчаква може би няколко секунди и след това веднага изпраща друга дълга заявка за анкета, като ефективно работи завинаги, докато потребителят остава на страницата.
Въпреки че този модел работи за малка потребителска база, той много бързо достига ограничения, защото всяка чакаща заявка консумира цял PHP worker. С Apache prefork или PHP-FPM настройка, всеки блокиран екземпляр на скрипт използва RAM и ресурси през цялото време, докато е неактивен, което означава, че дори 30-40 едновременни клиенти могат да бъдат забележими, а 100+ започват да стават опасни без внимателна настройка.
За да влоши нещата, лесно е да се разбере погрешно какво... sleep() обаждането всъщност прави. От гледна точка на операционната система, вашата PHP нишка буквално не прави нищо продуктивно по време на този спящ режим, но въпреки това се брои за активен работен поток, който не може да бъде използван повторно за други заявки.
Нишки, процеси и моделът на уеб сървъра
За да разберете наистина проблема с дългите запитвания в PHP, трябва да разгледате как вашият уеб сървър управлява връзките и работните процеси. Традиционният Apache prefork създава множество процеси, всеки от които обработва по една заявка в даден момент; други MPM или PHP-FPM използват пулове от работници с подобен модел „една заявка на работник“.
Когато всеки клиент за дълго запитване държи заявка отворена в продължение на минути, бързо изчерпвате този ограничен пул от PHP работници. Всеки един от тях е предимно празен, блокира паметта и предотвратява обслужването на по-нататъшен трафик, след като достигнете конфигурирания максимум.
Сравнете това със системи, изградени около неблокиращи входно-изходни операции и цикли на събития, където една нишка на операционната система може да жонглира с хиляди едновременни връзки, стига повечето от тях да са неактивни. В този свят „нещо“ вътре в стека на операционната система наистина чака, но това не е тежък потребителски процес за всяка връзка.
Nginx е добър пример от страна на HTTP: той използва архитектура, управлявана от събития, за да управлява огромен брой отворени връзки с много малко потребление на памет. Когато включите PHP към Nginx чрез FastCGI или PHP-FPM, Nginx може да поддържа клиентските връзки активни и да прехвърля заявките към фиксиран пул от PHP работници, което добавя известно пространство за дишане, но не решава магически блокиращите PHP скриптове.
Ето защо простата мантра „всяка дълга анкета блокира нишка“ е прекалено опростена, но все пак болезнено точна за много класически PHP внедрявания. Освен ако не използвате асинхронна среда за изпълнение или различна архитектура, типичният PHP скрипт се изпълнява синхронно и ще заема работник, докато е активен.
Наистина ли Node.js е специален тук?
Node.js често се цитира като магическо решение, защото използва еднонишков цикъл на събития и неблокиращ входно/изходен поток по подразбиране. Вместо да генерира нишка за всяка връзка, Node следи едновременно много отворени сокети и извършва реална работа само когато пристигнат данни или се задейства събитие.
Този архитектурен избор значително улеснява поддръжката на огромен брой неактивни дълги анкети или WebSocket връзки на скромен хардуер. Когато не се получават съобщения, цикълът на събития на Node е все още активен, но едва работи, а използването на RAM на връзка е малко в сравнение с класическата PHP настройка на worker-per-request.
PHP обаче не е напълно изключен от тази игра; той също така разполага с модерни библиотеки за цикли на събития, които осигуряват подобно неблокиращо поведение. Проекти като ReactPHP, Amp или Revolt ви предоставят управлявана от събития среда за изпълнение в PHP, която може да управлява сокети или асинхронни задачи, без да задейства блокиращ worker за всяка връзка.
На практика обаче по-голямата част от наследените PHP приложения все още работят на синхронен модел с Apache или PHP-FPM и без цикъл на събития. За тези приложения репутацията „Node е по-добър в дългите анкети“ е до голяма степен основателна, защото ще трябва значително да преработите своя PHP стек, за да получите сравнима мащабируемост.
Колко дълго е провеждането на анкети в сравнение с WebSockets и SSE
Дългото запитване е само един от начините за прехвърляне на данни от сървъра към клиента; WebSockets и Server-Sent Events (SSE) често са по-подходящи за съвременни приложения в реално време. Всяка техника има свои собствени компромиси по отношение на сложност, възможности и потребление на ресурси.
WebSockets установяват истински двупосочен канал между клиент и сървър през една обновена TCP връзка. След като ръкостискането за надстройката приключи, двете страни могат да изпращат съобщения по желание, без да повтарят HTTP заглавките, което е огромна печалба за чат приложения, мултиплейър игри, табла за управление и всеки сценарий с чести актуализации.
SSE, от друга страна, предлага еднопосочен поток от събития от сървър към клиент през дълготрайна HTTP връзка. Подходящ е за известия, емисии на живо или всеки случай, където само сървърът трябва да изпраща данни, а клиентът рядко изпраща информация обратно след първоначалната заявка.
Дългото запитване е някъде по средата: то симулира изпращане от сървъра, използвайки повтарящи се HTTP заявки, така че все още плащате за връзка и заглавки всеки път. Предимството е, че работи с обикновена HTTP инфраструктура и стандартни браузъри без допълнителни протоколи или специална клиентска поддръжка.
Много библиотеки за реално време, като Socket.IO, всъщност започват с дълго запитване и след това надграждат до WebSockets, когато е възможно, третирайки дългото запитване като резервен, а не като основен механизъм. Това ви казва много за относителната ефективност на дългите анкети в голям мащаб.
Съображения за сигурност за крайни точки с дълго запитване
Въпреки че дългото запитване изглежда като дребен детайл, всяка крайна точка на дългото запитване е просто HTTP API повърхност, която трябва да бъде правилно защитена. Първата неоспорима стъпка е да се обслужва изключително през HTTPS, така че трафикът да не може да бъде прихванат или подправен по време на преминаване.
В допълнение към сигурността на транспорта, е необходимо строго удостоверяване и оторизация за всички дълги заявки за анкетиране. Независимо дали използвате сесийни бисквитки, JWT, API токени или OAuth, сървърът трябва да провери дали всяка отворена връзка съответства на валиден потребител и дали на потребителя е разрешено да получава заявения поток от данни.
Някои класически проблеми със сигурността на браузъра, като CSRF, може да са по-малко релевантни, ако не разчитате на имплицитно удостоверяване, базирано на бисквитки, от стандартни формуляри, но все пак трябва да помислите за вашия специфичен контекст. Ако са включени „бисквитки“ или ако крайната точка може да задейства промени в състоянието, тогава защитите срещу CSRF (токени, „бисквитки“ на същия сайт и др.) остават важни.
Валидирането на входните данни е също толкова важно за дългите ползи, колкото и за всеки друг HTTP API. Параметри като идентификатори на канали, потребителски идентификатори, филтри или времеви отметки трябва да бъдат дезинфекцирани и валидирани на сървъра, за да се избегне SQL инжектиране, преминаване през пътя или логически грешки, които биха могли да доведат до изтичане на данни между потребителите.
Дългото запитване също така отваря вратата към сценарии за отказ на услуга, тъй като връзките са умишлено дълготрайни и относително скъпи в PHP. Трябва да наложите разумни ограничения на скоростта на достъп за всеки IP адрес или за всеки акаунт, да ограничите броя на едновременните връзки и да приложите времеви ограничения за връзка, за да държите използването на ресурси под контрол.
Непрекъснатото наблюдение, регистрирането и периодичните одити на сигурността са последният слой защита. Регистрирането на грешки при дълго запитване, неуспехи при удостоверяване и необичайни модели на свързване ви дава данните, от които се нуждаете, за да откриете злоупотреби, регресии или проблеми с конфигурацията, преди те да се разраснат под въздействието на реален трафик.
Обработка на грешки и устойчивост при дълги анкети
Тъй като дългото запитване разчита на много дълготрайни връзки, надеждната обработка на грешки е от решаващо значение, за да се избегнат каскадни грешки или блокирани клиенти. Започнете, като зададете реалистичен таймаут от страна на сървъра за всяка заявка, така че липсващо събитие да не оставя връзките висящи завинаги.
Когато това време за изчакване се достигне без нови данни, сървърът трябва да отговори с ясен, добре дефиниран полезен товар. Това може да е празен списък, специфична JSON структура или HTTP 204 статус, но трябва да е предвидимо, за да може клиентът да различи „няма данни все още“ от реални грешки.
За истински проблеми от страна на сървъра – прекъсвания на базата данни, вътрешни изключения, невалидни параметри – е важно да се изпращат точни HTTP кодове за състояние. Кодове като 500 (грешка в сървъра), 404 (ресурсът не е намерен) или 401/403 (проблеми с удостоверяването) позволяват на клиента да реагира по подходящ начин, вместо сляпо да опитва отново.
От страна на клиента, логиката за автоматично повторно опитване с експоненциално отсрочване е почти задължителна за дълги запитвания. Ако дадена заявка е неуспешна или времето за изчакване изтече по неочакван начин, клиентът трябва да изчака малко, преди да изпрати следващата, и да увеличи времето за изчакване при повтарящи се неуспехи, за да избегне спиране на работещия бекенд.
Проверките за състоянието и управлението на състоянието на връзката също са част от добрия дизайн. Ако клиентът открие загуба на мрежа или повтарящи се грешки в сървъра, той може да превключи към по-опростен резервен механизъм, като например кратко запитване или да деактивира функции в реално време, като същевременно показва приятелско съобщение на потребителя.
И накрая, цялото това поведение трябва да е наблюдаемо: грешки в регистрите, времеви ограничения и модели на повторен опит, след което тези регистрационни файлове и показатели трябва да се използват за настройване на времеви ограничения, стратегии за отсрочка и капацитет на инфраструктурата. Дългото анкетиране, което се проваля безшумно, е един от най-бързите начини да се ядосат потребителите и да се получат недиагностицирани главоболия, свързани с производителността.
Стратегии за мащабируемост, производителност и оптимизация
Имплементирано, наивното дълго запитване в PHP не се мащабира добре, но има много копчета, които можете да завъртите, преди да се откажете напълно от него. Целта е да се намалят разходите за връзка и да се използва по-добре вашата инфраструктура.
Един от най-полезните трикове е да се групират отговорите, вместо да се изпраща по едно съобщение за всеки HTTP отговор. Ако по време на един дълъг прозорец за анкета постъпят множество събития, обединете ги в един полезен товар, за да амортизирате HTTP натоварването и да намалите броя на повторните връзки.
Компресията също може да окаже значително влияние, особено за подробни JSON полезни товари. Активирането на Gzip (или подобен) на ниво уеб сървър или PHP свива размера на отговорите, ускорява доставката и намалява консумацията на трафик, което е забележимо при голям мащаб.
Кеширането не бива да се пренебрегва: специален кеш слой може да абсорбира много повтаряща се работа, която иначе би засегнала вашата база данни или микросървиси. Ако много потребители са абонирани за подобни потоци, кешираните моментни снимки или изчислените агрегати могат драстично да намалят времето за обработка на заявка.
От страна на връзката, обединяването и повторната употреба са от значение предимно за зависимости от бекенда, като бази данни или външни API. Поддържането на отворени и повторно използвани връзки към базата данни, вместо повторното свързване при всяка анкета, може да намали латентността и натоварването на процесора, особено при интензивна паралелизация.
Ограничаването на скоростта и дроселирането изпълняват две роли: те защитават вашата инфраструктура от злоупотреба и помагат за поддържане на предвидима производителност при натоварване. Като ограничавате колко често даден потребител или IP адрес може да отваря дълги връзки за анкетиране, намалявате риска от изчерпване на ресурсите и създавате място за честна употреба.
Балансирането на натоварването между множество сървъри на приложения е друг мощен лост. Разпределението на дълги заявки за анкетиране между възлите предотвратява превръщането на отделна машина в гореща точка, особено когато се комбинира със „лепкави“ сесии или външни хранилища за състояние на потребителски абонаменти.
Асинхронната обработка и фоновите работници трябва да обработват всичко, което не е строго част от цикъла „изчакване на събитие, изпращане на събитие“. Опашките за съобщения, работниците на задачи и разпределените системи позволяват на вашата крайна точка за дълго запитване да остане тънка и отзивчива, докато тежката работа се извършва другаде.
Подходящите времена за връзка и скриптове са последните предпазни клапани. Затваряйте празните или заседнали връзки след разумен период, дръжте max_execution_time в съответствие с вашата логика за дълго запитване и бъдете ясни относно това колко дълго е позволено да чакат както сървърът, така и клиентът.
Съвети за имплементация, специфични за PHP, за дълги запитвания
При имплементирането на дълго запитване в обикновен PHP, няколко конфигурационни и кодови детайли оказват голямо влияние върху поведението и стабилността. Един от големите се обажда session_write_close() възможно най-рано в сценария.
Стандартният манипулатор на сесии в PHP използва заключване на файлове, което означава, че заявка, която държи сесията отворена, може да блокира други заявки от същия потребител. Затваряне на сесията за писане на заключващи версии, като същевременно се позволява достъп за четене на данните от сесията, така че допълнителни паралелни заявки да не се нареждат на опашка след дългата анкета.
Обикновено ще трябва също да повишите или отмените max_execution_time ограничение, за да позволите на скриптовете да се изпълняват за 60, 120 или 300 секунди. Без тази настройка, PHP може да прекъсне скрипта преди дългият цикъл на запитване да завърши, което да доведе до объркващи грешки от страна на клиента или полудоставени отговори.
Вътре в цикъла бъдете много внимателни колко често достигате до базата данни или други бекендове. Тесен цикъл, който отправя заявки към базата данни на всеки 100 милисекунди, ще „стопи“ вашата база данни дори при умерено натоварване; въведете разумни спящи режими, кеширане или push-стил тригери, за да поддържате системата бърза.
Струва си да се помисли и за регистрирането и показателите в този цикъл. Проследявайте колко често излизате поради изтичане на времето за изчакване спрямо реалните данни, колко е средното време на чакане и колко едновременни дълги анкети обслужвате, след което въведете тези числа обратно в планирането на капацитета и настройването на кода.
Преди всичко, имайте предвид, че всеки блокиращ PHP скрипт е свързан с определен worker, така че минимизирането на цикли, спящи процеси и ненужна работа е от съществено значение, ако искате да поддържате повече от малка шепа потребители. За вътрешни инструменти или приложения с нисък трафик това може да е напълно приемливо; за нещо по-голямо ще ви е необходима по-стабилна архитектура.
Реални модели, библиотеки и алтернативи
Много разработчици откриват дългите анкети чрез малки демонстрации: PHP скрипт наблюдава текстов файл и когато редактирате този файл, промяната се появява мигновено в браузъра. Това е чудесен ментален модел: прост код, ясно поведение и незабавна обратна връзка.
В производствена среда този тривиален пример бързо се сблъсква с ограниченията, които обсъдихме, но все пак илюстрира основния протокол. JavaScript клиент изпраща AJAX заявка, PHP скриптът блокира, докато файлът не се промени или не изтече време за изчакване, след което отговаря и повтаря.
По-специално за PHP екосистемите ще намерите няколко рамки и шаблона, които се опитват да направят това по-лесно за управление. Laravel, например, подтиква разработчиците към излъчване на събития чрез WebSockets или управлявани услуги, въпреки че все още можете да настройвате дълги маршрути за анкетиране ръчно.
Извън PHP, има зрели рамки в почти всеки език, които или поддържат директно дългите запитвания, или предоставят по-приятни абстракции около комуникацията в реално време. Node.js с Express.js или Socket.IO, Python с Flask или Django Channels, Java със Spring, Ruby on Rails и .NET с ASP.NET SignalR са все популярни избори.
Някои платформи крият от вас изцяло целия проблем с мащабирането и връзката. Управляваните услуги в реално време – включително тези, които се фокусират върху pub/sub съобщения и присъствие – предлагат клиентски SDK, криптиране, прецизен контрол на достъпа и глобална инфраструктура за обработка на милиони едновременни връзки, така че да не се налага да преоткривате колелото.
В света на PHP често можете да получите най-доброто от двата свята, като комбинирате съществуващата логика на приложението си с такава услуга в реално време. PHP остава отговорен за бизнес правилата, постоянството и API-тата, докато външната платформа обработва дълготрайни връзки, разклоняване и доставка в реално време чрез дългосрочно запитване, SSE или WebSockets, според случая.
Други екосистеми обозначават тези стратегии в реално време с имена като Comet, Reverse Ajax, HTTP Streaming, Pushlet или AJAX long polling. Под капака всички те са вариации на една и съща идея: превръщане на фундаментално протокол за заявка/отговор в нещо, което се усеща като базирано на push.
За PHP разработчиците, които се борят с проблеми с дългите запитвания, ключът е честно да оценят нуждите си от паралелизъм, хостинг средата си и колко сложност са готови да поемат. Понякога е достатъчен един обикновен блокиращ цикъл; друг път е по-добре да приемете библиотека за цикъл на събития, да прехвърлите push на специална услуга или да прехвърлите част от стека си към технология, изградена за огромен брой отворени връзки.
Когато съберете всички тези части заедно – HTTP механиката, модела на изпълнение на PHP, сървърната архитектура, сигурността, обработката на грешки и мащабируемите дизайнерски модели – дългите ползи престават да бъдат мистериозен убиец на производителността и се превръщат просто в още един инструмент, който можете да използвате целенасочено, където има смисъл.