الأمان والأداء

تحسين أداء الخادم

20 دقيقة الدرس 20 من 35

مقدمة في تحسين أداء الخادم

تحسين أداء جانب الخادم أمر حيوي لتقديم تطبيقات ويب سريعة وموثوقة. بينما يحسن التحسين من جانب العميل تجربة المستخدم، يؤثر أداء الخادم بشكل مباشر على قابلية التوسع وكفاءة التكلفة والقدرة على التعامل مع المستخدمين المتزامنين. في هذا الدرس الشامل، سنستكشف تقنيات متقدمة لتحسين PHP وNode.js وعمليات قواعد البيانات لتحقيق أقصى أداء.

تحسين الأداء ليس فقط عن السرعة—إنه يتعلق بكفاءة الموارد وتقليل التكلفة وتوفير تجربة متسقة تحت أحمال متفاوتة. الخادم المُحسَّن جيداً يمكنه التعامل مع 10 أضعاف حركة المرور بنفس العتاد، مما يقلل تكاليف البنية التحتية ويحسن الموثوقية.

ضبط أداء PHP

PHP هي واحدة من أكثر لغات جانب الخادم استخداماً، وتدعم منصات مثل WordPress وLaravel وMagento. ومع ذلك، فإن طبيعة PHP المفسرة تعني أن التحسين ضروري لبيئات الإنتاج.

تكوين OpCache

OpCache هي ذاكرة التخزين المؤقت للبايت كود المدمجة في PHP والتي تحسن الأداء بشكل كبير عن طريق تخزين البايت كود المترجم مسبقاً في الذاكرة. بدون OpCache، يجب على PHP تحليل وترجمة النصوص في كل طلب.

<!-- تكوين php.ini -->
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+) لأداء أفضل
تحذير: مع validate_timestamps=0، يجب عليك مسح OpCache يدوياً بعد نشر تغييرات الكود باستخدام opcache_reset() أو إعادة تشغيل PHP-FPM.

تحسين PHP-FPM

PHP-FPM (FastCGI Process Manager) يدير عمليات العمل في PHP. التكوين الصحيح ضروري للتعامل مع الطلبات المتزامنة بكفاءة.

<!-- /etc/php-fpm.d/www.conf -->
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: ينشئ عمال فقط عند الحاجة (جيد للمواقع منخفضة الحركة)
نصيحة: احسب max_children باستخدام: (إجمالي الذاكرة العشوائية - نظام التشغيل/MySQL) / متوسط حجم عملية PHP. راقب بـ: ps aux | grep php-fpm | wc -l

إدارة الذاكرة

تسريبات الذاكرة في PHP والكود غير الفعال يمكن أن يؤدي إلى تدهور الأداء بمرور الوقت. إليك استراتيجيات التحسين:

<?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(); // إجبار جمع القمامة
ملاحظة: جامع القمامة في PHP يعمل تلقائياً، لكن يمكنك تشغيله يدوياً بـ gc_collect_cycles() بعد معالجة مجموعات بيانات كبيرة لتحرير الذاكرة فوراً.

تحسين أداء Node.js

Node.js يتفوق في التعامل مع الاتصالات المتزامنة بنموذج I/O غير الحاجب القائم على الأحداث. ومع ذلك، فإنه يتطلب استراتيجيات تحسين مختلفة عن PHP.

وضع المجموعة لاستخدام متعدد النوى

بشكل افتراضي، Node.js يعمل على نواة CPU واحدة. استخدم وحدة cluster لاستخدام جميع النوى المتاحة:

const cluster = require('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`));
}
نصيحة: للإنتاج، استخدم مدير العمليات PM2 الذي يتعامل مع التجميع والمراقبة وإعادة التشغيل التلقائي: pm2 start app.js -i max

تحسين حلقة الأحداث

حلقة الأحداث في 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';
إرشادات الفهرسة: أنشئ فهارس على الأعمدة المستخدمة في WHERE وJOIN وORDER BY وGROUP BY. تجنب الفهرسة المفرطة لأنها تبطئ الكتابة. استخدم الفهارس المركبة لاستعلامات متعددة الأعمدة.

مشكلة N+1

مشكلة N+1 تحدث عندما تنفذ استعلاماً واحداً لجلب السجلات، ثم N استعلامات إضافية لجلب البيانات المرتبطة:

<?php
// سيء: استعلامات 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();

تخزين نتائج الاستعلام مؤقتاً

خزن نتائج الاستعلامات المكلفة مؤقتاً لتجنب الوصول المتكرر إلى قاعدة البيانات:

<?php
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

// Node.js مع mysql2
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);
});
نصيحة: اضبط connectionLimit بناءً على max_connections لقاعدة البيانات وعدد خوادم التطبيقات. الصيغة: max_connections / number_of_app_servers - 10 (مخزن مؤقت).

تجميع اتصالات Redis

const redis = require('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 compression = require('compression');
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

للحصول على أداء أفضل، تعامل مع الضغط على مستوى خادم الويب:

server {
    # ضغط 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;
}
ملاحظة: Brotli عادة يحقق ضغطاً أفضل بنسبة 15-20% من Gzip لكنه يتطلب المزيد من CPU. استخدم مستوى Gzip 6 (متوازن) أو مستوى 1 (سريع) للمواقع عالية الحركة.

المعالجة غير المتزامنة

انقل المهام التي تستغرق وقتاً طويلاً من دورة الطلب والاستجابة لتحسين الأداء المتصور وقابلية التوسع.

قائمة انتظار الوظائف مع 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
// إنشاء فئة الوظيفة
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)

// مراقبة New Relic (Node.js)
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);
    });
});

مقاييس الأداء المخصصة

<?php
// 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 لتحديد الاختناقات
  • خزِّن بقوة لكن أبطِل بذكاء
  • قِس قبل التحسين—قِس، لا تخمن
نصيحة محترف: قاعدة 80/20 تنطبق على تحسين الأداء. ركز على 20% من الكود الذي يسبب 80% من مشاكل الأداء. استخدم أدوات التحليل لتحديد هذه النقاط الساخنة قبل استثمار الوقت في التحسين.