Site logo
Tác giả
  • avatar Nguyễn Đức Xinh
    Name
    Nguyễn Đức Xinh
    Twitter
Ngày xuất bản
Ngày xuất bản

Redis với Laravel: Hướng dẫn chi tiết cách tích hợp và sử dụng Redis trong Laravel

Redis với Laravel

Laravel cung cấp tích hợp sâu và mượt mà với Redis, biến nó thành một trong những cache và queue drivers được sử dụng phổ biến nhất trong Laravel ecosystem. Redis không chỉ giúp tăng performance đáng kể mà còn mở ra nhiều possibilities cho real-time features, distributed systems, và scalable architectures.

Trong bài viết này, chúng ta sẽ đi sâu vào cách sử dụng Redis trong Laravel, từ basic setup đến advanced patterns, kèm theo real-world examples và best practices được kiểm chứng trong production environments.

Tại sao sử dụng Redis với Laravel?

Performance Benefits

Laravel với Redis có thể cải thiện performance đáng kể:

So sánh response time:

  • Database query: 50-200ms
  • File cache: 5-20ms
  • Redis cache: 1-5ms

So sánh throughput:

  • Database: ~1,000 queries/second
  • Redis: ~100,000 operations/second

Use Cases phổ biến

  1. Cache Layer - Lưu trữ database query results, API responses, rendered views
  2. Session Store - Manage user sessions với expiration tự động
  3. Queue Driver - Background job processing với reliability
  4. Rate Limiting - API throttling và request limiting
  5. Broadcasting - Real-time events với Pub/Sub
  6. Locks - Distributed locking cho concurrent operations
  7. Analytics - Real-time counters và statistics

Cài đặt và Cấu hình

Bước 1: Cài đặt Redis Server

Trước tiên, đảm bảo Redis đã được cài đặt:

# Ubuntu/Debian
sudo apt update
sudo apt install redis-server

# macOS
brew install redis
brew services start redis

# Kiểm tra Redis đang chạy
redis-cli ping
# Expected: PONG

Laravel hỗ trợ hai PHP extensions cho Redis:

Option 1: PhpRedis (Recommended - faster)

# Ubuntu/Debian
sudo apt-get install php-redis

# macOS
pecl install redis

# Verify installation
php -m | grep redis

Option 2: Predis (Pure PHP - no extension needed)

composer require predis/predis

So sánh PhpRedis vs Predis:

Aspect PhpRedis Predis
Speed Nhanh hơn (~30-40%) Pure PHP (chậm hơn)
Installation Cần PHP extension Chỉ cần Composer
Compatibility C extension PHP pure
Laravel Support Native support Native support
Recommended ✅ Production Development, shared hosting

Bước 3: Configure Laravel

1. Update .env file:

CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=0
REDIS_CACHE_DB=1
REDIS_PREFIX=laravel_

2. Configure config/database.php:

'redis' => [
    'client' => env('REDIS_CLIENT', 'phpredis'), // or 'predis'

    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
    ],

    'default' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_DB', '0'),
    ],

    'cache' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_CACHE_DB', '1'),
    ],

    'queue' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_QUEUE_DB', '2'),
    ],
],

3. Test connection:

// Trong routes/web.php hoặc tinker
Route::get('/test-redis', function () {
    try {
        Redis::connection()->ping();
        return 'Redis connected successfully!';
    } catch (Exception $e) {
        return 'Redis connection failed: ' . $e->getMessage();
    }
});

// Hoặc trong artisan tinker
php artisan tinker
>>> Redis::ping()
=> "+PONG"

Caching với Redis trong Laravel

Basic Cache Operations

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;

// Store item in cache
Cache::put('key', 'value', $seconds);
Cache::put('user:1', $user, 3600); // 1 hour

// Store forever (until manually deleted)
Cache::forever('settings', $settings);

// Store if not exists
Cache::add('key', 'value', $seconds);

// Retrieve item
$value = Cache::get('key');
$user = Cache::get('user:1');

// Retrieve with default value
$value = Cache::get('key', 'default');
$value = Cache::get('key', function () {
    return DB::table('users')->first();
});

// Check if exists
if (Cache::has('key')) {
    // Key exists
}

// Retrieve and delete
$value = Cache::pull('key');

// Delete item
Cache::forget('key');

// Clear all cache
Cache::flush();

Remember Pattern (Cache or Execute)

Pattern phổ biến nhất trong Laravel:

use Illuminate\Support\Facades\Cache;

// Cache database query results
$users = Cache::remember('users:active', 3600, function () {
    return User::where('active', true)->get();
});

// Remember forever
$settings = Cache::rememberForever('settings', function () {
    return Setting::all()->pluck('value', 'key');
});

// Real-world example: Product detail page
public function show($id)
{
    $product = Cache::remember("product:$id", 3600, function () use ($id) {
        return Product::with(['category', 'images', 'reviews'])
            ->findOrFail($id);
    });

    return view('products.show', compact('product'));
}

Cache Tags (Organized Cache Management)

Cache tags cho phép group related cache entries:

// Store với tags
Cache::tags(['products', 'featured'])->put('featured:products', $products, 3600);
Cache::tags(['users', 'active'])->put('active:users', $users, 3600);

// Retrieve với tags
$products = Cache::tags(['products', 'featured'])->get('featured:products');

// Flush tất cả cache với specific tag
Cache::tags(['products'])->flush();

// Real-world example: E-commerce category products
class CategoryController extends Controller
{
    public function show($slug)
    {
        $category = Category::where('slug', $slug)->firstOrFail();
        
        $products = Cache::tags(['products', "category:{$category->id}"])
            ->remember("category:{$category->id}:products", 3600, function () use ($category) {
                return $category->products()
                    ->with(['images', 'reviews'])
                    ->where('active', true)
                    ->orderBy('featured', 'desc')
                    ->paginate(20);
            });
        
        return view('categories.show', compact('category', 'products'));
    }
    
    // Khi update product, invalidate cache
    public function update(Request $request, Product $product)
    {
        $product->update($request->validated());
        
        // Clear cache cho category này
        Cache::tags(["category:{$product->category_id}"])->flush();
        
        return redirect()->back();
    }
}

Advanced Caching Patterns

1. Cache Aside Pattern (Lazy Loading)

public function getUserProfile($userId)
{
    $cacheKey = "user:profile:$userId";
    
    // Try to get from cache
    $profile = Cache::get($cacheKey);
    
    if ($profile === null) {
        // Cache miss - load from database
        $profile = User::with(['posts', 'followers', 'following'])
            ->findOrFail($userId);
        
        // Store in cache
        Cache::put($cacheKey, $profile, 3600);
    }
    
    return $profile;
}

2. Write-Through Cache Pattern

public function updateUserProfile(Request $request, $userId)
{
    $validated = $request->validated();
    
    // Update database
    $user = User::findOrFail($userId);
    $user->update($validated);
    
    // Update cache immediately (write-through)
    $profile = User::with(['posts', 'followers', 'following'])
        ->find($userId);
    
    Cache::put("user:profile:$userId", $profile, 3600);
    
    return $user;
}

3. Cache-Aside với Lock (Prevent Cache Stampede)

use Illuminate\Support\Facades\Cache;

public function getPopularProducts()
{
    $cacheKey = 'products:popular';
    $lockKey = 'lock:products:popular';
    
    // Try to get from cache
    $products = Cache::get($cacheKey);
    
    if ($products === null) {
        // Try to acquire lock
        $lock = Cache::lock($lockKey, 10); // 10 seconds lock
        
        if ($lock->get()) {
            try {
                // Double-check cache (another process might have updated)
                $products = Cache::get($cacheKey);
                
                if ($products === null) {
                    // Load from database
                    $products = Product::withCount('orders')
                        ->orderBy('orders_count', 'desc')
                        ->take(10)
                        ->get();
                    
                    // Cache for 1 hour
                    Cache::put($cacheKey, $products, 3600);
                }
            } finally {
                $lock->release();
            }
        } else {
            // Could not acquire lock, wait and retry
            sleep(1);
            return $this->getPopularProducts();
        }
    }
    
    return $products;
}

4. Probabilistic Early Expiration (Prevent Thundering Herd)

public function getCachedData($key, $ttl, callable $callback)
{
    $data = Cache::get($key);
    
    if ($data !== null) {
        // Probabilistic early recomputation
        $expiresAt = Cache::get($key . ':expires_at');
        $delta = $expiresAt - time();
        $beta = 1.0; // Tuning parameter
        
        // Early recompute with probability
        if ($delta - $beta * log(rand() / getrandmax()) <= 0) {
            $data = $callback();
            Cache::put($key, $data, $ttl);
            Cache::put($key . ':expires_at', time() + $ttl, $ttl);
        }
        
        return $data;
    }
    
    // Cache miss
    $data = $callback();
    Cache::put($key, $data, $ttl);
    Cache::put($key . ':expires_at', time() + $ttl, $ttl);
    
    return $data;
}

Session Management với Redis

Configure Redis Sessions

1. Update .env:

SESSION_DRIVER=redis
SESSION_CONNECTION=default

2. Configure config/session.php:

'driver' => env('SESSION_DRIVER', 'redis'),
'connection' => env('SESSION_CONNECTION', 'default'),
'store' => env('SESSION_STORE', null),

Benefits của Redis Sessions

Aspect File Sessions Database Sessions Redis Sessions
Speed Slow (I/O) Medium Very Fast
Scalability Single server Good Excellent
Load Balancing Requires sticky sessions Easy Easy
Expiration Manual cleanup Manual cleanup Automatic
Performance Poor Good Excellent

Working with Sessions

// Store data in session
session(['key' => 'value']);
session(['user_preferences' => $preferences]);

// Retrieve from session
$value = session('key');
$value = session('key', 'default');

// Check if exists
if (session()->has('key')) {
    // exists
}

// Remove from session
session()->forget('key');
session()->forget(['key1', 'key2']);

// Flash data (available only for next request)
session()->flash('message', 'Profile updated successfully!');

// Regenerate session ID (security)
session()->regenerate();

// Destroy session
session()->flush();

Custom Session Handler

// app/Services/CustomRedisSessionHandler.php
namespace App\Services;

use SessionHandlerInterface;
use Illuminate\Support\Facades\Redis;

class CustomRedisSessionHandler implements SessionHandlerInterface
{
    protected $redis;
    protected $minutes;
    
    public function __construct($minutes = 120)
    {
        $this->redis = Redis::connection('session');
        $this->minutes = $minutes;
    }
    
    public function read($sessionId)
    {
        return $this->redis->get("session:$sessionId") ?: '';
    }
    
    public function write($sessionId, $data)
    {
        $this->redis->setex(
            "session:$sessionId",
            $this->minutes * 60,
            $data
        );
        
        return true;
    }
    
    public function destroy($sessionId)
    {
        $this->redis->del("session:$sessionId");
        return true;
    }
    
    // Implement other required methods...
}

Queue với Redis trong Laravel

Configure Redis Queue

1. Update .env:

QUEUE_CONNECTION=redis
REDIS_QUEUE_DB=2

2. Create Queue Job:

php artisan make:job SendWelcomeEmail

3. Implement Job:

namespace App\Jobs;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $user;
    public $tries = 3; // Retry 3 times
    public $timeout = 120; // 2 minutes timeout

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function handle()
    {
        Mail::to($this->user->email)->send(new WelcomeMail($this->user));
    }
    
    public function failed(\Throwable $exception)
    {
        // Handle failed job
        \Log::error("Failed to send welcome email to {$this->user->email}", [
            'exception' => $exception->getMessage()
        ]);
    }
}

Dispatching Jobs

use App\Jobs\SendWelcomeEmail;
use App\Models\User;

// Dispatch immediately
SendWelcomeEmail::dispatch($user);

// Dispatch với delay
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(10));

// Dispatch to specific queue
SendWelcomeEmail::dispatch($user)->onQueue('emails');

// Chain jobs (sequential execution)
SendWelcomeEmail::dispatch($user)
    ->chain([
        new SendFollowUpEmail($user),
        new UpdateUserStats($user),
    ]);

// Batch jobs (parallel execution)
use Illuminate\Support\Facades\Bus;

Bus::batch([
    new ProcessImportRow($row1),
    new ProcessImportRow($row2),
    new ProcessImportRow($row3),
])->then(function (Batch $batch) {
    // All jobs completed successfully
})->catch(function (Batch $batch, Throwable $e) {
    // First batch job failure
})->finally(function (Batch $batch) {
    // Batch finished executing
})->dispatch();

Running Queue Workers

# Start queue worker
php artisan queue:work redis

# Work specific queue
php artisan queue:work redis --queue=high,default,low

# Process only one job
php artisan queue:work redis --once

# Specify max tries
php artisan queue:work redis --tries=3

# Specify timeout
php artisan queue:work redis --timeout=60

# Run with supervisor (production)
# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path/to/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=8
redirect_stderr=true
stdout_logfile=/path/to/worker.log
stopwaitsecs=3600

Queue Monitoring

// Monitor queue in code
use Illuminate\Support\Facades\Queue;

Route::get('/queue-status', function () {
    $connection = Queue::connection('redis');
    
    return [
        'default_size' => Redis::llen('queues:default'),
        'high_size' => Redis::llen('queues:high'),
        'failed_jobs' => DB::table('failed_jobs')->count(),
    ];
});

Advanced Queue Patterns

1. Job Chaining với Conditional Logic

use App\Jobs\ProcessPayment;
use App\Jobs\SendInvoice;
use App\Jobs\UpdateInventory;

public function checkout(Order $order)
{
    ProcessPayment::withChain([
        function () use ($order) {
            if ($order->fresh()->payment_status === 'paid') {
                return [
                    new SendInvoice($order),
                    new UpdateInventory($order),
                ];
            }
            return [];
        }
    ])->dispatch($order);
}

2. Rate Limited Jobs

use Illuminate\Support\Facades\RateLimiter;

class CallExternalAPI implements ShouldQueue
{
    public function handle()
    {
        $executed = RateLimiter::attempt(
            'external-api',
            $perMinute = 60,
            function() {
                // Call API
                Http::get('https://api.example.com/data');
            }
        );
        
        if (!$executed) {
            // Rate limit exceeded, retry later
            $this->release(60); // Retry after 60 seconds
        }
    }
}

Rate Limiting với Redis

Laravel sử dụng Redis để implement efficient rate limiting:

API Rate Limiting

use Illuminate\Support\Facades\RateLimiter;

// Define rate limiter in RouteServiceProvider
RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

// Advanced: Different limits for authenticated vs guest
RateLimiter::for('api', function (Request $request) {
    return $request->user()
        ? Limit::perMinute(100)->by($request->user()->id)
        : Limit::perMinute(10)->by($request->ip());
});

// Apply to routes
Route::middleware(['throttle:api'])->group(function () {
    Route::get('/user', function () {
        //
    });
});

Custom Rate Limiting

use Illuminate\Support\Facades\RateLimiter;

class LoginController extends Controller
{
    public function login(Request $request)
    {
        $key = 'login-attempt:' . $request->ip();
        
        // Check rate limit
        if (RateLimiter::tooManyAttempts($key, 5)) {
            $seconds = RateLimiter::availableIn($key);
            
            return response()->json([
                'message' => "Too many login attempts. Try again in $seconds seconds."
            ], 429);
        }
        
        // Attempt login
        if (Auth::attempt($request->only('email', 'password'))) {
            // Clear rate limiter on success
            RateLimiter::clear($key);
            
            return response()->json(['message' => 'Login successful']);
        }
        
        // Increment attempts
        RateLimiter::hit($key, 60); // Block for 60 seconds after 5 attempts
        
        return response()->json(['message' => 'Invalid credentials'], 401);
    }
}

Advanced Rate Limiting with Multiple Windows

class AdvancedRateLimiter
{
    public function checkLimit($userId)
    {
        $limits = [
            'per_second' => ['limit' => 10, 'decay' => 1],
            'per_minute' => ['limit' => 100, 'decay' => 60],
            'per_hour' => ['limit' => 1000, 'decay' => 3600],
            'per_day' => ['limit' => 10000, 'decay' => 86400],
        ];
        
        foreach ($limits as $name => $config) {
            $key = "rate_limit:$userId:$name";
            
            if (RateLimiter::tooManyAttempts($key, $config['limit'])) {
                $seconds = RateLimiter::availableIn($key);
                
                throw new RateLimitException(
                    "Rate limit exceeded for $name. Try again in $seconds seconds."
                );
            }
            
            RateLimiter::hit($key, $config['decay']);
        }
        
        return true;
    }
}

Broadcasting và Pub/Sub

Configure Broadcasting

1. Install Pusher (or use Redis directly):

composer require pusher/pusher-php-server

2. Update .env:

BROADCAST_DRIVER=redis

3. Create Event:

php artisan make:event NewOrderPlaced
namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class NewOrderPlaced implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $order;

    public function __construct(Order $order)
    {
        $this->order = $order;
    }

    public function broadcastOn()
    {
        return new Channel('orders');
    }
    
    public function broadcastWith()
    {
        return [
            'id' => $this->order->id,
            'total' => $this->order->total,
            'customer' => $this->order->customer->name,
        ];
    }
}

4. Broadcast Event:

use App\Events\NewOrderPlaced;

public function store(Request $request)
{
    $order = Order::create($request->validated());
    
    // Broadcast event
    broadcast(new NewOrderPlaced($order));
    
    return response()->json($order, 201);
}

Direct Pub/Sub với Redis

use Illuminate\Support\Facades\Redis;

// Publisher
Route::post('/publish', function (Request $request) {
    Redis::publish('notifications', json_encode([
        'type' => 'message',
        'content' => $request->input('message'),
        'timestamp' => now(),
    ]));
    
    return response()->json(['status' => 'published']);
});

// Subscriber (run in console)
php artisan make:command RedisSubscriber

// app/Console/Commands/RedisSubscriber.php
public function handle()
{
    Redis::subscribe(['notifications'], function ($message) {
        $data = json_decode($message, true);
        $this->info("Received: " . $data['content']);
        
        // Process notification
        // Send to WebSocket, save to DB, etc.
    });
}

Distributed Locks

Basic Lock Usage

use Illuminate\Support\Facades\Cache;

public function processOrder(Order $order)
{
    $lock = Cache::lock("order:{$order->id}", 10); // 10 seconds
    
    if ($lock->get()) {
        try {
            // Your code here - guaranteed single execution
            $order->process();
            $order->save();
        } finally {
            $lock->release();
        }
    } else {
        // Could not acquire lock
        return response()->json(['message' => 'Order is being processed'], 409);
    }
}

Block Until Lock Available

$lock = Cache::lock("resource:$id", 10);

// Block for maximum 5 seconds
if ($lock->block(5)) {
    try {
        // Lock acquired after waiting
        processResource($id);
    } finally {
        $lock->release();
    }
} else {
    // Could not acquire lock within 5 seconds
    throw new TimeoutException('Could not acquire lock');
}

Advanced Lock Patterns

class DistributedLockService
{
    public function executeOnce($key, callable $callback, $ttl = 10)
    {
        $lock = Cache::lock($key, $ttl);
        
        try {
            // Try to acquire lock
            if (!$lock->get()) {
                throw new LockException("Could not acquire lock for key: $key");
            }
            
            // Execute callback
            $result = $callback();
            
            return $result;
            
        } finally {
            // Always release lock
            optional($lock)->release();
        }
    }
    
    public function executeWithRetry($key, callable $callback, $maxRetries = 3)
    {
        $attempts = 0;
        
        while ($attempts < $maxRetries) {
            try {
                return $this->executeOnce($key, $callback);
            } catch (LockException $e) {
                $attempts++;
                
                if ($attempts >= $maxRetries) {
                    throw $e;
                }
                
                // Exponential backoff
                usleep(pow(2, $attempts) * 100000); // 0.2s, 0.4s, 0.8s
            }
        }
    }
}

Performance Optimization

Connection Pooling

// config/database.php
'redis' => [
    'client' => 'phpredis',
    
    'options' => [
        'cluster' => 'redis',
        'prefix' => env('REDIS_PREFIX', 'laravel_'),
        
        // Persistent connections (connection pooling)
        'persistent' => true,
        'persistent_id' => env('APP_NAME', 'laravel'),
        
        // Serialization
        'serializer' => Redis::SERIALIZER_IGBINARY, // Faster than PHP serialization
        
        // Compression
        'compression' => Redis::COMPRESSION_LZ4, // Reduce network traffic
    ],
],

Pipeline Multiple Commands

// ❌ Slow - Multiple network roundtrips
for ($i = 1; $i <= 1000; $i++) {
    Redis::set("key:$i", "value:$i");
}

// ✅ Fast - Single network roundtrip
Redis::pipeline(function ($pipe) {
    for ($i = 1; $i <= 1000; $i++) {
        $pipe->set("key:$i", "value:$i");
    }
});

Optimize Serialization

// Custom serialization for better performance
class OptimizedCache
{
    public function put($key, $value, $ttl)
    {
        // Use MessagePack for smaller payload
        $serialized = msgpack_pack($value);
        
        Redis::setex($key, $ttl, $serialized);
    }
    
    public function get($key)
    {
        $data = Redis::get($key);
        
        return $data ? msgpack_unpack($data) : null;
    }
}

So sánh Redis với các Cache/Queue Drivers khác

Cache Drivers Comparison

Driver Speed Scalability Persistence Features Best For
Redis ⚡⚡⚡⚡⚡ Excellent Yes (optional) Rich (tags, locks, pub/sub) Production, distributed systems
Memcached ⚡⚡⚡⚡ Excellent No Basic key-value Simple caching
File ⚡⚡ Poor Yes Basic Development, small apps
Database ⚡⚡ Good Yes Basic Simple apps
Array ⚡⚡⚡⚡⚡ N/A No N/A Testing only

Queue Drivers Comparison

Driver Reliability Performance Features Monitoring Best For
Redis Good ⚡⚡⚡⚡ Rich, Atomic Good Most applications
Database Good ⚡⚡ Basic Easy Simple apps
SQS Excellent ⚡⚡⚡ AWS integrated AWS CloudWatch AWS infrastructure
Beanstalkd Good ⚡⚡⚡⚡ Tube-based Basic Specialized workloads
Sync N/A ⚡⚡⚡⚡⚡ N/A N/A Development only

Best Practices

1. Separate Redis Databases

REDIS_DB=0           # Default/General
REDIS_CACHE_DB=1     # Cache
REDIS_QUEUE_DB=2     # Queue
REDIS_SESSION_DB=3   # Sessions

2. Use Appropriate TTLs

// Short-lived data
Cache::put('trending-now', $data, 300); // 5 minutes

// Medium-lived data
Cache::put('user-profile', $user, 3600); // 1 hour

// Long-lived data
Cache::put('site-settings', $settings, 86400); // 24 hours

// Cache that updates frequently
Cache::remember('stats', 60, function() {
    return Stats::calculate();
});

3. Implement Cache Warming

php artisan make:command WarmCache

class WarmCache extends Command
{
    public function handle()
    {
        $this->info('Warming cache...');
        
        // Warm popular products
        $products = Product::popular()->get();
        Cache::put('products:popular', $products, 3600);
        
        // Warm categories
        $categories = Category::with('products')->get();
        Cache::put('categories:all', $categories, 7200);
        
        $this->info('Cache warmed successfully!');
    }
}

4. Monitor Redis Performance

// Create monitoring endpoint
Route::get('/redis-stats', function () {
    $info = Redis::info();
    
    return [
        'memory_used' => $info['used_memory_human'],
        'connected_clients' => $info['connected_clients'],
        'total_commands' => $info['total_commands_processed'],
        'ops_per_sec' => $info['instantaneous_ops_per_sec'],
        'hit_rate' => calculateHitRate($info),
    ];
});

function calculateHitRate($info)
{
    $hits = $info['keyspace_hits'];
    $misses = $info['keyspace_misses'];
    $total = $hits + $misses;
    
    return $total > 0 ? round(($hits / $total) * 100, 2) : 0;
}

5. Handle Redis Failures Gracefully

use Illuminate\Support\Facades\Redis;
use Illuminate\Redis\Connections\PhpRedisConnection;

class ResilientCache
{
    public function get($key, callable $callback, $ttl = 3600)
    {
        try {
            return Cache::remember($key, $ttl, $callback);
        } catch (\Exception $e) {
            \Log::warning('Redis unavailable, falling back to callback', [
                'key' => $key,
                'exception' => $e->getMessage()
            ]);
            
            // Fallback: execute callback directly
            return $callback();
        }
    }
}

6. Security Best Practices

// .env - Always use password in production
REDIS_PASSWORD=your_strong_random_password_here

// config/database.php - Bind to specific interface
'redis' => [
    'default' => [
        'host' => env('REDIS_HOST', '127.0.0.1'), // Never use 0.0.0.0 in production
        'password' => env('REDIS_PASSWORD'),
        // Use TLS in production
        'scheme' => env('REDIS_SCHEME', 'tcp'), // or 'tls'
    ],
],

7. Testing với Redis

// tests/Feature/RedisCacheTest.php
use Illuminate\Support\Facades\Redis;

class RedisCacheTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        
        // Clear Redis before each test
        Redis::flushdb();
    }
    
    public function test_cache_stores_and_retrieves_data()
    {
        Cache::put('test-key', 'test-value', 60);
        
        $this->assertEquals('test-value', Cache::get('test-key'));
    }
    
    public function test_cache_expires_after_ttl()
    {
        Cache::put('test-key', 'test-value', 1);
        
        sleep(2);
        
        $this->assertNull(Cache::get('test-key'));
    }
}

Troubleshooting Common Issues

1. Connection Refused

# Check if Redis is running
sudo systemctl status redis

# Check Redis logs
tail -f /var/log/redis/redis-server.log

# Test connection
redis-cli ping

2. Memory Issues

// Clear cache
php artisan cache:clear

// Monitor memory usage
Route::get('/redis-memory', function() {
    return Redis::info('memory');
});

// Implement maxmemory policy in redis.conf
# maxmemory 256mb
# maxmemory-policy allkeys-lru

3. Slow Performance

# Check slow queries
redis-cli SLOWLOG GET 10

# Monitor commands
php artisan horizon:pause  # If using Horizon
redis-cli MONITOR

# Check connection pool
php artisan config:cache

Kết luận

Redis là một game-changer cho Laravel applications, mang lại significant performance improvements và enabling advanced features như real-time broadcasting, efficient queuing, và distributed locking.

Key Takeaways

  1. Setup properly - PhpRedis extension cho best performance
  2. Separate databases - Different databases cho cache, queue, sessions
  3. Use appropriate patterns - Remember pattern, cache tags, locks
  4. Monitor performance - Track hit rates, memory usage, ops/sec
  5. Handle failures - Graceful degradation khi Redis unavailable
  6. Security first - Always use passwords, TLS in production
  7. Test thoroughly - Include Redis in your testing strategy

Các bước tiếp theo

  1. ✅ Setup Redis với PhpRedis extension
  2. ✅ Implement caching cho expensive queries
  3. ✅ Move queues từ database sang Redis
  4. ✅ Setup monitoring và alerting
  5. ✅ Implement cache warming strategy
  6. ✅ Configure Redis Sentinel hoặc Cluster cho HA
  7. ✅ Load test với realistic traffic

Resources

  • Laravel Cache Documentation: https://laravel.com/docs/cache
  • Laravel Queue Documentation: https://laravel.com/docs/queues
  • Redis Documentation: https://redis.io/documentation
  • Laravel Horizon (Redis queue dashboard): https://laravel.com/docs/horizon

Với Redis integration properly configured, Laravel application của bạn sẽ có thể scale và handle millions of requests với performance tuyệt vời! 🚀