blog.dopana

Back

Redis là in-memory data store nhanh nhất — dùng để cache, session, pub/sub, queue, rate limiting. Đa năng hơn bạn nghĩ.

Data Structures#

String — Cache Cơ Bản#

SET user:123 '{"name":"Alice"}' EX 3600
GET user:123
INCR page:views
INCRBY counter 5
bash

List — Queue / Stack#

# Queue (FIFO)
LPUSH notifications:user_123 "New message"
RPOP notifications:user_123

# Stack (LIFO)
LPUSH stack:actions "action1"
LPOP stack:actions

# Range
LRANGE messages:room_abc 0 -1 # Tất cả
bash

Set — Unique Items#

SADD post:42:likes user_123
SADD post:42:likes user_456
SCARD post:42:likes # 2
SISMEMBER post:42:likes user_123 # 1 (true)
SINTER post:42:likes post:99:likes # Intersection
bash

Sorted Set — Leaderboard#

ZADD leaderboard 1000 "player_1"
ZADD leaderboard 2500 "player_2"
ZADD leaderboard 1500 "player_3"

ZREVRANGE leaderboard 0 2 WITHSCORES # Top 3
ZRANK leaderboard player_2 # Rank
ZINCRBY leaderboard 500 player_1 # +500 điểm
bash

Hash — Object#

HSET user:123 name "Alice" email "alice@example.com" age 30
HGET user:123 name
HGETALL user:123
HINCRBY user:123 age 1
bash

Caching Patterns#

Cache Aside (Phổ biến nhất)#

async function getUser(id: number) {
  // 1. Check cache
  const cached = await redis.get(`user:${id}`);
  if (cached) return JSON.parse(cached);

  // 2. Cache miss → query DB
  const user = await db.user.findUnique({ where: { id } });

  // 3. Set cache
  if (user) {
    await redis.setex(`user:${id}`, 3600, JSON.stringify(user));
  }

  return user;
}
typescript

Cache Penetration — Khi Data Không Tồn Tại#

Cache Breakdown — Khi Key Hết Hạn#

Dùng mutex lock — chỉ một request được query DB:

Cache Stampede — Preload Trước Khi Hết Hạn#

Session Store#

import session from 'express-session';
import RedisStore from 'connect-redis';

app.use(session({
  store: new RedisStore({ client: redis }),
  secret: process.env.SESSION_SECRET!,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    maxAge: 24 * 60 * 60 * 1000, // 24h
  },
}));
typescript

Pub/Sub — Real-time Message#

// Publisher
await redis.publish('channel:notifications', JSON.stringify({
  type: 'order.created',
  orderId: 123,
  userId: 456,
}));

// Subscriber
const subscriber = redis.duplicate();
await subscriber.subscribe('channel:notifications', (message) => {
  const event = JSON.parse(message);
  console.log('Received:', event);
  // Gửi WebSocket cho user, send email, etc.
});
typescript

Rate Limiting — Sliding Window#

Distributed Lock#

Queue — BullMQ#

HyperLogLog — Đếm Unique#

# Đếm unique visitors — chỉ dùng 12KB, sai số ~2%
PFADD page:visits:2024-01-01 ip_1 ip_2 ip_3
PFADD page:visits:2024-01-01 ip_1 ip_4
PFCOUNT page:visits:2024-01-01 # 4
PFMERGE page:visits:2024-01 page:visits:2024-01-01 page:visits:2024-01-02
bash

Persistence#

# RDB — snapshot định kỳ (mặc định)
save 900 1 # 15 phút nếu có 1 thay đổi
save 300 10 # 5 phút nếu có 10 thay đổi
save 60 10000 # 1 phút nếu có 10000 thay đổi

# AOF — log mọi write (chậm hơn nhưng an toàn hơn)
appendonly yes
appendfsync everysec # Sync mỗi giây
bash

Kết Luận#

Redis đa năng hơn “cache đơn thuần”:

Use caseCấu trúc
CacheString + TTL
SessionString hoặc Hash
Rate limitingSorted Set + ZRem
QueueList hoặc BullMQ
Pub/SubPub/Sub channels
Distributed lockString + NX + Lua
LeaderboardSorted Set
Unique counterHyperLogLog

Install: brew install redis hoặc Docker. Production: Redis Cluster hoặc managed (Upstash, Redis Cloud).

Tài liệu tham khảo#