تحسين أداء الخادم
مقدمة في تحسين أداء الخادم
تحسين أداء جانب الخادم أمر حيوي لتقديم تطبيقات ويب سريعة وموثوقة. بينما يحسن التحسين من جانب العميل تجربة المستخدم، يؤثر أداء الخادم بشكل مباشر على قابلية التوسع وكفاءة التكلفة والقدرة على التعامل مع المستخدمين المتزامنين. في هذا الدرس الشامل، سنستكشف تقنيات متقدمة لتحسين PHP وNode.js وعمليات قواعد البيانات لتحقيق أقصى أداء.
تحسين الأداء ليس فقط عن السرعة—إنه يتعلق بكفاءة الموارد وتقليل التكلفة وتوفير تجربة متسقة تحت أحمال متفاوتة. الخادم المُحسَّن جيداً يمكنه التعامل مع 10 أضعاف حركة المرور بنفس العتاد، مما يقلل تكاليف البنية التحتية ويحسن الموثوقية.
ضبط أداء PHP
PHP هي واحدة من أكثر لغات جانب الخادم استخداماً، وتدعم منصات مثل WordPress وLaravel وMagento. ومع ذلك، فإن طبيعة PHP المفسرة تعني أن التحسين ضروري لبيئات الإنتاج.
تكوين OpCache
OpCache هي ذاكرة التخزين المؤقت للبايت كود المدمجة في PHP والتي تحسن الأداء بشكل كبير عن طريق تخزين البايت كود المترجم مسبقاً في الذاكرة. بدون OpCache، يجب على PHP تحليل وترجمة النصوص في كل طلب.
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.fast_shutdown=1
opcache.enable_cli=1
opcache.jit=tracing
opcache.jit_buffer_size=128M
شرح إعدادات OpCache الرئيسية:
- opcache.memory_consumption: كمية الذاكرة المخصصة لـ OpCache (256 ميجابايت جيدة لمعظم التطبيقات)
- opcache.max_accelerated_files: الحد الأقصى لعدد الملفات للتخزين المؤقت (اضبطه أعلى من إجمالي ملفات PHP لديك)
- opcache.validate_timestamps=0: تعطيل فحص الطوابع الزمنية في الإنتاج لأقصى أداء
- opcache.jit: التجميع في الوقت المناسب (PHP 8.0+) لأداء أفضل
تحسين PHP-FPM
PHP-FPM (FastCGI Process Manager) يدير عمليات العمل في PHP. التكوين الصحيح ضروري للتعامل مع الطلبات المتزامنة بكفاءة.
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
pm.process_idle_timeout = 10s
request_terminate_timeout = 300
rlimit_files = 65536
أوضاع مدير العمليات:
- dynamic: يضبط عدد العمال بناءً على الطلب (موصى به لمعظم الحالات)
- static: عدد ثابت من العمال (استخدم للمواقع عالية الحركة ويمكن التنبؤ بها)
- ondemand: ينشئ عمال فقط عند الحاجة (جيد للمواقع منخفضة الحركة)
إدارة الذاكرة
تسريبات الذاكرة في PHP والكود غير الفعال يمكن أن يؤدي إلى تدهور الأداء بمرور الوقت. إليك استراتيجيات التحسين:
// سيء: تحميل مجموعة البيانات بالكامل في الذاكرة
$users = User::all(); // يحمل 100,000+ سجل
foreach ($users as $user) {
// معالجة المستخدم
}
// جيد: معالجة مجزأة للحد من الذاكرة
User::chunk(1000, function ($users) {
foreach ($users as $user) {
// معالجة المستخدم
}
});
// أفضل: التحميل الكسول بالمؤشر
foreach (User::cursor() as $user) {
// معالجة مستخدم واحد في كل مرة
}
// تنظيف الذاكرة
unset($largeArray);
gc_collect_cycles(); // إجبار جمع القمامة
تحسين أداء Node.js
Node.js يتفوق في التعامل مع الاتصالات المتزامنة بنموذج I/O غير الحاجب القائم على الأحداث. ومع ذلك، فإنه يتطلب استراتيجيات تحسين مختلفة عن PHP.
وضع المجموعة لاستخدام متعدد النوى
بشكل افتراضي، Node.js يعمل على نواة CPU واحدة. استخدم وحدة cluster لاستخدام جميع النوى المتاحة:
const os = require('os');
const express = require('express');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
console.log(`Master process ${process.pid} spawning ${numCPUs} workers`);
// إنشاء عمال
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// إعادة تشغيل العامل عند التعطل
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died, restarting...`);
cluster.fork();
});
} else {
// عملية العامل
const app = express();
app.get('/', (req, res) => res.send('Hello from worker ' + process.pid));
app.listen(3000, () => console.log(`Worker ${process.pid} started`));
}
تحسين حلقة الأحداث
حلقة الأحداث في Node.js يمكن أن تُحظر بواسطة عمليات كثيفة CPU. حافظ على حلقة الأحداث حرة:
app.get('/compute', (req, res) => {
let result = 0;
for (let i = 0; i < 1e9; i++) {
result += i; // يحظر حلقة الأحداث لثوان
}
res.json({ result });
});
// جيد: تفريغ إلى خيوط عمل
const { Worker } = require('worker_threads');
app.get('/compute', (req, res) => {
const worker = new Worker('./compute-worker.js');
worker.on('message', result => res.json({ result }));
worker.on('error', err => res.status(500).json({ error: err.message }));
});
// أفضل: استخدم قائمة انتظار الوظائف للمهام الثقيلة
const Queue = require('bull');
const computeQueue = new Queue('compute');
app.get('/compute', async (req, res) => {
const job = await computeQueue.add({ data: req.body });
res.json({ jobId: job.id, status: 'processing' });
});
تحسين استعلامات قاعدة البيانات
استعلامات قاعدة البيانات غالباً ما تكون عنق الزجاجة الأساسي في أداء تطبيقات الويب. تحسين الاستعلامات يمكن أن يوفر تحسينات أداء من 10 إلى 100 ضعف.
تحليل الاستعلام والفهرسة
استخدم EXPLAIN لتحليل أداء الاستعلام وتحديد الفهارس المفقودة:
EXPLAIN SELECT * FROM users
WHERE email = 'user@example.com'
AND status = 'active'
AND created_at > '2025-01-01';
-- إنشاء فهرس مركب
CREATE INDEX idx_users_email_status_created
ON users(email, status, created_at);
-- التحقق من استخدام الفهرس
EXPLAIN SELECT * FROM users
WHERE email = 'user@example.com'
AND status = 'active'
AND created_at > '2025-01-01';
مشكلة N+1
مشكلة N+1 تحدث عندما تنفذ استعلاماً واحداً لجلب السجلات، ثم N استعلامات إضافية لجلب البيانات المرتبطة:
// سيء: استعلامات N+1 (1 + 100 استعلام)
$posts = Post::all(); // استعلام واحد
foreach ($posts as $post) {
echo $post->author->name; // 100 استعلام إضافي
}
// جيد: التحميل المسبق (2 استعلام إجمالي)
$posts = Post::with('author')->get(); // استعلام واحد للمنشورات + واحد للمؤلفين
foreach ($posts as $post) {
echo $post->author->name; // بدون استعلامات إضافية
}
// أفضل: تحميل مسبق للعلاقات المتداخلة
$posts = Post::with(['author', 'comments.user', 'tags'])->get();
تخزين نتائج الاستعلام مؤقتاً
خزن نتائج الاستعلامات المكلفة مؤقتاً لتجنب الوصول المتكرر إلى قاعدة البيانات:
use Illuminate\Support\Facades\Cache;
// التخزين المؤقت لمدة ساعة واحدة
$popularPosts = Cache::remember('popular_posts', 3600, function () {
return Post::where('views', '>', 1000)
->orderBy('views', 'desc')
->take(10)
->get();
});
// إبطال ذاكرة التخزين المؤقت عند تغيير البيانات
public function updatePost($id)
{
$post = Post::find($id);
$post->update(request()->all());
// مسح ذاكرة التخزين المؤقت ذات الصلة
Cache::forget('popular_posts');
Cache::forget("post_{$id}");
}
تجميع الاتصالات
إنشاء اتصالات قاعدة البيانات مكلف. تجميع الاتصالات يعيد استخدام الاتصالات عبر الطلبات، مما يحسن الأداء بشكل كبير.
تكوين تجميع اتصالات MySQL
const mysql = require('mysql2');
const pool = mysql.createPool({
host: 'localhost',
user: 'dbuser',
password: 'password',
database: 'myapp',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
enableKeepAlive: true,
keepAliveInitialDelay: 10000
});
// استخدم التجميع للاستعلامات
pool.query('SELECT * FROM users WHERE id = ?', [userId], (err, results) => {
if (err) throw err;
console.log(results);
});
تجميع اتصالات Redis
// إنشاء عميل Redis مع تجميع الاتصالات
const client = redis.createClient({
socket: {
host: 'localhost',
port: 6379,
reconnectStrategy: (retries) => Math.min(retries * 50, 500)
},
database: 0
});
await client.connect();
// استخدام الاتصال
await client.set('key', 'value', { EX: 3600 });
const value = await client.get('key');
ضغط الاستجابة
ضغط استجابات HTTP يقلل من استخدام النطاق الترددي ويحسن أوقات التحميل. Gzip وBrotli هما أكثر خوارزميات الضغط شيوعاً.
ضغط Express.js
const express = require('express');
const app = express();
// تمكين الضغط لجميع الاستجابات
app.use(compression({
level: 6, // مستوى الضغط (0-9، 6 متوازن)
threshold: 1024, // ضغط الاستجابات فقط > 1KB
filter: (req, res) => {
// لا تضغط إذا لم يقبل العميل الترميز
if (req.headers['x-no-compression']) return false;
return compression.filter(req, res);
}
}));
ضغط Nginx
للحصول على أداء أفضل، تعامل مع الضغط على مستوى خادم الويب:
# ضغط Gzip
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/atom+xml image/svg+xml;
# ضغط Brotli (إذا كانت الوحدة متاحة)
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss;
}
المعالجة غير المتزامنة
انقل المهام التي تستغرق وقتاً طويلاً من دورة الطلب والاستجابة لتحسين الأداء المتصور وقابلية التوسع.
قائمة انتظار الوظائف مع Redis
const Queue = require('bull');
const emailQueue = new Queue('email', { redis: { port: 6379, host: 'localhost' } });
app.post('/register', async (req, res) => {
// إنشاء مستخدم (سريع)
const user = await User.create(req.body);
// قائمة انتظار إرسال البريد الإلكتروني (غير متزامن)
await emailQueue.add({
to: user.email,
template: 'welcome',
data: { name: user.name }
}, {
attempts: 3,
backoff: { type: 'exponential', delay: 5000 }
});
res.json({ success: true, user });
});
// العامل: معالجة الوظائف
emailQueue.process(async (job) => {
const { to, template, data } = job.data;
await sendEmail(to, template, data);
return { sent: true };
});
نظام قائمة الانتظار في Laravel
// إنشاء فئة الوظيفة
php artisan make:job SendWelcomeEmail
// فئة الوظيفة
class SendWelcomeEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $timeout = 30;
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function handle()
{
Mail::to($this->user->email)->send(new WelcomeEmail($this->user));
}
}
// إرسال الوظيفة
SendWelcomeEmail::dispatch($user);
مراقبة الأداء
المراقبة المستمرة ضرورية لتحديد اختناقات الأداء والتراجعات.
مراقبة أداء التطبيق (APM)
require('newrelic');
// تتبع المعاملات المخصصة
const newrelic = require('newrelic');
app.get('/api/complex', (req, res) => {
newrelic.startSegment('database-query', true, async () => {
const results = await db.query('SELECT * FROM large_table');
res.json(results);
});
});
مقاييس الأداء المخصصة
// Laravel: تسجيل الاستعلامات البطيئة
DB::listen(function ($query) {
if ($query->time > 1000) { // > 1 ثانية
Log::warning('Slow query detected', [
'sql' => $query->sql,
'bindings' => $query->bindings,
'time' => $query->time
]);
}
});
الملخص وأفضل الممارسات
تحسين أداء جانب الخادم يتطلب نهجاً شاملاً:
- فعّل OpCache وقم بتكوين PHP-FPM بشكل صحيح لتطبيقات PHP
- استخدم التجميع لاستخدام جميع نوى CPU في تطبيقات Node.js
- حسّن استعلامات قاعدة البيانات بالفهرسة الصحيحة والتحميل المسبق
- نفذ تجميع الاتصالات لاتصالات قاعدة البيانات وذاكرة التخزين المؤقت
- فعّل الضغط على مستوى خادم الويب للحصول على أفضل أداء
- انقل المهام الثقيلة إلى قوائم انتظار الوظائف غير المتزامنة
- راقب باستمرار بأدوات APM لتحديد الاختناقات
- خزِّن بقوة لكن أبطِل بذكاء
- قِس قبل التحسين—قِس، لا تخمن