RAJESTARY DIGITAL TECH • SECURITY • EDUCATION
دانش • فناوری • امنیت

دنیای تکنولوژی
از یادگیری شروع می‌شود

آموزش‌های کاربردی، برنامه‌نویسی، امنیت و فناوری برای ساختن، یادگرفتن و بهتر زندگی کردن با تکنولوژی.

LEARN BUILD SECURE CREATE

Caching و افزایش عملکرد API

Caching و افزایش عملکرد API

📖 درس هفتم: Caching و افزایش عملکرد API

👋

سلام مهمان عزیز!

برای دسترسی به تمام محتوا و امکانات، وارد شوید.

🎯 هدف این درس: آشنایی با تکنیک‌های بهینه‌سازی عملکرد API شامل پیاده‌سازی کش با Redis، استفاده از ETag، Cache-Control، Query Optimization و روش‌های افزایش سرعت پاسخ‌دهی.

مهمان عزیز! 👋
تا اینجا یک API کامل با امنیت، احراز هویت و مستندات حرفه‌ای ساخته‌ایم. اما یک API خوب باید سریع هم باشد!

وقتی کاربران از API شما استفاده می‌کنند، زمان پاسخ‌دهی (Response Time) یکی از مهم‌ترین عواملی است که تجربه کاربری را تعیین می‌کند. در این درس، با تکنیک‌های مختلفی آشنا می‌شویم که API شما را تا چندین برابر سریع‌تر می‌کنند.

💡 چرا Caching مهم است؟

کش کردن (Caching) یعنی ذخیره موقت نتایج محاسبات سنگین یا کوئری‌های دیتابیس، تا درخواست‌های بعدی بتوانند پاسخ را سریع‌تر دریافت کنند. این کار باعث می‌شود:

  • ✅ زمان پاسخ‌دهی تا ۱۰ برابر کاهش یابد.
  • ✅ بار روی دیتابیس کاهش پیدا کند.
  • ✅ هزینه‌های سرور کمتر شود.
  • ✅ تجربه کاربری بهبود یابد.

🗄️ آشنایی با Redis

Redis یک دیتابیس NoSQL درون‌حافظه‌ای (In-Memory) است که برای کش کردن و ذخیره موقت داده‌ها استفاده می‌شود. سرعت بالای Redis به دلیل ذخیره داده‌ها در RAM است.

نصب و راه‌اندازی Redis

  • روش ۱ – در لینوکس: sudo apt-get install redis-server
  • روش ۲ – در ویندوز: از Docker یا WSL استفاده کنید.
  • روش ۳ – با Docker: docker run -p 6379:6379 --name redis -d redis

نصب کتابخانه PhpRedis



📦
Install Predis

Composer




# نصب Predis (کتابخانه PHP برای Redis)
composer require predis/predis

# یا استفاده از PhpRedis Extension (پیش‌فرض نیست)
# sudo pecl install redis

🔗 کلاس Cache Manager

یک کلاس برای مدیریت کش با Redis می‌سازیم:



🔗
Cache.php

PHP
8.2




<?php

require_once __DIR__ . '/../vendor/autoload.php';

use Predis\Client;

class Cache
{
    private static $instance = null;
    private $redis;
    private $enabled = true;

    private function __construct()
    {
        try {
            $this->redis = new Client([
                'scheme' => 'tcp',
                'host' => '127.0.0.1',
                'port' => 6379,
                'timeout' => 2.5
            ]);
            
            // تست اتصال
            $this->redis->ping();
        } catch (Exception $e) {
            // اگر Redis در دسترس نبود، کش غیرفعال می‌شود
            $this->enabled = false;
            error_log('Redis connection failed: ' . $e->getMessage());
        }
    }

    // Singleton Pattern
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    // ذخیره داده در کش
    public function set($key, $value, $ttl = 3600)
    {
        if (!$this->enabled) {
            return false;
        }

        $serialized = serialize($value);
        return $this->redis->setex($key, $ttl, $serialized);
    }

    // دریافت داده از کش
    public function get($key)
    {
        if (!$this->enabled) {
            return null;
        }

        $data = $this->redis->get($key);
        
        if ($data === null) {
            return null;
        }

        return unserialize($data);
    }

    // حذف یک کلید از کش
    public function delete($key)
    {
        if (!$this->enabled) {
            return false;
        }

        return $this->redis->del($key);
    }

    // پاک کردن کش بر اساس الگو
    public function deletePattern($pattern)
    {
        if (!$this->enabled) {
            return false;
        }

        $keys = $this->redis->keys($pattern);
        
        if (empty($keys)) {
            return true;
        }

        return $this->redis->del($keys);
    }

    // بررسی وجود کلید در کش
    public function exists($key)
    {
        if (!$this->enabled) {
            return false;
        }

        return $this->redis->exists($key) > 0;
    }

    // بررسی فعال بودن کش
    public function isEnabled()
    {
        return $this->enabled;
    }
}

⚡ پیاده‌سازی کش در کنترلر

حالا از کش برای ذخیره نتایج کوئری‌های سنگین استفاده می‌کنیم:




UserController.php (با کش)

PHP
8.2




<?php

require_once __DIR__ . '/../Core/Cache.php';

class UserController
{
    private $userModel;
    private $cache;

    public function __construct()
    {
        require_once __DIR__ . '/../Models/User.php';
        $this->userModel = new User();
        $this->cache = Cache::getInstance();
    }

    // GET /users - دریافت لیست کاربران با کش
    public function index($request)
    {
        // احراز هویت
        require_once __DIR__ . '/../Core/AuthMiddleware.php';
        AuthMiddleware::authenticate();
        AuthMiddleware::authorize(['admin']);

        // ساخت کلید کش بر اساس پارامترها
        $page = $_GET['page'] ?? 1;
        $limit = $_GET['limit'] ?? 10;
        $filters = $_GET ?? [];

        // حذف پارامترهای غیرمرتبط با فیلتر
        unset($filters['page']);
        unset($filters['limit']);

        $cacheKey = 'users:' . md5($page . ':' . $limit . ':' . json_encode($filters));

        // بررسی وجود داده در کش
        $cachedData = $this->cache->get($cacheKey);

        if ($cachedData !== null) {
            // ارسال پاسخ از کش
            header('X-Cache: HIT');
            return Response::success($cachedData);
        }

        // داده در کش وجود ندارد - از دیتابیس دریافت کن
        $users = $this->userModel->getAll($page, $limit, $filters);
        $total = $this->userModel->getTotalCount($filters);

        $responseData = [
            'data' => $users,
            'pagination' => [
                'current_page' => $page,
                'per_page' => $limit,
                'total' => $total,
                'total_pages' => ceil($total / $limit)
            ]
        ];

        // ذخیره در کش به مدت ۵ دقیقه (۳۰۰ ثانیه)
        $this->cache->set($cacheKey, $responseData, 300);

        header('X-Cache: MISS');
        return Response::success($responseData);
    }

    // GET /users/{id} - دریافت یک کاربر با کش
    public function show($request, $params)
    {
        $id = $params[0] ?? null;

        if (!$id || !is_numeric($id)) {
            return Response::error('Valid user ID is required', 400);
        }

        $cacheKey = 'user:' . $id;

        // بررسی کش
        $cachedData = $this->cache->get($cacheKey);

        if ($cachedData !== null) {
            header('X-Cache: HIT');
            return Response::success($cachedData);
        }

        $user = $this->userModel->find($id);

        if (!$user) {
            return Response::notFound('User not found');
        }

        // ذخیره در کش به مدت ۱۰ دقیقه
        $this->cache->set($cacheKey, $user, 600);

        header('X-Cache: MISS');
        return Response::success($user);
    }

    // POST /users - ایجاد کاربر (پاک کردن کش)
    public function store($request)
    {
        // ... کد ایجاد کاربر ...

        // پس از ایجاد کاربر، کش لیست کاربران را پاک کن
        $this->cache->deletePattern('users:*');

        return Response::created($user);
    }

    // PUT /users/{id} - بروزرسانی کاربر (پاک کردن کش)
    public function update($request, $params)
    {
        $id = $params[0] ?? null;

        // ... کد بروزرسانی کاربر ...

        // پاک کردن کش کاربر و لیست کاربران
        $this->cache->delete('user:' . $id);
        $this->cache->deletePattern('users:*');

        return Response::success($user, 'User updated successfully');
    }

    // DELETE /users/{id} - حذف کاربر (پاک کردن کش)
    public function delete($request, $params)
    {
        $id = $params[0] ?? null;

        // ... کد حذف کاربر ...

        // پاک کردن کش کاربر و لیست کاربران
        $this->cache->delete('user:' . $id);
        $this->cache->deletePattern('users:*');

        return Response::noContent();
    }
}

📌 ETag و Cache-Control

ETag و Cache-Control هدرهایی هستند که به کلاینت اجازه می‌دهند داده‌ها را به صورت محلی کش کند و از ارسال درخواست‌های تکراری جلوگیری کنند.



📌
ETag & Cache-Control

PHP
8.2




<?php

class CacheControl
{
    // تنظیم هدرهای Cache-Control
    public static function setCacheHeaders($maxAge = 3600)
    {
        header('Cache-Control: public, max-age=' . $maxAge);
        header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $maxAge) . ' GMT');
        header('Pragma: cache');
    }

    // تنظیم هدر برای عدم کش (مخصوص داده‌های حساس)
    public static function setNoCacheHeaders()
    {
        header('Cache-Control: no-store, no-cache, must-revalidate, private');
        header('Pragma: no-cache');
        header('Expires: -1');
    }

    // تولید ETag بر اساس داده
    public static function generateETag($data)
    {
        $etag = md5(json_encode($data));
        header('ETag: "' . $etag . '"');
        return $etag;
    }

    // بررسی If-None-Match برای ETag
    public static function checkETag($data)
    {
        $etag = md5(json_encode($data));
        
        $ifNoneMatch = $_SERVER['HTTP_IF_NONE_MATCH'] ?? '';
        
        if (strpos($ifNoneMatch, $etag) !== false) {
            // داده تغییر نکرده است، پاسخ 304 Not Modified
            http_response_code(304);
            exit;
        }
        
        header('ETag: "' . $etag . '"');
    }
}

// استفاده در کنترلر
public function getUserWithETag($request, $params)
{
    $user = $this->userModel->find($params[0]);

    // بررسی ETag
    CacheControl::checkETag($user);

    // تنظیم هدرهای کش
    CacheControl::setCacheHeaders(600); // ۱۰ دقیقه

    return Response::success($user);
}

⚡ Query Optimization

بهینه‌سازی کوئری‌های دیتابیس نقش مهمی در افزایش سرعت API دارد:




Query Optimization

SQL
MySQL 8.x




-- ❌ کوئری بدون ایندکس (کند)
SELECT * FROM users WHERE email = 'younes@example.com';

-- ✅ اضافه کردن ایندکس روی email
CREATE INDEX idx_users_email ON users(email);

-- ❌ استفاده از SELECT * (دریافت ستون‌های اضافی)
SELECT * FROM users WHERE id = 1;

-- ✅ فقط ستون‌های مورد نیاز را انتخاب کن
SELECT id, name, email, role FROM users WHERE id = 1;

-- ❌ کوئری با JOIN بدون ایندکس
SELECT u.*, o.* 
FROM users u 
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = 1;

-- ✅ اضافه کردن ایندکس روی کلید خارجی
CREATE INDEX idx_orders_user_id ON orders(user_id);

📊 مقایسه عملکرد با و بدون کش

سناریو بدون کش با کش (Redis) بهبود
دریافت لیست ۱۰۰ کاربر ~۱۵۰ms ~۵ms ۳۰x
دریافت یک کاربر با JOIN ~۸۰ms ~۲ms ۴۰x
جستجوی پیشرفته با فیلتر ~۲۵۰ms ~۸ms ۳۱x

🔴 خطاهای رایج در Caching

  • ❌ اشتباه: کش کردن داده‌های حساس بدون کنترل دسترسی.
  • ✅ درست: داده‌های حساس را با private Cache-Control کش کن.
  • ❌ اشتباه: عدم پاک کردن کش پس از بروزرسانی داده.
  • ✅ درست: پس از هر عملیات CRUD، کش مرتبط را پاک کن.
  • ❌ اشتباه: زمان TTL بسیار طولانی.
  • ✅ درست: TTL را متناسب با میزان تغییرات داده تنظیم کن.
  • ❌ اشتباه: کش کردن همه چیز بدون استراتژی.
  • ✅ درست: فقط داده‌هایی که هزینه محاسباتی بالایی دارند را کش کن.

💎 نکات کلیدی درس

  • ✅ Redis یک دیتابیس In-Memory برای کش کردن است.
  • ✅ Cache Manager با Singleton Pattern مدیریت کش را ساده می‌کند.
  • ✅ ETag از ارسال مجدد داده‌های بدون تغییر جلوگیری می‌کند.
  • ✅ Cache-Control زمان کش شدن داده‌ها را در کلاینت کنترل می‌کند.
  • ✅ پس از هر تغییر داده، کش مرتبط باید پاک شود (Cache Invalidation).
  • ✅ کوئری‌های دیتابیس را با ایندکس‌گذاری بهینه کن.
  • ✅ از هدر X-Cache برای نشان دادن وضعیت کش استفاده کن.

🛠️ پروژه عملی درس هفتم

مهمان عزیز، حالا سیستم کش را به API محصولات اضافه کن!

مسئله:

یک سیستم کش کامل برای API محصولات پیاده‌سازی کن:

  • ✅ کش کردن لیست محصولات با Redis (TTL: ۵ دقیقه).
  • ✅ کش کردن جزئیات هر محصول (TTL: ۱۰ دقیقه).
  • ✅ استفاده از ETag برای محصولات.
  • ✅ پاک کردن کش پس از ایجاد، بروزرسانی یا حذف محصول.
  • ✅ Cache-Control مناسب برای داده‌های مختلف.
  • ✅ اضافه کردن هدر X-Cache به پاسخ‌ها.

🧪 راه‌حل پروژه (پاسخ)

مراحل پیاده‌سازی:

  • 1️⃣ کلاس Cache را با Redis پیاده‌سازی کن.
  • 2️⃣ در ProductController از کش برای index و show استفاده کن.
  • 3️⃣ کلاس CacheControl را برای ETag و Cache-Control ایجاد کن.
  • 4️⃣ در متدهای store، update و delete کش مرتبط را پاک کن.
  • 5️⃣ Redis را نصب و راه‌اندازی کن و کش را تست کن.

📝 تمرین‌های عملی

🧪 تمرین ۱ (ساده):

Redis را روی سیستم خود نصب کن و کلاس Cache را تست کن. یک کلید با مقدار مشخص ذخیره کن و سپس آن را دریافت کن.

🧪 تمرین ۲ (متوسط):

یک سیستم کش هوشمند پیاده‌سازی کن که بر اساس پارامترهای category و price کلیدهای کش متفاوت تولید کند.

🧪 تمرین ۳ (چالشی):

یک سیستم کش دینامیک با TTL متغیر پیاده‌سازی کن. داده‌هایی که بیشتر درخواست می‌شوند، TTL بیشتری داشته باشند و داده‌های کم‌استفاده TTL کمتری داشته باشند.


🏁 جمع‌بندی درس

مهمان عزیز، در این درس یاد گرفتی:

  • ✅ Redis چیست و چگونه نصب و استفاده می‌شود.
  • ✅ چگونه یک Cache Manager با Singleton Pattern بسازی.
  • ✅ چگونه از کش در کنترلرها برای افزایش سرعت استفاده کنی.
  • ✅ ETag و Cache-Control چیست و چگونه پیاده‌سازی می‌شود.
  • ✅ چگونه کوئری‌های دیتابیس را با ایندکس بهینه کنی.
  • ✅ چگونه کش را پس از تغییر داده پاک کنی (Cache Invalidation).

حالا API شما با استفاده از کش، چندین برابر سریع‌تر شده است. در درس بعدی، با API Versioning و مدیریت چرخه حیات API آشنا می‌شویم. آماده‌ای؟ 🚀


🗺️ نقشه راه دوره: شما اینجا هستید!

برای اینکه بدانی دقیقاً کجای مسیر هستی و چه درس‌هایی در انتظار توست، به جدول زیر نگاه کن. درس فعلی با رنگ متفاوت مشخص شده است.

درس عنوان درس آنچه یاد می‌گیرید
۱ مفاهیم پایه API HTTP، REST، معماری Client-Server
۲ پیاده‌سازی RESTful API با PHP خام مسیریابی، متدها، پارامترها، پاسخ‌های JSON
۳ مدیریت داده‌ها و پایگاه‌داده اتصال PDO، CRUD، تراکنش‌ها، Pagination
۴ اعتبارسنجی و احراز هویت JWT، Roles/Permissions، Middleware
۵ امنیت API پیشرفته OWASP Top 10، SQLi، XSS، CSRF، Rate Limiting
۶ مستندسازی API با OpenAPI Swagger، OpenAPI Specification، مستندات تعاملی
۷ Caching و افزایش عملکرد Redis، ETag، Cache-Control، Query Optimization
۸ API Versioning و مدیریت چرخه حیات استراتژی‌های نسخه‌بندی، Deprecation، Backward Compatibility
۹ تست و دیباگ API حرفه‌ای PHPUnit، Mocking، Logging، Postman Collection
۱۰ پروژه نهایی: API فروشگاهی پروژه کامل فروشگاه اینترنتی با تمام قابلیت‌ها

🔗 لینک‌های مرتبط

👤
نویسنده
📅
تاریخ انتشار 30 مرداد 1405
🔄
آخرین بروزرسانی 30 مرداد 1405

نظر خود را بنویسید

با ثبت نظر، به بهبود محتوای ما کمک کنید

امتیاز شما به این مطلب (اختیاری)
برای امتیازدهی، روی ستاره‌ها کلیک کنید
نام شما در سایت نمایش داده می‌شود
ایمیل شما محفوظ می‌ماند
0 کاراکتر | حداقل ۱۰ کاراکتر
نظر سازنده و مفید بنویسید
ارسال دیدگاه: +۵ امتیاز
امتیازدهی: +۲ امتیاز
نشان شما: مبتدی
لطفاً از کلمات محترمانه استفاده کنید. دیدگاه‌های توهین‌آمیز حذف می‌شوند.
⚠️

هشدار مهم!

برای اتصال به درگاه پرداخت و انجام تراکنش، لطفاً VPN یا فیلترشکن خود را خاموش کنید.

⏳ ادامه در
5
ثانیه