Laravel Octane với FrankenPHP: Tăng Tốc Ứng Dụng Gấp 10 Lần
·
8 min read
Giới Thiệu
Laravel Octane là một package chính thức của Laravel giúp tăng tốc ứng dụng bằng cách giữ application trong bộ nhớ giữa các request, thay vì khởi tạo lại từ đầu như PHP-FPM truyền thống.
FrankenPHP là application server mới nhất được Octane hỗ trợ, được xây dựng trên Go và Caddy, mang lại hiệu năng vượt trội cùng với nhiều tính năng hiện đại như HTTP/3, Early Hints, và worker mode.
Tại Sao Cần Octane?
Trong PHP-FPM truyền thống:
Request 1 → Bootstrap → Handle → Response → Destroy
Request 2 → Bootstrap → Handle → Response → Destroy
Request 3 → Bootstrap → Handle → Response → Destroy
Với Octane:
Bootstrap (1 lần)
↓
Request 1 → Handle → Response
Request 2 → Handle → Response
Request 3 → Handle → Response
Việc loại bỏ quá trình bootstrap lặp đi lặp lại giúp giảm đáng kể thời gian response.
So Sánh Các Driver
Swoole
// Ưu điểm
- Hiệu năng cao nhất (benchmark)
- Coroutines support
- WebSocket built-in
- Concurrent tasks
// Nhược điểm
- Cần cài extension PHP
- Không tương thích với một số packages
- Debug khó hơn
RoadRunner
// Ưu điểm
- Không cần PHP extension
- Dễ cài đặt
- Plugin system linh hoạt
// Nhược điểm
- Binary size lớn
- Không có coroutines
- Hiệu năng thấp hơn Swoole
FrankenPHP
// Ưu điểm
- HTTP/3 và Early Hints native
- Automatic HTTPS
- Worker mode hiệu quả
- Tích hợp Caddy server
- Không cần extension
- Docker-friendly
// Nhược điểm
- Mới, community nhỏ hơn
- Một số edge cases chưa được cover
Cài Đặt FrankenPHP
Yêu Cầu Hệ Thống
- PHP 8.2+
- Laravel 10+
- Docker (khuyến nghị)
Cài Đặt Qua Docker
# Dockerfile
FROM dunglas/frankenphp:latest-php8.3
# Cài đặt extensions cần thiết
RUN install-php-extensions \
pcntl \
pdo_mysql \
redis \
opcache \
intl \
zip
# Copy application
COPY . /app
# Set working directory
WORKDIR /app
# Install composer dependencies
RUN composer install --no-dev --optimize-autoloader
# Cấu hình Octane
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
Docker Compose
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "443:443"
- "80:80"
volumes:
- ./:/app
- caddy_data:/data
- caddy_config:/config
environment:
- APP_ENV=production
- OCTANE_SERVER=frankenphp
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: laravel
MYSQL_ROOT_PASSWORD: secret
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:alpine
volumes:
- redis_data:/data
volumes:
caddy_data:
caddy_config:
mysql_data:
redis_data:
Cài Đặt Octane
# Cài đặt package
composer require laravel/octane
# Publish config
php artisan octane:install
# Chọn FrankenPHP khi được hỏi
Cấu Hình Octane
// config/octane.php
return [
'server' => env('OCTANE_SERVER', 'frankenphp'),
'https' => env('OCTANE_HTTPS', true),
'workers' => env('OCTANE_WORKERS', 'auto'),
'max_requests' => env('OCTANE_MAX_REQUESTS', 1000),
'listeners' => [
WorkerStarting::class => [
EnsureUploadedFilesAreValid::class,
EnsureUploadedFilesCanBeMoved::class,
],
RequestReceived::class => [
// Custom listeners
],
RequestHandled::class => [
// Cleanup sau mỗi request
],
RequestTerminated::class => [
FlushTemporaryContainerInstances::class,
],
],
// Warm these instances on worker start
'warm' => [
...Octane::defaultServicesToWarm(),
// Custom services
],
// Flush these instances between requests
'flush' => [
// Services cần reset
],
// Tables for concurrent state (Swoole only)
'tables' => [
'cache' => [
'columns' => [
['name' => 'value', 'type' => 'string', 'size' => 10000],
['name' => 'expiration', 'type' => 'int'],
],
'rows' => 1000,
],
],
];
Cấu Hình FrankenPHP Chi Tiết
Caddyfile
# Caddyfile
{
# Global options
frankenphp {
worker {
file ./public/index.php
num {$FRANKENPHP_WORKERS:auto}
env APP_ENV production
}
}
# Automatic HTTPS
auto_https on
# HTTP/3 support
servers {
protocols h1 h2 h3
}
}
:443, :80 {
root * /app/public
# Compression
encode zstd gzip
# Static file serving với cache
@static {
file
path *.css *.js *.ico *.gif *.jpg *.jpeg *.png *.svg *.woff *.woff2
}
handle @static {
header Cache-Control "public, max-age=31536000, immutable"
file_server
}
# Early Hints cho critical resources
push /css/app.css
push /js/app.js
# PHP handling
php_server {
resolve_root_symlink
}
}
Environment Variables
# .env
OCTANE_SERVER=frankenphp
OCTANE_WORKERS=auto
OCTANE_MAX_REQUESTS=1000
OCTANE_HTTPS=true
# FrankenPHP specific
FRANKENPHP_WORKERS=auto
FRANKENPHP_CONFIG="worker ./public/index.php"
Tối Ưu Hiệu Năng
1. Service Warming
// app/Providers/OctaneServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Laravel\Octane\Facades\Octane;
class OctaneServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Warm database connections
Octane::warm([
\Illuminate\Database\DatabaseManager::class,
]);
// Pre-compile views
Octane::tick('view-compiler', function () {
// Compile frequently used views
})->seconds(60);
}
}
2. Memory Management
// Flush services giữa các requests
// config/octane.php
'flush' => [
// Reset auth guard
\Illuminate\Auth\AuthManager::class,
// Reset session
\Illuminate\Session\SessionManager::class,
// Custom services có state
\App\Services\CartService::class,
],
3. Tránh Memory Leaks
// ❌ Sai - Static property accumulates
class BadService
{
private static array $cache = [];
public function process($data): void
{
self::$cache[] = $data; // Memory leak!
}
}
// ✅ Đúng - Reset sau mỗi request
class GoodService
{
private array $cache = [];
public function process($data): void
{
$this->cache[] = $data;
}
public function flush(): void
{
$this->cache = [];
}
}
// Đăng ký flush
Octane::onRequestTerminated(function () {
app(GoodService::class)->flush();
});
4. Concurrent Tasks
use Laravel\Octane\Facades\Octane;
// Chạy nhiều tasks đồng thời
[$users, $orders, $analytics] = Octane::concurrently([
fn () => User::active()->get(),
fn () => Order::recent()->get(),
fn () => Analytics::dashboard(),
]);
// Với timeout
$results = Octane::concurrently([
'users' => fn () => User::all(),
'posts' => fn () => Post::published()->get(),
], 5000); // 5 seconds timeout
5. Caching Thông Minh
// app/Services/CacheWarmer.php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use Laravel\Octane\Facades\Octane;
class CacheWarmer
{
public function warmOnWorkerStart(): void
{
// Cache config và routes
$this->warmConfig();
$this->warmRoutes();
$this->warmFrequentData();
}
private function warmConfig(): void
{
// Pre-load config vào memory
config()->all();
}
private function warmRoutes(): void
{
// Pre-compile routes
app('router')->getRoutes()->refreshNameLookups();
}
private function warmFrequentData(): void
{
// Cache data thường xuyên truy cập
Cache::remember('settings', 3600, fn () =>
Setting::all()->pluck('value', 'key')
);
Cache::remember('categories', 3600, fn () =>
Category::with('children')->whereNull('parent_id')->get()
);
}
}
Benchmark Thực Tế
Setup Test
# Cài đặt wrk
apt install wrk
# Test endpoint
wrk -t12 -c400 -d30s http://localhost/api/benchmark
Kết Quả So Sánh
| Metric | PHP-FPM | RoadRunner | Swoole | FrankenPHP |
|---|---|---|---|---|
| Requests/sec | 850 | 3,200 | 4,500 | 4,100 |
| Latency (avg) | 45ms | 12ms | 8ms | 9ms |
| Memory | 512MB | 256MB | 280MB | 240MB |
| CPU Usage | 85% | 60% | 55% | 58% |
Benchmark Script
// routes/api.php
Route::get('/benchmark', function () {
// Simulate database query
$users = User::limit(10)->get();
// Simulate cache hit
$settings = Cache::remember('bench_settings', 60, fn () => [
'app_name' => config('app.name'),
'timestamp' => now(),
]);
// Simulate JSON response
return response()->json([
'users' => $users,
'settings' => $settings,
'memory' => memory_get_usage(true),
]);
});
HTTP/3 và Early Hints
Cấu Hình HTTP/3
{
servers {
protocols h1 h2 h3
}
}
:443 {
# Enable HTTP/3
header Alt-Svc `h3=":443"; ma=86400`
# Your config...
}
Early Hints
// app/Http/Middleware/EarlyHints.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class EarlyHints
{
public function handle(Request $request, Closure $next)
{
// Gửi Early Hints cho critical resources
if ($request->expectsHtml()) {
$this->sendEarlyHints([
'</css/app.css>; rel=preload; as=style',
'</js/app.js>; rel=preload; as=script',
'</fonts/inter.woff2>; rel=preload; as=font; crossorigin',
]);
}
return $next($request);
}
private function sendEarlyHints(array $links): void
{
if (function_exists('headers_send_early')) {
headers_send_early(['Link' => implode(', ', $links)]);
}
}
}
Xử Lý Lỗi Thường Gặp
1. Memory Limit
// Tăng memory limit
ini_set('memory_limit', '512M');
// Hoặc trong octane.php
'max_requests' => 500, // Restart worker sau 500 requests
2. Database Connection Lost
// app/Listeners/RefreshDatabaseConnection.php
namespace App\Listeners;
use Illuminate\Support\Facades\DB;
class RefreshDatabaseConnection
{
public function handle(): void
{
// Reconnect nếu connection bị mất
try {
DB::connection()->getPdo();
} catch (\Exception $e) {
DB::reconnect();
}
}
}
3. Session Issues
// Sử dụng database hoặc redis session
SESSION_DRIVER=redis
// config/session.php
'driver' => env('SESSION_DRIVER', 'redis'),
'connection' => 'session',
Production Deployment
Health Check
// routes/web.php
Route::get('/health', function () {
return response()->json([
'status' => 'healthy',
'octane' => true,
'server' => config('octane.server'),
'workers' => env('OCTANE_WORKERS', 'auto'),
]);
});
Graceful Shutdown
# Restart workers gracefully
php artisan octane:reload
# Stop server
php artisan octane:stop
Monitoring
// Ghi log performance metrics
Octane::tick('metrics', function () {
Log::channel('metrics')->info('Worker stats', [
'memory' => memory_get_usage(true),
'peak_memory' => memory_get_peak_usage(true),
'requests_handled' => app('octane.request_count') ?? 0,
]);
})->seconds(30);
Kết Luận
FrankenPHP là lựa chọn tuyệt vời cho Laravel Octane với:
- Dễ cài đặt: Không cần PHP extension
- Modern: HTTP/3, Early Hints, automatic HTTPS
- Hiệu năng cao: Gần bằng Swoole
- Docker-friendly: Single binary, dễ deploy
Khuyến nghị:
- Development: FrankenPHP (dễ setup)
- Production nhỏ-vừa: FrankenPHP
- Production lớn cần WebSocket: Swoole
- Không muốn extension: RoadRunner hoặc FrankenPHP