Redis — Tổng hợp kiến thức đầy đủ

23 phút đọc

1. Redis là gì?

Redis (Remote Dictionary Server) là một in-memory data structure store — lưu dữ liệu trực tiếp trên RAM, hỗ trợ persistence xuống disk, dùng như cache, message broker, session store, hoặc database tùy use case.

Đặc điểm cốt lõi:

  • Single-threaded I/O (dùng event loop) → không có race condition
  • Tốc độ cực cao: ~100,000 ops/giây
  • Hỗ trợ nhiều kiểu dữ liệu phong phú
  • Persistence tùy chọn (RDB snapshot / AOF log)
  • Replication, Sentinel, Cluster sẵn có

2. Kiến trúc tổng quan

flowchart TD
    Client["Client (Laravel App)"] -->|TCP / RESP Protocol| Redis["Redis Server (RAM)"]
    Redis -->|Snapshot| RDB["RDB File (.rdb)"]
    Redis -->|Append log| AOF["AOF File (.aof)"]
    Redis -->|Replication| Replica["Replica Node"]
    Redis -->|Health check| Sentinel["Redis Sentinel"]
    Sentinel -->|Failover| Replica

Luồng xử lý 1 request:

sequenceDiagram
    participant App as Laravel App
    participant Redis
    participant DB as Database

    App->>Redis: GET user:1
    alt Cache HIT
        Redis-->>App: Return cached data
    else Cache MISS
        Redis-->>App: nil
        App->>DB: SELECT * FROM users WHERE id=1
        DB-->>App: User data
        App->>Redis: SET user:1 {data} EX 3600
        App-->>App: Return data
    end

3. Các kiểu dữ liệu (Data Structures)

3.1 String

Kiểu đơn giản nhất — lưu text, số, JSON serialized, binary.

SET name "Trung Phan"
GET name          → "Trung Phan"
SETEX token 3600 "abc123"   # TTL 1 giờ
INCR counter      # Atomic increment

Laravel:

Cache::put('user:1', $user, 3600);
Cache::get('user:1');
Cache::increment('api_hits:today');
  • Giải thích đoạn code

    Đoạn code trên minh họa cách sử dụng Laravel Cache với Redis làm backend:

    Cache::put('user:1', $user, 3600);
    Cache::get('user:1');
    Cache::increment('api_hits:today');
    

    Chi tiết từng dòng:

    1. Cache::put('user:1', $user, 3600)

    • Chức năng: Lưu dữ liệu vào cache với key là user:1
    • Tham số:
      • 'user:1' - Key để định danh cache (convention: resource:id)
      • $user - Dữ liệu cần cache (Laravel tự động serialize object)
      • 3600 - TTL (Time To Live) = 3600 giây = 1 giờ
    • Redis command tương ứng: SETEX user:1 3600 "{serialized_user_data}"
    • Khi nào dùng: Cache thông tin user sau khi query từ DB để giảm tải cho các request tiếp theo

    2. Cache::get('user:1')

    • Chức năng: Lấy dữ liệu từ cache theo key
    • Return:
      • Trả về dữ liệu đã cache (Laravel tự unserialize) nếu key tồn tại và chưa hết hạn
      • Trả về null nếu key không tồn tại hoặc đã expire
    • Redis command tương ứng: GET user:1

    3. Cache::increment('api_hits:today')

    • Chức năng: Tăng giá trị counter lên 1 (atomic operation)
    • Use case: Đếm số lượng API request trong ngày, page views, download count...
    • Redis command tương ứng: INCR api_hits:today
    • Đặc điểm:
      • Thread-safe: Redis đảm bảo không bị race condition khi nhiều request cùng increment
      • Nếu key chưa tồn tại, Redis tự khởi tạo = 0 rồi mới +1
      • Có thể dùng Cache::increment('key', 5) để tăng 5 đơn vị

    Ví dụ thực tế kết hợp:

    // Controller
    public function show($id)
    {
        // Tăng counter lượt xem
        Cache::increment("post:$id:views");
    
        // Lấy bài viết từ cache, nếu không có thì query DB
        $post = Cache::remember("post:$id", 3600, function () use ($id) {
            return Post::with('author', 'tags')->findOrFail($id);
        });
    
        // Lấy số lượt xem
        $views = Cache::get("post:$id:views", 0);
    
        return view('post.show', compact('post', 'views'));
    }
    

    Best Practices:

    • Naming convention: Dùng pattern resource:id:attribute (ví dụ: user:123:profile, post:456:comments)
    • TTL hợp lý:
      • Data ít thay đổi: 3600-86400s (1-24h)
      • Data thay đổi thường xuyên: 300-900s (5-15 phút)
      • Session data: theo config session lifetime
    • Xử lý cache miss: Luôn dùng Cache::remember() thay vì get() + put() riêng lẻ
    • Invalidation: Nhớ xóa cache khi update data: Cache::forget('user:1') sau $user->update()

3.2 Hash

Lưu object dạng field-value — tương tự row trong DB nhưng trên RAM.

HSET user:1 name "Trung" email "trung@test.com" age 28
HGET user:1 name        → "Trung"
HGETALL user:1          → {name, email, age}
HDEL user:1 age

Khi nào dùng Hash thay vì String?

  • Khi object có nhiều field cần cập nhật từng phần (không muốn deserialize/reserialize toàn bộ)
  • Tiết kiệm memory hơn lưu JSON string

Laravel:

// Dùng Redis facade trực tiếp
Redis::hset('user:1', 'name', 'Trung');
Redis::hgetall('user:1');

3.3 List

Linked list — push/pop hai đầu, dùng cho queue, stack, activity feed.

LPUSH notifications "msg1"   # Push trái
RPUSH notifications "msg2"   # Push phải
LPOP notifications           # Pop trái
LRANGE notifications 0 -1    # Lấy tất cả
LLEN notifications           # Độ dài
flowchart LR
    LPUSH -->|"msg3"| L["[msg3 | msg1 | msg2]"] 
    L -->|RPOP| RPOP["msg2"]

Dùng như task queue đơn giản:

Redis::rpush('jobs', json_encode($job));
$job = json_decode(Redis::blpop('jobs', 5)); // blocking pop, timeout 5s

3.4 Set

Tập hợp không trùng lặp, không có thứ tự — dùng cho tags, unique visitors, relationships.

SADD tags:post:1 "php" "laravel" "api"
SMEMBERS tags:post:1     → {php, laravel, api}
SISMEMBER tags:post:1 "php"  → 1 (true)
SINTER tags:post:1 tags:post:2   # Intersection
SUNION tags:post:1 tags:post:2   # Union

Use case thực tế:

  • Lưu danh sách user đã đọc bài viết
  • Unique IP visitors trong ngày
  • Online users list

3.5 Sorted Set (ZSet)

Giống Set nhưng mỗi member có score → tự động sắp xếp theo score. Dùng cho leaderboard, rate limiting, scheduled jobs.

ZADD leaderboard 1500 "user:1"
ZADD leaderboard 2200 "user:2"
ZADD leaderboard 1800 "user:3"
ZRANGE leaderboard 0 -1 WITHSCORES   # Tăng dần
ZREVRANGE leaderboard 0 2 WITHSCORES # Top 3
ZRANK leaderboard "user:1"            # Vị trí rank

Laravel — Rate Limiting với Sorted Set:

// Đếm request trong 60s gần nhất
$key = 'rate:user:' . $userId;
$now = microtime(true);
Redis::zadd($key, $now, $now);               // Thêm timestamp
Redis::zremrangebyscore($key, 0, $now - 60); // Xóa quá 60s
$count = Redis::zcard($key);                 // Đếm request
if ($count > 100) abort(429);

3.6 Tổng kết Data Structures

Kiểu Dùng khi Use case thực tế
String Lưu giá trị đơn, counter, token Cache HTML, session token, feature flag
Hash Lưu object nhiều field User profile, product detail
List Queue/Stack theo thứ tự Notification feed, task queue, log buffer
Set Tập hợp unique, kiểm tra membership Tags, unique visitors, follow list
Sorted Set Xếp hạng, thứ tự theo score Leaderboard, rate limiting, delay queue

4. TTL & Key Expiration

Mỗi key có thể được gán TTL (Time To Live) — Redis tự động xóa key khi hết hạn.

SET session:abc "data" EX 1800   # 30 phút
EXPIRE user:1 3600               # Set TTL cho key sẵn có
TTL user:1                       # Kiểm tra thời gian còn lại
PERSIST user:1                   # Xóa TTL → key không bao giờ hết

Cơ chế xóa key expired:

flowchart TD
    A[Key hết TTL] --> B{Redis xóa khi nào?}
    B --> C["Lazy deletion:\nXóa khi key được truy cập"]
    B --> D["Active expiration:\nBackground scan định kỳ 100ms"]
    C --> E[Giải phóng memory]
    D --> E

Laravel:

Cache::put('key', $value, now()->addMinutes(30)); // TTL
Cache::forever('key', $value);                    // Không hết hạn
Cache::forget('key');                             // Xóa thủ công

5. Persistence — Lưu dữ liệu xuống disk

Redis là in-memory nhưng có thể persist để tránh mất dữ liệu khi restart.

5.1 RDB (Redis Database Snapshot)

  • Tạo snapshot toàn bộ data theo định kỳ (mặc định: 900s/1 change, 300s/10 changes)
  • Ưu điểm: File nhỏ, restore nhanh, ít ảnh hưởng performance
  • Nhược điểm: Có thể mất data giữa 2 lần snapshot

5.2 AOF (Append Only File)

  • Ghi log mọi write command vào file
  • Ưu điểm: Ít mất data hơn (có thể config fsync every second)
  • Nhược điểm: File lớn hơn, restore chậm hơn
Tiêu chí RDB AOF
Tốc độ restore Nhanh Chậm hơn
Rủi ro mất data Cao hơn (vài phút) Thấp (tối đa 1 giây)
Kích thước file Nhỏ Lớn hơn
Dùng cho Backup, cache thuần túy Dữ liệu quan trọng (queue jobs)

Khuyến nghị: Dùng cả hai (RDB + AOF) cho production.


6. Eviction Policy — Khi RAM đầy

Khi Redis hết memory, nó sẽ xóa key theo policy được cấu hình (maxmemory-policy):

Policy Hành vi Dùng khi
noeviction Trả error khi đầy Dữ liệu không được mất (queue)
allkeys-lru Xóa key ít dùng gần nhất (toàn bộ) Cache thuần túy ✅ phổ biến nhất
volatile-lru Xóa key có TTL, ít dùng nhất Mix cache + persistent data
allkeys-lfu Xóa key ít truy cập nhất (tần suất) Cache với access pattern không đều
volatile-ttl Xóa key sắp hết hạn sớm nhất Muốn giữ key dài hạn lâu hơn

Laravel config:

// config/database.php
'redis' => [
    'default' => [
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', 6379),
        'database' => 0,
    ],
]

7. Redis trong Laravel — 4 Use Cases chính

7.1 Cache

flowchart LR
    Request --> Cache{"Cache::get(key)"}
    Cache -->|HIT| Response
    Cache -->|MISS| DB[(Database)]
    DB --> Store["Cache::put(key, data, ttl)"]
    Store --> Response
// Cache-aside pattern
$users = Cache::remember('users:all', 3600, function () {
    return User::with('roles')->get();
});

// Cache tags (grouping)
Cache::tags(['users', 'admin'])->put('user:1', $user, 600);
Cache::tags(['users'])->flush(); // Xóa tất cả cache liên quan user

// Cache với lock (tránh cache stampede)
$value = Cache::lock('process-report')->get(function () {
    return DB::table('orders')->sum('amount');
});

Cache Stampede là gì?

Các trường hợp sử dụng thực tế:

  • Cache kết quả query phức tạp — Dashboard báo cáo doanh thu, thống kê theo tháng: query nặng nhiều JOIN, chạy lại mỗi request rất tốn kém
$report = Cache::remember('report:revenue:' . $month, 1800, function () use ($month) {
    return Order::whereMonth('created_at', $month)
        ->with('items.product')
        ->selectRaw('SUM(total) as revenue, COUNT(*) as orders')
        ->first();
});
  • Cache danh sách dropdown/filter — Danh mục sản phẩm, tỉnh/thành, config hệ thống: ít thay đổi nhưng được gọi ở nhiều nơi
$categories = Cache::remember('categories:tree', 86400, function () {
    return Category::with('children')->whereNull('parent_id')->get();
});
// Khi admin thêm/sửa category:
Cache::forget('categories:tree');
  • Cache API response từ bên thứ 3 — Tỷ giá tiền tệ, thời tiết, dữ liệu từ external API: tránh gọi quá nhiều lần, giảm chi phí và latency
$exchangeRate = Cache::remember('exchange:USD:VND', 3600, function () {
    return Http::get('https://api.exchangerate.host/latest?base=USD')->json();
});
  • Cache permission/role của user — Mỗi request cần kiểm tra quyền, query DB mỗi lần rất chậm
$permissions = Cache::remember("user:{$userId}:permissions", 600, function () use ($userId) {
    return User::find($userId)->getAllPermissions()->pluck('name');
});
// Khi user được cập nhật quyền:
Cache::forget("user:{$userId}:permissions");
  • Page view counter — Đếm lượt xem bài viết, sản phẩm một cách atomic, không cần ghi DB mỗi lần
// Tăng counter (atomic, không race condition)
Cache::increment("post:{$postId}:views");
// Sync vào DB định kỳ (ví dụ mỗi 5 phút bằng scheduler)
$views = Cache::get("post:{$postId}:views", 0);
Post::find($postId)->update(['views' => $views]);

7.2 Queue (Job Queue)

flowchart LR
    App["Laravel App"] -->|dispatch| Queue["Redis Queue (List)"]
    Queue -->|BLPOP| Worker1["Queue Worker 1"]
    Queue -->|BLPOP| Worker2["Queue Worker 2"]
    Worker1 -->|success| Done["✅ Done"]
    Worker1 -->|fail| Retry["Retry / Dead Letter"]
// .env
QUEUE_CONNECTION=redis

// Dispatch job
SendEmailJob::dispatch($user)->onQueue('emails');
ProcessReportJob::dispatch($data)->delay(now()->addMinutes(5));

// Run worker
// php artisan queue:work redis --queue=emails --tries=3

// Job class
class SendEmailJob implements ShouldQueue
{
    public $tries = 3;
    public $backoff = [10, 30, 60]; // Retry sau 10s, 30s, 60s

    public function handle(): void
    {
        Mail::to($this->user)->send(new WelcomeMail());
    }

    public function failed(Throwable $e): void
    {
        // Ghi log, notify Slack...
    }
}

Các trường hợp sử dụng thực tế:

  • Gửi email/SMS hàng loạt — Sau khi user đăng ký, hệ thống dispatch job gửi welcome email → user không phải chờ
// Trong controller — trả về response ngay, email gửi nền
user::create($data);
SendWelcomeEmailJob::dispatch($user); // ~0ms từ góc nhìn user
return response()->json(['message' => 'Đăng ký thành công']);
  • Xử lý ảnh/video sau upload — Resize ảnh, tạo thumbnail, convert video: tác vụ nặng không nên block request
after upload:
ProcessImageJob::dispatch($imagePath, [
    'sizes' => [150, 400, 800],
    'format' => 'webp',
])->onQueue('media');
  • Tạo báo cáo PDF/Excel — Export dữ liệu lớn mất 5-30 giây, không thể để user chờ
// User bấm "Export" → dispatch job → nhận email khi xong
GenerateReportJob::dispatch($filters, $user->email)
    ->onQueue('reports')
    ->delay(now()->addSeconds(2));
return response()->json(['message' => 'Báo cáo đang được tạo, chúng tôi sẽ gửi email cho bạn']);
  • Đồng bộ dữ liệu với hệ thống ngoài — Sau khi tạo đơn hàng, sync sang ERP/kế toán/CRM mà không block luồng chính
OrderCreated::dispatch($order);
// Trong listener:
class SyncOrderToErp implements ShouldQueue
{
    public function handle(OrderCreated $event): void
    {
        ERP::syncOrder($event->order); // Gọi API bên ngoài, có thể chậm
    }
}
  • Delayed job — nhắc nhở / hẹn giờ — Gửi email nhắc nhở 24 giờ sau khi user bỏ giỏ hàng
AbandonedCartReminderJob::dispatch($cart)
    ->delay(now()->addHours(24));

7.3 Session Storage

// .env
SESSION_DRIVER=redis

// config/session.php
'connection' => 'session', // Dùng Redis connection riêng

// Laravel tự động handle — không cần code thêm
// Session key pattern: laravel_session:{session_id}

Tại sao dùng Redis cho session thay vì file/DB?

  • Scalable: Nhiều server chia sẻ cùng 1 Redis → stateless
  • Nhanh hơn: Không cần I/O disk hay query DB
  • TTL tự động: Session tự hết hạn

Các trường hợp sử dụng thực tế:

  • Multi-server deployment — App chạy trên 2+ server sau load balancer: session file chỉ tồn tại trên 1 server, Redis giúp mọi server đều đọc được session
User → Load Balancer → Server A (request 1)
                    → Server B (request 2)  ← đọc session từ Redis, không bị mất login
  • Lưu trạng thái multi-step form — Checkout nhiều bước (địa chỉ → thanh toán → xác nhận): lưu data từng bước vào session
// Bước 1
session(['checkout.address' => $request->address]);
// Bước 2
session(['checkout.payment' => $request->payment_method]);
// Bước 3 — lấy toàn bộ
$checkoutData = session('checkout');
  • Flash message — Thông báo sau redirect (success/error): Laravel dùng session để pass message giữa các request
return redirect()->route('posts.index')
    ->with('success', 'Bài viết đã được tạo thành công!');
// Trong view: session('success')
  • Giới hạn thiết bị đăng nhập — Chỉ cho phép 1 thiết bị đăng nhập cùng lúc, invalidate session cũ khi login mới
// Khi user login trên thiết bị mới
$oldSessionId = Cache::get("user:{$userId}:session_id");
if ($oldSessionId) {
    // Xóa session cũ
    Redis::del('laravel_session:' . $oldSessionId);
}
Cache::put("user:{$userId}:session_id", session()->getId(), 86400);
  • Remember Token / Keep me logged in — Lưu long-lived token trong Redis thay vì DB để tăng tốc verify
$token = Str::random(60);
Cache::put("remember_token:{$token}", $userId, now()->addDays(30));
// Cookie gửi về browser chứa $token

7.4 Pub/Sub — Real-time Events

  • Phải setup thêm trên FE

    Đúng! Laravel Broadcasting bắt buộc phải có JS phía frontend mới nhận được event real-time. Đây là cách nó hoạt động:

    Luồng hoàn chỉnh

    Backend (PHP)           WebSocket Server        Frontend (JS)
    ──────────────          ────────────────        ─────────────
    broadcast(event) ──→   Redis Pub/Sub       ←── Laravel Echo subscribe
                        ──→ Soketi/Reverb      ──→ nhận event → update UI
    

    Backend phát event → WebSocket server chuyển tiếp → JS nhận và xử lý.


    Các thành phần cần có

    1. Backend (PHP) — bạn đã có:

    broadcast(new OrderConfirmed($order));
    

    2. WebSocket server — cần cài thêm 1 trong:

    • Laravel Reverb (mới nhất, built-in Laravel 11) — khuyến nghị
    • Soketi (self-hosted, miễn phí)
    • Pusher (cloud service, có free tier)

    3. Frontend JS — bắt buộc:

    npm install laravel-echo pusher-js
    
    // resources/js/bootstrap.js
    import Echo from 'laravel-echo';
    import Pusher from 'pusher-js';
    
    window.Echo = new Echo({
        broadcaster: 'reverb', // hoặc 'pusher'
        key: import.meta.env.VITE_REVERB_APP_KEY,
        wsHost: import.meta.env.VITE_REVERB_HOST,
        wsPort: import.meta.env.VITE_REVERB_PORT,
    });
    
    // Lắng nghe event
    Echo.channel('orders')
        .listen('OrderConfirmed', (e) => {
            alert(`Đơn hàng #${e.order.id} đã được xác nhận!`);
        });
    

    Nếu không muốn dùng JS riêng?

    Nếu dùng Livewire hoặc Inertia.js, chúng tích hợp sẵn với Echo nên ít phải tự viết JS hơn. Nhưng về bản chất vẫn luôn cần JS để duy trì kết nối WebSocket — đây là đặc tính của browser, server không thể tự push đến browser mà không có client-side script.


    Tóm lại: Redis chỉ là "ống dẫn" trung gian giữa Laravel và WebSocket server. Còn để browser nhận event thật sự → JS là bắt buộc, không có cách nào tránh khỏi.

sequenceDiagram
    participant P as Publisher (App)
    participant R as Redis
    participant S1 as Subscriber 1 (WebSocket)
    participant S2 as Subscriber 2 (Logger)

    S1->>R: SUBSCRIBE notifications
    S2->>R: SUBSCRIBE notifications
    P->>R: PUBLISH notifications "new_order:123"
    R-->>S1: "new_order:123"
    R-->>S2: "new_order:123"
// Publisher
Redis::publish('notifications', json_encode([
    'event' => 'new_order',
    'order_id' => 123
]));

// Subscriber (thường chạy trong artisan command)
Redis::subscribe(['notifications'], function ($message) {
    $data = json_decode($message);
    // Xử lý event...
});

Laravel Broadcasting với Redis:

// .env
BROADCAST_DRIVER=redis

// Event
class OrderCreated implements ShouldBroadcast
{
    public function broadcastOn(): Channel
    {
        return new Channel('orders');
    }
}

// Frontend (Laravel Echo)
Echo.channel('orders').listen('OrderCreated', (e) => {
    console.log(e.order);
});

Các trường hợp sử dụng thực tế:

  • Thông báo đơn hàng real-time — Khi admin xác nhận đơn, user nhận được notification ngay trên web mà không cần F5
// Backend khi admin confirm order
broadcast(new OrderConfirmed($order))->toOthers();

// Frontend
Echo.private(`orders.${userId}`)
    .listen('OrderConfirmed', (e) => {
        showNotification(`Đơn hàng #${e.order.id} đã được xác nhận!`);
    });
  • Live chat / Tin nhắn — Hệ thống chat giữa user và support, tin nhắn hiển thị ngay khi gửi
// Khi user gửi tin
broadcast(new MessageSent($message));
// Recipient nhận qua WebSocket (Laravel Echo + Pusher/Soketi)
Echo.private(`chat.${roomId}`).listen('MessageSent', (e) => {
    appendMessage(e.message);
});
  • Dashboard thống kê live — Màn hình monitoring đơn hàng, số user online, doanh thu cập nhật theo giây
// Scheduler chạy mỗi phút, broadcast stats mới
Redis::publish('dashboard:stats', json_encode([
    'online_users' => Cache::get('online_users_count'),
    'orders_today' => Order::today()->count(),
    'revenue_today' => Order::today()->sum('total'),
]));
  • Invalidate cache trên nhiều server — Khi 1 server update data, broadcast cho tất cả server còn lại xóa cache cũ
// Server A update product
$product->update($data);
Cache::forget("product:{$product->id}");
Redis::publish('cache:invalidate', json_encode([
    'key' => "product:{$product->id}"
]));
// Server B, C lắng nghe và xóa cache của họ
Redis::subscribe(['cache:invalidate'], function ($message) {
    $data = json_decode($message);
    Cache::forget($data->key);
});
  • Tracking trạng thái online — Hiển thị "user đang online" trong app chat/collaboration
// Khi user active
Cache::put("user:{$userId}:online", true, 60); // TTL 60s
Redis::publish('presence', json_encode(['user_id' => $userId, 'status' => 'online']));
// Middleware tự gia hạn TTL mỗi request

8. Redis Streams — Nâng cao

Redis Streams (từ Redis 5.0) là append-only log — giống Kafka nhưng đơn giản hơn. Khác với List/Pub/Sub:

  • Message persist sau khi consumer đọc
  • Hỗ trợ Consumer Groups — nhiều consumer đọc song song, mỗi message chỉ xử lý 1 lần
  • Message có ID dựa trên timestamp
XADD orders * product_id 101 qty 2 user_id 5
XREAD COUNT 10 STREAMS orders 0
XGROUP CREATE orders processing $
XREADGROUP GROUP processing worker1 COUNT 5 STREAMS orders >
XACK orders processing {message-id}

So sánh Pub/Sub vs List vs Streams:

Tiêu chí Pub/Sub List (Queue) Streams
Message persistence ❌ Không ✅ Đến khi LPOP ✅ Giữ mãi
Consumer groups ✅ Competing consumers ✅ Named groups
Replay messages
Phù hợp Real-time broadcast Laravel Queue Event sourcing, audit log

9. High Availability

9.1 Replication

flowchart LR
    Master["Master (Read/Write)"] -->|Async replication| R1["Replica 1 (Read-only)"]
    Master -->|Async replication| R2["Replica 2 (Read-only)"]
  • Mọi write đến Master, read có thể phân tán sang Replica
  • Replica là bản sao real-time của Master

9.2 Sentinel

flowchart TD
    S1[Sentinel 1] & S2[Sentinel 2] & S3[Sentinel 3] -->|Monitor| Master
    Master -->|Replication| R1[Replica]
    Master -- "❌ Xuống" --> Failover[Sentinel bầu chọn]
    Failover --> R1
    R1 -- "Promote" --> NewMaster[New Master]

9.3 Redis Cluster

Sharding tự động data ra nhiều node — dùng khi data quá lớn cho 1 máy.

  • Data chia thành 16384 hash slots
  • Mỗi node quản lý 1 phần slots
  • Tự động rebalance khi thêm/bớt node

10. Atomic Operations & Lua Scripting

Redis single-threaded nên mọi command đều atomic. Với nhiều command cần thực thi như 1 unit → dùng MULTI/EXEC hoặc Lua script.

# Transaction
MULTI
SET balance:user:1 900
SET balance:user:2 1100
EXEC
-- Lua script: atomic check-and-decrement
local balance = redis.call('GET', KEYS[1])
if tonumber(balance) >= tonumber(ARGV[1]) then
    redis.call('DECRBY', KEYS[1], ARGV[1])
    return 1
else
    return 0
end
// Laravel — Lua script
$result = Redis::eval(
    file_get_contents('scripts/decrement.lua'),
    1,              // số lượng KEYS
    'balance:1',    // KEYS[1]
    100             // ARGV[1]
);

11. Best Practices & Anti-patterns

✅ Nên làm

  • Đặt TTL cho mọi cache key — tránh key tồn tại vô thời hạn làm đầy RAM
  • Namespace key có cấu trúc: {app}:{entity}:{id}:{field} (e.g. myapp:user:1:profile)
  • Dùng connection pool — không tạo mới connection mỗi request
  • Dùng SCAN thay KEYS trong production — KEYS * block Redis
  • Monitor với redis-cli --latency, INFO memory, SLOWLOG
  • Dùng Redis database riêng cho cache vs queue (DB 0 cho cache, DB 1 cho queue)

❌ Tránh

Anti-pattern Vấn đề Giải pháp
KEYS * trong production Block Redis hoàn toàn Dùng SCAN với cursor
Lưu object quá lớn (>1MB) Tăng latency, lãng phí RAM Compress hoặc lưu S3
Không set TTL cho cache Memory leak Luôn set TTL hoặc allkeys-lru
Dùng Redis như primary DB RAM đắt, giới hạn query Redis là lớp cache, không thay DB
Không handle cache miss storm Cache stampede Dùng Lock hoặc stale-while-revalidate

12. Lộ trình học Redis (cho Laravel Dev)

  • Bước 1 — Nền tảng: Cài Redis local, thử các command cơ bản với redis-cli
  • Bước 2 — Data structures: Thực hành từng kiểu, hiểu khi nào dùng cái nào
  • Bước 3 — Laravel Cache: Implement cache-aside pattern trong project thực
  • Bước 4 — Laravel Queue: Chuyển một feature sang background job với Redis driver
  • Bước 5 — Session & Auth: Cấu hình session Redis, kết hợp với JWT/token cache
  • Bước 6 — Performance: Học MONITOR, SLOWLOG, INFO, đo latency
  • Bước 7 — Production: Tìm hiểu Sentinel/Cluster, eviction policy, persistence config
  • Bước 8 — Advanced: Redis Streams, Lua scripting, atomic operations

Bài viết liên quan

Đang cập nhật...