اکثر ما با نحوه عملکرد یک برنامه وب آشنا هستیم. همچنین میدانیم که وقتی URL را در مرورگر خود وارد میکنیم، با یک وب سرور ارتباط برقرار میشود و کدهای HTML را برمیگرداند. سپس مرورگر درخواست را پیگیری کرده و بعد صفحه وب را رندر میکند.
اما چه اتفاقی در وب سرور هنگام بازگشت HTML رخ میدهد؟ وب سرور چگونه HTML را که به مرورگر ما برمیگردد، دریافت میکند؟ و به طور دقیقتر در PHP چگونه این اتفاق میافتد؟
این چیزی نیست که اکثر ما بتوانیم به آن پاسخ دهیم. ما میدانیم (یا حداقل باید بدانیم) چگونه یک وب سایت PHP بسازیم. اما آنچه بین کد PHP و مرورگر اتفاق میافتد کمی اسرارآمیز است.
به احتمال زیاد شما اینگونه فکر میکنید: خوب سایت من به خوبی لود میشود، پس چرا باید به این موضوع فکر کنم؟ اما حقیقت این است که دانستن نحوه عملکرد یک برنامه PHP میتواند بسیار مفید باشد.
همچنین میتواند بر نحوه طراحی برنامه PHP شما تأثیر بگذارد. و اگر هم به دنبال بهبود عملکرد برنامه خود هستید، بسیار حائز اهمیت خواهد بود. به عنوان مثال اگر از نحوه عملکرد یک برنامه PHP آگاهی نداشته باشید، درک نحوه ذخیره برنامههای PHP هم دشوار میشود.
طول عمر یک درخواست PHP
اگر قبلا با یک زبان کامپایل شده (مانند Java یا #C) کار کردهاید، مهمترین چیزی که باید بدانید این است که PHP اینگونه نیست. این یک زبان تفسیری است؛ یعنی باید کد درخواست شما را ابتدا تفسیر کرده و بعد کامپایل کند.
بعد میتوانید از حافظه کش و بهینهسازیهای دیگر برای سرعت بخشیدن به این فرآیند استفاده کنید. اما ماهیت نحوه عملکرد درخواستها به برنامه PHP شما تغییر نمیکند. آنها همیشه شبیه این خواهند بود:
همانطور که در تصویر بالا مشاهده میکنید، همیشه مرورگر ارسال درخواست را به صفحه وب شروع میکند. این درخواست به وب سرور فرستاده میشود، سپس وب سرور آن را تجزیه و تحلیل کرده و تعیین میکند که با آن چه کار کند.
اگر وب سرور تشخیص دهد که درخواست برای یک فایل PHP (اغلب index.php) است، آن فایل را به مترجم PHP منتقل میکند. مترجم PHP فایل را میخواند، آن را تجزیه میکند (به همراه سایر فایلهای موجود) و سپس آن را اجرا مینماید. هنگامی که مترجم PHP اجرای فایل را به پایان رساند، خروجی را برمیگرداند. وب سرور هم خروجی را گرفته و به عنوان پاسخی به مرورگر ارسال میکند.
برنامه PHP فقط یک اسکریپت است
هدف از توضیح چرخه عمر یک درخواست PHP، رسیدن به این عبارت بود. این حقیقت پشت هر برنامه PHP وجود دارد، چه برنامه تحت Laravel نوشته شده باشد و چه Symfony (یا حتی CMS مانند دروپال یا وردپرس). همه آنها چیزی بیشتر از یک Shell Script نیستند.
در این سناریو، وب سرور چیزی بیشتر از یک واسطه نیست. هر بار که شخصی درخواست صفحه PHP میکند، فقط از وب سرور میخواهد که یک اسکریپت PHP خاص را اجرا کند. اگر اسکریپت PHP خروجی را برگرداند، وب سرور آن را ارسال میکند.
این موضوع با آنچه در سایر زبانهای برنامهنویسی وب مشاهده میکنید متفاوت است. چرا که زبانهای برنامهنویسی وب چیزی را ایجاد میکنند که ما آن را برنامههای وب سنتی مینامیم. چنین برنامههای وب سنتی اغلب با استفاده از یک اپلیکیشن سرور کار میکنند. برخی از اپلیکیشن سرورهای معروف عبارتند از Tomcat برای جاوا، IIS برای NET.، Gunicorn برای پایتون و Unicorn برای روبی.
کار با اپلیکیشن سرور از برخی جهات اساسا متفاوت است. مهمترین مورد این است که یک برنامه وب سنتی در داخل اپلیکیشن سرور قرار دارد. این اسکریپتی نیست که وب سرور شما بتواند آن را فراخوانی کند. در واقع این اپلیکیشن سرورها به عنوان وب سرور عمل میکنند. تصویر زیر میتواند به شما در تجسم این موضوع کمک کند.
همانطور که مشاهده میکنید، وب اپلیکیشن در داخل اپلیکیشن سرور قرار دارد (حتی میتواند بیش از یک وب اپلیکیشن در هر اپلیکیشن سرور وجود داشته باشد). این اپلیکیشن سرور همچنین وب سرور شما نیز هست. در واقع هر درخواستی که به آن ارسال میکنید، همان درخواست از وب اپلیکیشن است و مانند PHP واسطه نیست.
تفاوت داینامیکی
این تفاوت اساسی بین برنامه PHP و برنامه وب سنتی مهم است و تغییرات زیادی در داینامیک بودن آنها ایجاد میکند. به همین دلیل است که میتوانید هر فایل PHP را روی یک سرور ویرایش کنید و تغییرات را بلافاصله مشاهده نمایید.
اما این موضوع در مورد برنامههای وب سنتی صدق نمیکند. چنین برنامههایی فقط به شما امکان میدهند فایلهای قالب را قبل از بارگذاری ویرایش کنید. هرگونه تغییر دیگر مستلزم این است که سرور برنامه را مجددا راهاندازی کنید (حتی اگر از زبانهای کامپایل شده استفاده کنید، همه چیز پیچیدهتر میشود).
هر چند این تنها تفاوت بین برنامه PHP و برنامه وب سنتی نیست. تفاوتهای دیگری نیز وجود دارد که دانستن آنها بسیار مهم است. بنابراین اجازه دهید برخی از آنها را بررسی کنیم.
مدیریت حافظه
ابتدا بیایید با مدیریت حافظه شروع کنیم. وقتی با یک برنامه PHP کار میکنیم، حافظه را چگونه مدیریت کنیم؟ پاسخ این است که اکثر ما هنگام کار بر روی یک برنامه PHP حافظه را مدیریت نمیکنیم.
ما فقط در صورتی به دنبال راه حل هستیم که PHP خطای مربوط به حافظه بدهد. و اغلب هنگام مواجه شدن با این خطا فقط محدودیت حافظه PHP را افزایش میدهیم! سپس به هر چیزی که قبل از مشاهده خطا روی آن کار میکردیم، ادامه خواهیم داد.
اما اگر تمرکز بیشتری بر عملکرد داشته باشید، سعی میکنید میزان استفاده از حافظه کد خود را کاهش دهید. برای مثال فرض کنید که حافظه شما هنگام ذخیره سازی یک فایل XML بزرگ توسط SimpleXML پر شده است. در این صورت میتوانید برای تعویض فایل XML به XMLReader سویچ کنید.
نشت حافظه
آیا تا به حال با نشت حافظه در PHP مواجه شدهاید؟ نشت حافظه نتیجه مدیریت نامناسب حافظه مانند خطاهای خارج از حافظه است. گاهی پیش میآید که شما دادهها را در حافظه باقی میگذارید و حافظه پس از اتمام کار با دادهها هرگز پاک نمیشود.
با این وجود نشت حافظه در برنامههای PHP بسیار نادر است. به این دلیل که PHP یک اسکریپت است که اجرا میشود و سپس خاتمه مییابد. سپس PHP تمام حافظه استفاده شده توسط اسکریپت را پاک میکند. همچنین ریست حافظه به این طریق از نشت حافظه در اکثر برنامههای PHP جلوگیری مینماید.
اما این امر در مورد یک برنامه وب سنتی صدق نمیکند. از آنجا که برنامه بخشی از وب سرور است، تنها با راهاندازی مجدد وب سرور خاتمه مییابد. این بدان معناست که نشت حافظه در این نوع برنامهها یک واقعیت مشترک است.
اگر سرور برنامه خود را مجددا راهاندازی نکنید، حافظه سرور شما در جایی به اتمام میرسد. هنگامی که این اتفاق میافتد، سرور برنامه اصطلاحا کرش میکند.
ماندگاری دادهها
بنابراین کاملا آگاه هستیم که نشت حافظه چگونه است و چرا PHP از این مشکل رنج نمیبرد. اما این تنها جنبه منحصر به فرد مدیریت حافظه در PHP نیست. علاوه بر این PHP هیچ راه داخلی برای ماندگاری دادهها در حافظه ندارد.
در حقیقت بخشی از دلایل نشت حافظه در PHP به دلیل این جنبه مدیریت حافظه در PHP است. بیایید تصور کنیم که ما دو درخواست یکسان برای برنامه PHP خود داریم. اولین مورد از طریق اسکریپت PHP اجرا میشود و دادهها را در این فرایند در حافظه ذخیره میکند. هیچ یک از این دادهها برای درخواست دوم در دسترس نخواهد بود.
این موضوع به آنچه در مورد اسکریپتهای PHP قبلا توضیح دادیم مربوط میشود. هنگامی که PHP اجرای یک اسکریپت را به پایان رساند، سیستمعامل حافظهای را که استفاده میکرد پاک میکند. این بدان معناست که PHP نمیتواند با استفاده از حافظه مانند برنامههای وب سنتی، دادهها را بین درخواستها به اشتراک بگذارد.
در عوض PHP به مکانیزمهای دیگر متکی است تا دادهها را بین درخواستها به اشتراک بگذارد. بدین ترتیب میتوانید از sessionها استفاده کنید که خود آنها از یک متد ذخیره سازی استفاده میکنند. این متد ذخیره سازی میتواند یک فایل، یک مجموعه داده یا حتی دیتابیس باشد.
یک برنامه PHP تمایل دارد از یک یا چند متد ذخیره سازی استفاده کند. اینکه از کدام یک استفاده میکنید بستگی به این دارد که برنامه شما از چه چیزی پشتیبانی میکند و نیازهای شما چیست. اما اگر مجبورید یکی را انتخاب کنید، استفاده از یک پایگاه داده مانند Redis یا Memcached بهترین گزینه است.
همزمانی
همزمانی مفهوم دیگری است که درک آن در برنامههای PHP اهمیت دارد. ایده این است که میتوانید نرمافزار را به قسمتهای کوچکتر تقسیم کرده، سپس این قطعات کوچکتر را به طور همزمان یا در یک ترتیب متفاوت بدون هیچ مشکلی اجرا کنید.
در حال حاضر اینگونه برداشت میشود که همزمانی در برنامههای PHP واقعا منطقی نیست. چرا که PHP برنامه را به قسمتهای کوچکتر تجزیه نکرده و همزمان آنها را اجرا میکند. با این وجود همزمانی هنوز هم به دلیل نحوه مدیریت چندین درخواست PHP در یک برنامه کاربرد دارد.
همانطور که تاکنون مشاهده کردیم، هر درخواستی که از یک برنامه PHP انجام میشود مستقل از دیگری است. وب سرور درخواست را به مترجم PHP منتقل میکند تا اسکریپت برنامه را اجرا نماید. این بدین معنی است که PHP بیش از یک اسکریپت را همزمان پردازش میکند.
جدول زمانی بالا سعی دارد این موضوع را با استفاده از یک تایملاین برجسته کند. ما سه فرایند همزمان در یک اسکریپت PHP داریم. آنها در زمانهای مختلف شروع شده و به پایان میرسند، اما اجرای آنها در برخی جاها با هم تداخل دارد.
به همین دلیل است که هنگام کار با یک برنامه PHP باید همزمانی را در نظر داشته باشید و همانند مدیریت حافظه تاثیر بالایی در PHP دارد. گرچه این چیزی نیست که همیشه باید به آن فکر کنید (اما خوب است که درباره آن بدانید).
مالتیتسکینگ
این نوع پردازش بدین صورت است که میتوانید مجموعهای از فرایندها را در صورت اجرای همزمان، سریعتر انجام دهید. برنامههای PHP برای انجام کارهای محاسباتی فشرده به این مفهوم بسیار وابسته هستند.
به همین دلیل پیادهسازی مالتیتسکینگ در PHP بسیار آسان است. همانطور که قبلا مشاهده کردیم، ممکن است مترجم PHP بیش از یک اسکریپت را همزمان پردازش کند. بنابراین مالتیتسکینگ به ایجاد درخواست برای این فرایندهای اضافی اشاره دارد که باید انجام دهیم.
اما این چگونه کار میکند؟ خوب همانطور که در نمودار بالا مشاهده میکنید، یک درخواست اولیه از برنامه PHP خود دارید. این درخواست اولیه باعث میشود برنامه PHP سایر درخواستهای فرعی را برای خود ایجاد کند. این درخواستهای فرعی دیگر جزء وظایف جدید برنامه ما هستند. چرا که همزمان با درخواست اولیه اجرا میشوند.
شایان ذکر است که این درخواستها اغلب به صورت غیرهمزمان ارسال میشوند. همچنین تأثیر زیادی بر زمان اجرای یک اسکریپت PHP ندارند. یعنی اینکه آنها در طول درخواست اولیه اجرای برنامه PHP را کند نمیکنند.
یک مثال خوب از مالتیتسکینگ در برنامههای PHP این است که چگونه CMSهای محبوب وظایف cron را انجام میدهند. در CMS، cron فرایندی است که شما در آینده برنامهریزی کردهاید (مانند انتشار یک پست) که برای مدیریت آن به CMS نیاز دارید. CMS با ایجاد درخواست برای یک URL خاص، هر زمان که به آن درخواست دهید، این job cron را اداره میکند.
گرسنگی منابع
همزمانی چندان بدون مشکل هم نیست و رایجترین آن گرسنگی منابع است. همانطور که از نامش پیداست، این به فرایندهای همزمان PHP اشاره میکند که منابع مورد نیاز برای اجرای درست را ندارند.
توجه داشته باشید که نشت حافظه (قبلا در مورد آن بحث کردیم) نوعی گرسنگی منابع است و افزایش آن حافظه سرور را گرسنه نگه میدارد تا زمانی که دیگر برنامه کار نکند. اما از آنجا که برنامههای PHP از نشت حافظه رنج نمیبرند، علت مشکل اینجا نیست.
بیایید فرض کنیم که سرور شما دارای 300 مگابایت حافظه است و برنامه PHP شما به طور متوسط 50 مگابایت حافظه را حین اجرای خود اشغال میکند. این بدان معناست که از نظر تئوری، سرور شما فقط میتواند شش اجرای همزمان را مدیریت کند.
بنابراین اگر سرور نیاز به پردازش بیش از شش اسکریپت به صورت همزمان داشته باشد، چه اتفاقی میافتد؟ در این صورت بستگی به نحوه پیکربندی سرور شما توسط هاست دارد. احتمالا فقط میتواند سرعت خود را کاهش دهد یا به طور کلی از کار بیافتد. (این ایده پشت اکثر حملات DOS است).
به همین دلیل گرسنگی منابع در برنامه PHP چندان مشکلی ایجاد نمیکند. زیرا میتوانید استفاده از حافظه را کاهش دهید و در نتیجه اجرای همزمان برنامه PHP خود را داشته باشید.
اما بهتر است سرور خود را طوری پیکربندی کنید که هرگز سعی نکند چندین اجرای همزمان را انجام دهد. به همین دلیل این یک تنظیمات پیکربندی مهم برای سرور برنامه PHP است. گرسنگی منابع بیشتر یک مشکل پیکربندی سرور است تا یک مشکل برنامه.
شرایط رقابتی
یکی دیگر از موضوعاتی که باید بر آن آگاهی داشته باشید شرایط رقابتی است. این مفهوم نسبتا پیچیدهای است که توضیح آن با مثال آسانتر است. یک مثال رایج از شرایط رقابتی، ردیابی view صفحات است.
در بالا جدول زمانی کد PHP وجود دارد که view صفحه را ردیابی میکند. کد ردیابی آن هم به رنگ صورتی است. در آن تعداد view فعلی صفحات را واکشی میکنیم، آن را یک بار افزایش میدهیم و سپس دوباره مقدار را ذخیره میکنیم. تا اینجا همه چیز بسیار ساده است.
حالا بیایید همزمانی را به ترکیب اضافه کنیم. اگر چندین اجرا در view صفحه ما برای ردیابی کد PHP وجود داشته باشد، چه اتفاقی میافتد؟ خوب بیایید جدول زمانی خود را به روز کرده و آن را بررسی کنیم.
در این جدول زمانی همه چیز خوب به نظر میرسد. ما چندین کد PHP را اجرا میکنیم، اما هیچ یک از نوارهای صورتی همپوشانی ندارند. هر اجرا میتواند تعداد viewهای صفحه فعلی را واکشی کرده، آن را یک عدد افزایش داده و سپس دوباره ذخیره کند.
شرایط رقابتی در عمل
اما اگر چندین اجرا در کد PHP ما با هم همپوشانی داشته باشند چه اتفاقی میافتد؟ به هر حال، واقع بینانه نیست که انتظار داشته باشیم اجراهای ما خوب پیش رود و همیشه اینگونه توزیع شود. اینجاست که همه چیز شروع به خراب شدن میکند.
در بالا یک جدول زمانی وجود دارد که در آن سه اجرا از نظر زمانی به یکدیگر نزدیکتر هستند. همانطور که میبینید، اجرای کد PHP ردیابی view صفحه در حال حاضر همپوشانی دارد. این همپوشانی کد در واقع همان شرایط رقابتی ماست.
به همین دلیل، افزایش view صفحه از فرآیند دوم نادرست است. زیرا تعداد view صفحات را قبل از اینکه اولین فرآیند آن را ذخیره نماید، واکشی میکند. بنابراین همان تعداد view صفحات را به عنوان اولین فرایند میشمارد.
اما این پایان کار نیست. به دلیل شرایط رقابتی موجود، فرایند سوم همچنین تعداد view صفحات ثبت شده توسط فرآیند دوم را بازنویسی میکند. اگر فرآیندهای همزمان بیشتری داشتیم، این مشکلات ناشی از شرایط رقابتی را تشدید میکرد.
نتیجه به ردیابی نادرست تعداد view صفحات ختم میشود. کد ردیابی view صفحه ما در نهایت تنها به دو view صفحه پایان داد، در صورتی که باید سه مورد را شمارش میکرد. اگر بخواهید چنین سیستمی را طراحی کنید، این یک مشکل جدی خواهد بود.
رفع شرایط رقابتی
چگونه میتوان شرایط رقابتی را در کد PHP ردیابی view صفحه خود برطرف کرد؟ راه حل واضح این است که از یک سرویس خارجی مانند Google Analytics استفاده نمایید. اما اگر میخواهید رفع شرایط رقابتی را بیاموزید، این یک راه حل مفید نیست.
خوب مشکل شرایط رقابتی این است که راههای بیشماری برای رفع آن وجود دارد و ما نمیتوانیم همه آنها را در این مقاله پوشش دهیم.
کلید رفع شرایط رقابتی، مفهوم محرومیت متقابل است. محرومیت متقابل خود جزء ویژگیهای موضوعی کلیتر به نام کنترل همزمانی است. و نقطه کنترل همزمان این است که اطمینان حاصل شود عملیات همزمان (مانند ردیابی view صفحات) نتایج صحیح را ایجاد میکند.
حال بیایید به محرومیت متقابل بازگردیم. محرومیت متقابل این است که فقط یک اجرا میتواند یک قطعه کد مهم را در یک زمان وارد کند. این از شرایط رقابتی جلوگیری میکند، زیرا نمیتوانید بیش از یک بار اجرای این قطعه کد را همزمان انجام دهید.
محرومیت متقابل با استفاده از قفل
چگونه میتوانیم محرومیت متقابل را با PHP پیادهسازی کنیم؟ یکی از روشهای متداول اجرای آن استفاده از قفل است. به این صورت که برای محدود کردن دسترسی به منابع از قفل استفاده میکنید. در این سناریو، این تعداد viewهای صفحات است که ما میخواهیم در یک زمان فقط یک فرایند به آن دسترسی داشته باشد.
جدول زمانی بالا نشان میدهد که هنگام استفاده از قفل چه اتفاقی برای کد ردیابی view صفحه میافتد. نوار زرد در جدول زمانی ما نشاندهنده زمان صرف شده در انتظار اجرای دیگر برای آزاد کردن قفل است. کد ردیابی view صفحه ما تنها زمانی میتواند شروع شود که این اتفاق بیفتد.
اما حتی در این صورت هم بیش از یک روش برای پیادهسازی قفل وجود دارد. به عنوان مثال، میتوانید از یک سمافور (semaphore) استفاده کنید. این یک متغیر یا نوع داده خاص است که میتوانید برای تعیین اینکه آیا یک منبع در حال استفاده است یا نه از آن بهره بگیرید. در PHP سمافورهایی وجود دارد، بنابراین این یکی از گزینههای موجود است.
محرومیت متقابل با استفاده از انتقال پیام
یکی دیگر از گزینهها علاوه بر قفل، استفاده از انتقال پیام است. انتقال پیام حول محور جداسازی یک قطعه کد مهم در برنامه وب شما میچرخد. وقتی کد شما ایزوله شد، دیگر هرگز نباید با این قطعه کد ارتباط داشته باشید. در عوض باید پیامی را به برنامه وب ارسال کند تا برای شما اجرا شود.
بنابراین در مورد کد ردیابی view صفحه، آن را در برنامه خود جدا میکنیم. سپس با کدی که پیام را به برنامه ما ارسال میکند، جایگزین میکنیم. این پیام به برنامه میگوید که کد مد نظر را برای ما اجرا نماید.
اما تفاوت آن با فراخوانی کد ردیابی view صفحه چیست؟ عنصر مهم در ارسال پیام این است که شما پیام را خارج از فرایند فعلی ارسال میکنید. روند فعلی شما قطعه کد مهمی را که باعث شرایط رقابتی میشود اجرا نمیکند.
در عوض این فرآیند دیگری است که آن را اجرا میکند. و این فرایند میتواند محرومیت متقابل را برای آن قطعه مهم کد اعمال کند. این در نهایت مانع از شرایط رقابتی میشود.
بسیاری از برنامههای PHP از صف پیام برای پیادهسازی سیستم پیامرسانی خود استفاده میکنند. به این صورت که برنامه PHP پیامی را به صف پیام ارسال میکند. سپس این صف هر پیام را یک به یک به ترتیب دریافت کرده و پردازش مینماید.
همچنین گزینههای زیادی از سیستمهای صف پیام وجود دارد. برخی از فریمورکها (مانند لاراول) نیز دارای سیستم صف پیام مخصوص به خود هستند. در هر صورت، این یک راه عالی برای پیادهسازی محرومیت متقابل در داخل برنامه PHP شما است.
برنامههای PHP منحصر به فرد هستند
همانطور که دیدید، برنامههای PHP به گونهای رفتار نمیکنند که بسیاری از توسعهدهندگان وب با آنها آشنا هستند.
با این وجود، برخی عناصر مشترک با سایر زبانهای برنامهنویسی وجود دارد. اما دانستن اینکه چه چیزی یکسان است و چه چیزی متفاوت است در نهایت اهمیت دارد. این به شما کمک میکند تا با استفاده از PHP معماری راه حلهای خود را بهتر بشناسید و آنها را انجام دهید.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید