آموزش Laravel Octane و Swoole/RoadRunner

پکیج Laravel Octane یکی از ابزار های جدید معرفی شده توسط تیم Laravel است که می‌تواند بطور چشمگیری سرعت پاسخگویی اپلیکیشن های ساخته شده با فریم ورک لاراول را بالا ببرد. این ابزار برای ارائه چنین عملکرد فوق العاده‌ای از یک افزونه php به نام Swoole یا RoadRunner استفاده می‌کند که در این پست شرح داده می‌شوند.

مستندات رسمی لاراول درباره Laravel Octane را می‌توانید از لینک زیر بخوانید.

https://laravel.com/docs/8.x/octane

پکیج Laravel Octane
پکیج Laravel Octane

لاراول بدون Octane

ابتدا Lifecycle یا چرخه اجرای فریم ورک لاراول را بدون Octane را بررسی کنیم. این چرخه بصورت خلاصه به شکل زیر است:

User Request --> Web Server --> PHP-FPM --> PHP files --> Output

در این حالت لاراول حتما به یک وب سرور مانند NGINX یا Apache HTTP Server نیاز دارد. درخواست های کاربران از طریق وب سرور دریافت می‌شود و از آنجا به PHP-FPM ارسال می‌شود.

در این چرخه، PHP-FPM به ازی هر درخواست از سوی وب سرور، پروژه (فایل های PHP) را کامپایل، اجرا و خروجی را به وب سرور بازمیگرداند.

مشکل این چرخه کامپایل و اجرای پروژه به ازای هر درخواست کاربر است. به این صورت حجم زیادی از پردازش های تکراری انجام می‌شود و منابعی که می‌توان مورد استفاده مجدد قرار بگیرند (مانند Connection به دیتابیس) آزاد و دوباره اشغال می‌شوند.

متاسفانه راهکار رسمی از طرف PHP برای حل این مشکل فعلا (نسخه ۸) در دسترسی نیست و این مشکل باعث کاهش شدید محبوبیت این زبان و مهاجرت شرکت های بزرگ به زبان ها و تکنولوژی های دیگر شده است.

با این وجود، framework لاراول جهت حل این مسئله پکیج Laravel Octane را ارائه کرده که می‌تواند سرعت اجرای پروژه های لاراول را بطور چشمگیری بالا ببرد و بر مشکلات ذکر شده غلبه کند.

پکیج Laravel Octane

همانطور که قبلا اشاره شد، پکیج Laravel Octane برای بهینه سازی پروژه های لاراول و افزایش سرعت آنها توسط تیم لاراول ارائه شده است. این پکیج برای رسیدن به چنین عملکردی از یکی از ابزار های Swoole یا RoadRunner (بعنوان Driver) به دلخواه کاربر استفاده می‌کند. در این پست کوتاه در مورد RoadRunner و سپس درباره Swoole (روش پیشنهادی نویسنده) مفصل تر توضیح داده ‌می‌شود.

درایور RoadRunner

ابزار RoadRunner یک اپلیکیشن سرور، Reverse Proxy و Load Balancer برای پروژه های PHP است. این ابزار با زبان Go نوشته شده و از قابلیت های همزمانی (Concurrency) آن (Goroutine ها) استفاده می‌کند تا بر مشکل ذکر شده غلبه کند.

با استفاده از RoadRunner پروژه PHP تنها یکبار Compile و اجرا می‌شود. پس اجرا شدن پروژه ارتباطی از طریق Socket بین پروژه و RoadRunner برقرار می‌شود. به ازای هر درخواست کاربر یک Goroutine (مشابه Thread اما سبکتر و در فضای User) در RoadRunner ایجاد می‌شود و در آن Goroutine درخواست به اپلیکیشن PHP ارسال می‌شود و در اپلیکیشن یک تابع (Controller) اجرا و پاسخ به Goroutine و از آنجا به کاربر نهایی بازگردانده می‌شود.

بطور خلاصه می‌توان چرخه اجرای اپلیکیشن PHP با استفاده از RoadRunner را به صورت زیر در نظر گرفت.

User Request --> RoadRunner --> PHP function (controller) --> Output

با وجود RoadRunner دیگر نیازی به NGINX نیست و RoadRunner خود نقش وب سرور را نیز ایفا می‌کند.

درایور Swoole (پیشنهادی)

یکی از تلاش های Community زبان PHP برای حل مشکلات ذکر شده و همچنین عدم وجود قابلیت های همزمانی Extension یا افزونه Swoole است که با زبان C پیاده سازی شده است. این افزونه قابلیت های همزمانی (Coroutine ها) و یک سری ابزار مفید مانند HTTP Handler، وب سوکت، MQTT Server و ... را به PHP اضافه می‌کند.

قابلیت HTTP Handler مهمترین قابلیت این افزونه و قابلیت مورد استفاده Laravel Octane است. با استفاده از این قابلیت، خود PHP می‌تواند درخواست های کاربر را دریافت کند و دیگر به نیازی به وب سرور نیست.

نمونه کد زیر به PHP خام نوشته شده و مثالی برای استفاده از Swoole در PHP است.

<?php

use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;

$server = new Swoole\HTTP\Server(&quot127.0.0.1&quot, 8080);

// تنها یکبار بهنگام اجرای اپلیکیشن فراخوانی می‌شود
$server->on(&quotStart&quot, function(Server $server)
{
    echo &quotSwoole http server is started at http://127.0.0.1:8080\n&quot
});

// به ازای هر درخواست کاربر فراخوانی می‌شود
$server->on(&quotRequest&quot, function(Request $request, Response $response)
{
    $response->header(&quotContent-Type&quot, &quottext/html&quot);
    $response->end(&quot<p>Hello World</p>&quot);
});

$server->start();

کد بالا بدون نیاز به هیچ وب سروری با دستور زیر قابل اجرا است.

php index.php

با دستور فوق، فایل PHP تنها یکبار Compile و اجرا می‌شود. به ازای هر درخواست از سوی کاربر، افزونه Swoole در PHP یک Coroutine (مشابه Goroutine ها در زبان Go) ایجاد و تابع مربوط (در کد بالا) را فراخوانی و جواب تابع (Hello World) به کاربر بازگردانده می‌شود.

بطور خلاصه چرخه اجرای پروژه با استفاده از HTTP Handler افزونه Swoole بصورت زیر است.

User Request --> PHP function (controller) --> Output

این چرخه مشابه چرخه‌ی اجرا در زبان Go و تکنولوژی Node.js است و همان چرخه‌ای است که از یک زبان امروزی انتظار داریم.

اجرای لاراول با Octane

در این بخش نحوه اجرای پروژه های مبتنی بر فریم ورک لاراول با استفاده از پکیج Laravel Octane و Docker را توضیح می‌دهیم. اگر با روش اجرای لاراول با داکر و داکر کمپوز آشنا نیستید می‌توانید پست توسعه و اجرای پروژه های لاراول با داکر و داکر-کمپوز را بخوانید.

در لینک زیر فایل docker-compose.yml و دیگر فایل های مورد نیاز برای اجرای لاراول با استفاده از Laravel Octane آماده شده است. آنرا دانلود و به پروژه خود اضافه کنید.

https://github.com/miladrahimi/laravel-docker-compose/tree/main/php-8.0-octane

در فایل docker-compose.yml موجود در لینک بالا، دیگر خبری از کانتینر NGINX نیست و Port اپلیکیشن همان Port کانتینر PHP خواهد بود که می‌توانید در فایل .env پروژه با نام PHP_EXPOSED_PORT ست کنید. اگر Dockerfile مربوط به PHP را هم بررسی کنید نحوه نصب افزونه Swoole را می‌بینید.

پس از ساخت فایل .env متغیر OCTANE_SERVER را با مقدار "swoole" به آن اضافه کنید و دستورات زیر را برای اجرای لاراول انجام دهید.

docker-compose build
// دانلود و ساخت ایمیج های داکر

docker run --rm -it --volume $(pwd):/app sample_php composer install
// نصب پکیج ها با استفاده از کمپوزر درون ایمیج پروژه

docker run --rm -it --volume $(pwd):/app sample_php composer require laravel/octane
// نصب پکیج لاراول اکتان

docker run --rm -it --volume $(pwd):/app sample_php php artisan key:generate
// ساخت کلید امنیتی پروژه

docker-compose up -d
// اجرای پروژه با داکر 

docker-compose ps
// نمایش پورت های اکسپوز شده

نحوه اجرا شدن لاراول با Laravel Octane

با اجرا شدن پروژه، یک Object از کلاس Application لاراول (app$) ساخته می‌شود. یکبار همه Service Provider های لاراول register و boot می‌شود و لاراول (app$) آماده پاسخگویی به درخواست های کاربر می‌شود.

پس از دریافت درخواست کاربر لاراول یک clone از object اپلیکیشن (app$) ایجاد می‌کند و درخواست را به آن می‌دهد و خروجی را از آن دریافت می‌کند.

User Request --> Laravel --> clone $app object --> router --> controller -> ...

به این صورت مراحل ایجاد object اپلیکیشن و راه اندازی لاراول تنها یکبار صورت می‌گیرد و در ادامه با درخواست کاربر مستقیما Router و Controller مربوطه اجرا و پاسخ به کاربر باز می‌گردد.

اجرای لاراول با استفاده از Laravel Octane حدود ۱۰ برابر سریع تر از اجرای آن با PHP-FPM است. البته لازم به ذکر است این ضریب حدودی است و مقدار دقیق آن کاملا وابسته به کد های پروژه می‌باشد.

چالش های Laravel Octane

در انتها باید گفت اگر سرعت چشمگیر Laravel Octane شما را مجذوب کرده اما باید مراقب عادات همیشگی خود در PHP باشید! بخاطر داشته باشید که بسیاری از قسمت های لاراول در طول سرویس دهی تنها یکبار اجرا می‌شوند.

از مواردی مانند تابع ()auth و ()request که مختص به درخواست است در Service Provider ها که تنها یکبار اجرا می‌شوند استفاده نکنید.

مراقب Property های static در کلاس های پروژه باشید. مقدار آنها برای درخواست های کاربران بعدی نیز باقی می‌ماند و ممکن است از لحاظ امنیتی و عملکردی مشکل ساز شوند. اگر این متغیر ها آرایه باشند، علاوه بر آن می‌توانند باعث Memory Leak (نشت حافظه)‌ شوند. به این صورت که آنقدر بزرگ شوند که کل RAM سرور را پر کنند و سرور از سرویس‌دهی خارج شود.

مثال زیر از مستندات رسمی Laravel را در نظر بگیرید.

class UsersController {
    public function index()
    {
        Service::$data[] = Str::random(10);
        // ...
    }
}

هر بار با درخواست کاربر اکشن index از کنترلر UsersController اجرا می‌شود و یک مقدار تصادفی به آرایه data از کلاس Service اضافه می‌شود. در چرخه لاراول بدون Octane به دلیل اینکه همه چیز از اول کامپایل و اجرا می‌شد، مثال بالا هیچ خطری نداشت. اما اکنون پروژه تنها یکبار کامپایل و اجرا می‌شود و آرایه data برای درخواست های بعدی هم باقی می‌ماند و علاوه بر خطر امنیتی، به ازای هر درخواست یک آیتم جدید به آن اضافه و سنگین تر می‌شود و پس از تعدادی درخواست RAM سرور را بطور کامل اشغال می‌کند.

قابلیت های Swoole و Laravel Octane

در این بخش قابلیت هایی از پکیج Laravel Octane را معرفی می‌کنیم که تنها با درایور Swoole در دسترس هستند و درایور RoadRunner قادر به فراهم کردن آنها نیست. این قابلیت به شرح زیر هستند و هر کدام به بخش مربوطه در مستندات رسمی لاراول لینک شده اند.

  • اجرای همزمان توابع: با استفاده از این قابلیت می‌توانید چند تابع که کار های سنگین مانند کار با فایل و فراخوانی API را با همزمان فراخوانی کنید و به محض پایان یافتن همه آنها آرایه‌ای از نتایج بازگردانده می‌شود.
  • اجرای دوره‌ای توابع: با استفاده از این قابلیت می‌تواند یک تابع را بصورت دوره‌ای (برای مثال هر ۵ دقیقه یکبار) اجرا کنید. این قابلیت می‌تواند جایگزین Scheduled Tasks باشد.
  • درایور Cache: از این پس به جای Redis یا فایل‌ها می‌توانید از درایور octane برای Cache استفاده کنید.

قابلیت های آینده

افزونه Swoole قابلیت های بسیار کاربردی و مفیدی بخصوص در زمینه همزمانی برای PHP فراهم می‌کند و تعدادی از آنها مورد استقبال پکیج Laravel Octane قرار گرفته است. با این وجود، متاسفانه پکیج Octane قابلیت بسیار مهم Coroutine های Swoole را برای کاربران غیرفعال کرده که امیدواریم در آینده آنها را نیز فعال کند تا باز هم برنامه نویس های PHP را به نسل جدیدتری هدایت کند.

قابلیت Coroutine های افزونه Swoole همانند Goroutine ها در زبان Go به برنامه نویس ها امکان ایجاد Thread های سبک سمت User را فراهم ‌می‌کند. با این قابلیت، می‌توان بدون نیاز به Job های پیچیده لاراول، بسیاری از کار ها بصورت async انجام داد.

این قابلیت از سوی Laravel Octane بصورت پیشفرض غیرفعال شده و برنامه نویس ها بصورت پیشفرض نمی‌توانند از آن استفاده کنند. اما به احتمال زیاد، تیم لاراول در نسخه های بعدی Facade ها و API هایی جهت کار با آنها را پیاده سازی خواهد کرد.

سخن پایانی

در این مقاله، پکیج Laravel Octane معرفی شد. امکاناتی که این پکیج در اختیار لاراول قرار می‌دهد در دو بخش، یکی افزایش سرعت پاسخگویی که با هر دو درایور Swoole و RoadRunner قابل اجرا بود و دیگر امکاناتی نظیر همزمانی، Cache و اجرای دوره‌ای بود که تنها درایور Swoole ارائه می‌کرد.

در Benchmark های موجود در اینترنت این افزایش سرعت حدود ۱۰ برابر برای فراخوانی یک API ساده از اپلیکیشن تخمین زده شده است. اما بسته به پروژه و بهینه بودن آن این افزایش سرعت می‌تواند بیشتر یا کمتر باشد.

اجرای لاراول با استفاده از Laravel Octane می‌تواند یکی از مواردی باشد که در پروژه بعدی خود در نظر بگیرید و در صورتی که تجربه‌ای در این زمینه دارید لطفا در دیدگاه های این پست بنویسید.