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
- Cache Layer - Lưu trữ database query results, API responses, rendered views
- Session Store - Manage user sessions với expiration tự động
- Queue Driver - Background job processing với reliability
- Rate Limiting - API throttling và request limiting
- Broadcasting - Real-time events với Pub/Sub
- Locks - Distributed locking cho concurrent operations
- 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
Bước 2: Cài đặt PHP Redis Extension (Recommended)
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
- Setup properly - PhpRedis extension cho best performance
- Separate databases - Different databases cho cache, queue, sessions
- Use appropriate patterns - Remember pattern, cache tags, locks
- Monitor performance - Track hit rates, memory usage, ops/sec
- Handle failures - Graceful degradation khi Redis unavailable
- Security first - Always use passwords, TLS in production
- Test thoroughly - Include Redis in your testing strategy
Các bước tiếp theo
- ✅ Setup Redis với PhpRedis extension
- ✅ Implement caching cho expensive queries
- ✅ Move queues từ database sang Redis
- ✅ Setup monitoring và alerting
- ✅ Implement cache warming strategy
- ✅ Configure Redis Sentinel hoặc Cluster cho HA
- ✅ 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! 🚀
