Redis Là Gì? In-Memory Database Cho High Performance Applications
Khám phá Redis - in-memory database mạnh mẽ với nhiều use case: caching, session store, pub/sub, real-time analytics và nhiều hơn thế
Redis Là Gì? In-Memory Database Cho High Performance Applications
Redis (Remote Dictionary Server) là một in-memory database mã nguồn mở nổi tiếng, được sử dụng rộng rãi như một cache, message broker, và database. Với hiệu suất cao và độ linh hoạt lớn, Redis đã trở thành một phần không thể thiếu trong kiến trúc hiện đại.
Redis Là Gì?
Redis là một in-memory data structure store có khả năng:
- Lưu trữ dữ liệu trong bộ nhớ với nhiều loại data structure
- Persistence dữ liệu lên disk theo nhiều cách
- Replication và clustering cho high availability
- Pub/Sub messaging cho real-time applications
- Transactions và Lua scripting cho complex operations
Đặc điểm nổi bật:
- In-memory storage: Tốc độ cực nhanh (sub-millisecond latency)
- Đa dạng data structures: Strings, lists, sets, sorted sets, hashes, bitmaps, hyperloglogs, streams
- Persistence options: RDB snapshots, AOF (Append Only File)
- High availability: Master-slave replication, Redis Sentinel, Redis Cluster
- Atomic operations: Tất cả operations đều atomic
- Lua scripting: Server-side scripting cho complex operations
Cài Đặt và Cấu Hình Redis
Cài Đặt
# Ubuntu/Debian
sudo apt update
sudo apt install redis-server
# macOS
brew install redis
# Docker
docker run -d -p 6379:6379 --name redis redis:alpine
# Kiểm tra cài đặt
redis-cli ping
# Expected: PONG
Cấu Hình Cơ Bản
# File: /etc/redis/redis.conf
# Network
bind 127.0.0.1 ::1
port 6379
protected-mode yes
# Memory
maxmemory 256mb
maxmemory-policy allkeys-lru
# Persistence
save 900 1 # Save sau 900s nếu có 1 key thay đổi
save 300 10 # Save sau 300s nếu có 10 keys thay đổi
save 60 10000 # Save sau 60s nếu có 10000 keys thay đổi
# AOF (Append Only File)
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
# Logging
loglevel notice
logfile /var/log/redis/redis-server.log
Data Structures trong Redis
1. Strings - Cơ bản nhất nhưng mạnh mẽ
const redis = require('redis');
const client = redis.createClient();
// Basic operations
await client.set('user:1:name', 'John Doe');
await client.set('user:1:age', '30', 'EX', 3600); // Set với TTL 1 giờ
const name = await client.get('user:1:name');
const age = await client.get('user:1:age');
// Numeric operations
await client.set('counter', '0');
await client.incr('counter'); // 1
await client.incrby('counter', 5); // 6
await client.decr('counter'); // 5
// Bit operations
await client.setbit('user:1:permissions', 0, 1); // Bit 0: read permission
await client.setbit('user:1:permissions', 1, 1); // Bit 1: write permission
await client.setbit('user:1:permissions', 2, 0); // Bit 2: delete permission (no)
const permissions = await client.getbit('user:1:permissions', 1); // 1
2. Lists - Danh sách liên kết
// Social media feed
await client.lpush('feed:user:1', JSON.stringify({
type: 'post',
content: 'Hello Redis!',
timestamp: Date.now()
}));
await client.lpush('feed:user:1', JSON.stringify({
type: 'photo',
url: 'https://example.com/photo.jpg',
timestamp: Date.now()
}));
// Lấy 10 bài viết mới nhất
const recentPosts = await client.lrange('feed:user:1', 0, 9);
// Queue implementation
await client.rpush('task:queue', 'task:1', 'task:2', 'task:3');
const task = await client.lpop('task:queue'); // task:1
// Capped list - giữ 100 items mới nhất
await client.lpush('recent:searches', 'redis tutorial');
await client.ltrim('recent:searches', 0, 99);
3. Sets - Tập hợp không trùng lặp
// User interests
await client.sadd('user:1:interests', 'technology', 'programming', 'redis');
await client.sadd('user:2:interests', 'technology', 'music', 'sports');
// Tìm common interests
const commonInterests = await client.sinter('user:1:interests', 'user:2:interests');
// ['technology']
// Tìm unique interests của user:1
const uniqueInterests = await client.sdiff('user:1:interests', 'user:2:interests');
// ['programming', 'redis']
// Đếm số members
const count = await client.scard('user:1:interests'); // 3
// Kiểm tra membership
const isMember = await client.sismember('user:1:interests', 'redis'); // 1
// Random member
const randomInterest = await client.srandmember('user:1:interests');
4. Sorted Sets - Sets với score
// Leaderboard - game scores
await client.zadd('game:leaderboard', 1000, 'player:1', 850, 'player:2', 1200, 'player:3');
// Top 10 players
const topPlayers = await client.zrevrange('game:leaderboard', 0, 9, 'WITHSCORES');
// ['player:3', '1200', 'player:1', '1000', 'player:2', '850']
// Player rank
const rank = await client.zrevrank('game:leaderboard', 'player:1'); // 1 (0-based)
// Player score
const score = await client.zscore('game:leaderboard', 'player:1'); // 1000
// Players trong khoảng score
const playersInRange = await client.zrangebyscore('game:leaderboard', 800, 1100);
// ['player:2', 'player:1']
// Time-based data với timestamp làm score
await client.zadd('user:1:timeline', Date.now(), JSON.stringify({
action: 'login',
timestamp: Date.now()
}));
5. Hashes - Objects
// User profile
await client.hset('user:1', 'name', 'John Doe', 'email', 'john@example.com', 'age', '30');
// Lấy toàn bộ user
const user = await client.hgetall('user:1');
// { name: 'John Doe', email: 'john@example.com', age: '30' }
// Lấy specific fields
const name = await client.hget('user:1', 'name');
const fields = await client.hmget('user:1', 'name', 'email');
// Đếm số fields
const fieldCount = await client.hlen('user:1'); // 3
// Kiểm tra field existence
const exists = await client.hexists('user:1', 'email'); // 1
// Xóa field
await client.hdel('user:1', 'age');
// Tăng numeric field
await client.hincrby('user:1', 'login_count', 1);
6. Streams - Log-like data structure (Redis 5.0+)
// Add events to stream
await client.xadd('user:activity', '*', 'action', 'login', 'timestamp', Date.now());
await client.xadd('user:activity', '*', 'action', 'view_page', 'page', '/products', 'timestamp', Date.now());
// Read recent events
const events = await client.xrevrange('user:activity', '+', '-', 'COUNT', 10);
// Consumer groups
await client.xgroup('CREATE', 'user:activity', 'analytics-group', '0');
// Consumer đọc messages
const messages = await client.xreadgroup(
'GROUP', 'analytics-group', 'consumer-1',
'COUNT', 10,
'BLOCK', 5000,
'STREAMS', 'user:activity', '>'
);
// Acknowledge processed messages
await client.xack('user:activity', 'analytics-group', ...messageIds);
Caching Patterns với Redis
1. Cache-Aside Pattern
class CacheAside {
constructor(redisClient, dataSource) {
this.redis = redisClient;
this.dataSource = dataSource;
}
async get(key, fetchFunction, ttl = 3600) {
// Thử lấy từ cache
let data = await this.redis.get(key);
if (data) {
return JSON.parse(data);
}
// Cache miss - lấy từ source
data = await fetchFunction();
if (data) {
await this.redis.setex(key, ttl, JSON.stringify(data));
}
return data;
}
async invalidate(key) {
await this.redis.del(key);
}
async update(key, data, ttl = 3600) {
// Update source trước
await this.dataSource.update(key, data);
// Sau đó update cache
await this.redis.setex(key, ttl, JSON.stringify(data));
}
}
// Usage
const cache = new CacheAside(client, database);
// Get user with caching
const user = await cache.get('user:123', async () => {
return await database.getUser(123);
}, 1800); // TTL 30 phút
2. Write-Through Cache
class WriteThroughCache {
constructor(redisClient, dataSource) {
this.redis = redisClient;
this.dataSource = dataSource;
}
async write(key, data, ttl = 3600) {
// Write to source first
await this.dataSource.write(key, data);
// Then update cache
await this.redis.setex(key, ttl, JSON.stringify(data));
return data;
}
async read(key) {
return await this.get(key);
}
}
3. Write-Behind Cache
class WriteBehindCache {
constructor(redisClient, dataSource, batchSize = 100, flushInterval = 5000) {
this.redis = redisClient;
this.dataSource = dataSource;
this.batchSize = batchSize;
this.flushInterval = flushInterval;
this.writeBuffer = new Map();
this.startFlushTimer();
}
async write(key, data, ttl = 3600) {
// Update cache immediately
await this.redis.setex(key, ttl, JSON.stringify(data));
// Buffer write to source
this.writeBuffer.set(key, data);
// Flush nếu buffer đầy
if (this.writeBuffer.size >= this.batchSize) {
await this.flush();
}
}
async flush() {
if (this.writeBuffer.size === 0) return;
const batch = new Map(this.writeBuffer);
this.writeBuffer.clear();
try {
// Batch write to source
await this.dataSource.batchWrite(Array.from(batch.entries()));
} catch (error) {
// Restore buffer nếu fail
for (const [key, data] of batch) {
this.writeBuffer.set(key, data);
}
throw error;
}
}
startFlushTimer() {
setInterval(() => {
this.flush().catch(console.error);
}, this.flushInterval);
}
}
Pub/Sub với Redis
1. Basic Pub/Sub
// Publisher
await client.publish('notifications', JSON.stringify({
type: 'user_login',
userId: 123,
timestamp: Date.now()
}));
// Subscriber
const subscriber = redis.createClient();
const publisher = redis.createClient();
await subscriber.subscribe('notifications');
subscriber.on('message', (channel, message) => {
const data = JSON.parse(message);
console.log(`Received ${data.type} for user ${data.userId}`);
// Xử lý notification
handleNotification(data);
});
async function handleNotification(data) {
switch (data.type) {
case 'user_login':
await sendWelcomeEmail(data.userId);
break;
case 'order_completed':
await sendOrderConfirmation(data.orderId);
break;
}
}
2. Pattern-based Pub/Sub
// Subscribe to multiple channels với pattern
await subscriber.psubscribe('user:*:events');
subscriber.on('pmessage', (pattern, channel, message) => {
const userId = channel.split(':')[1];
const event = JSON.parse(message);
console.log(`User ${userId} event:`, event);
});
// Publishers
await publisher.publish('user:123:events', JSON.stringify({ type: 'login' }));
await publisher.publish('user:456:events', JSON.stringify({ type: 'logout' }));
Session Management với Redis
class SessionStore {
constructor(redisClient, ttl = 3600) {
this.redis = redisClient;
this.ttl = ttl;
}
async createSession(userId, sessionData) {
const sessionId = this.generateSessionId();
const sessionKey = `session:${sessionId}`;
const session = {
userId,
data: sessionData,
createdAt: Date.now(),
lastAccessed: Date.now()
};
await this.redis.setex(sessionKey, this.ttl, JSON.stringify(session));
// Lưu mapping user -> sessions để quản lý
await this.redis.sadd(`user:${userId}:sessions`, sessionId);
return sessionId;
}
async getSession(sessionId) {
const sessionKey = `session:${sessionId}`;
const sessionData = await this.redis.get(sessionKey);
if (!sessionData) {
return null;
}
const session = JSON.parse(sessionData);
session.lastAccessed = Date.now();
// Extend TTL khi access
await this.redis.setex(sessionKey, this.ttl, JSON.stringify(session));
return session;
}
async destroySession(sessionId) {
const sessionKey = `session:${sessionId}`;
const sessionData = await this.redis.get(sessionKey);
if (sessionData) {
const session = JSON.parse(sessionData);
// Xóa session
await this.redis.del(sessionKey);
// Xóa khỏi user sessions
await this.redis.srem(`user:${session.userId}:sessions`, sessionId);
}
}
async getUserSessions(userId) {
const sessionIds = await this.redis.smembers(`user:${userId}:sessions`);
const sessions = [];
for (const sessionId of sessionIds) {
const sessionData = await this.redis.get(`session:${sessionId}`);
if (sessionData) {
sessions.push(JSON.parse(sessionData));
}
}
return sessions;
}
generateSessionId() {
return require('crypto').randomBytes(32).toString('hex');
}
}
// Usage với Express
const sessionStore = new SessionStore(client);
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// Validate credentials
const user = await validateUser(username, password);
if (user) {
const sessionId = await sessionStore.createSession(user.id, {
username: user.username,
role: user.role
});
res.cookie('sessionId', sessionId, { httpOnly: true, secure: true });
res.json({ success: true, user: { id: user.id, username: user.username } });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
app.get('/profile', async (req, res) => {
const sessionId = req.cookies.sessionId;
if (!sessionId) {
return res.status(401).json({ error: 'No session' });
}
const session = await sessionStore.getSession(sessionId);
if (!session) {
return res.status(401).json({ error: 'Invalid session' });
}
res.json({ user: session.data });
});
Rate Limiting với Redis
class RateLimiter {
constructor(redisClient, windowMs = 60000, maxRequests = 100) {
this.redis = redisClient;
this.windowMs = windowMs;
this.maxRequests = maxRequests;
}
async isAllowed(identifier) {
const key = `rate_limit:${identifier}`;
const window = Math.floor(Date.now() / this.windowMs);
const windowKey = `${key}:${window}`;
// Sử dụng sliding window
const pipeline = this.redis.pipeline();
// Tăng counter cho current window
pipeline.incr(windowKey);
pipeline.expire(windowKey, Math.ceil(this.windowMs / 1000));
// Lấy previous window
const prevWindowKey = `${key}:${window - 1}`;
pipeline.get(prevWindowKey);
const results = await pipeline.exec();
const currentCount = results[0][1];
const prevCount = parseInt(results[2][1]) || 0;
// Tính toán sliding window count
const prevWindowWeight = 1 - (Date.now() % this.windowMs) / this.windowMs;
const slidingCount = Math.floor(currentCount + prevCount * prevWindowWeight);
return {
allowed: slidingCount <= this.maxRequests,
current: slidingCount,
limit: this.maxRequests,
remaining: Math.max(0, this.maxRequests - slidingCount)
};
}
async middleware(req, res, next) {
const identifier = req.ip || req.connection.remoteAddress;
try {
const rateLimit = await this.isAllowed(identifier);
// Thêm headers
res.setHeader('X-RateLimit-Limit', this.maxRequests);
res.setHeader('X-RateLimit-Remaining', rateLimit.remaining);
res.setHeader('X-RateLimit-Reset', Date.now() + this.windowMs);
if (!rateLimit.allowed) {
res.setHeader('Retry-After', Math.ceil(this.windowMs / 1000));
return res.status(429).json({
error: 'Too many requests',
retryAfter: Math.ceil(this.windowMs / 1000)
});
}
next();
} catch (error) {
console.error('Rate limiting error:', error);
// Cho phép request nếu Redis fail
next();
}
}
}
// Usage với Express
const rateLimiter = new RateLimiter(client, 60000, 100); // 100 requests per minute
app.use('/api/', (req, res, next) => rateLimiter.middleware(req, res, next));
Redis Cluster và High Availability
Redis Sentinel
const redis = require('redis');
// Sentinel configuration
const sentinelConfig = {
sentinels: [
{ host: 'sentinel1', port: 26379 },
{ host: 'sentinel2', port: 26379 },
{ host: 'sentinel3', port: 26379 }
],
name: 'mymaster',
role: 'master'
};
const client = redis.createClient(sentinelConfig);
client.on('error', (err) => {
console.error('Redis error:', err);
});
client.on('connect', () => {
console.log('Connected to Redis master via Sentinel');
});
Redis Cluster
const redis = require('redis');
// Cluster configuration
const clusterConfig = {
host: 'localhost',
port: 6379,
enableReadyCheck: true,
maxRetriesPerRequest: 3,
retryDelayOnFailover: 100,
slotsRefreshTimeout: 2000,
slotsRefreshInterval: 5000
};
const nodes = [
{ host: '127.0.0.1', port: 7000 },
{ host: '127.0.0.1', port: 7001 },
{ host: '127.0.0.1', port: 7002 },
{ host: '127.0.0.1', port: 7003 },
{ host: '127.0.0.1', port: 7004 },
{ host: '127.0.0.1', port: 7005 }
];
const cluster = new redis.Cluster(nodes, clusterConfig);
cluster.on('error', (err) => {
console.error('Cluster error:', err);
});
cluster.on('ready', () => {
console.log('Connected to Redis Cluster');
});
Performance Tuning
Connection Pooling
const redis = require('redis');
const { promisify } = require('util');
class RedisPool {
constructor(config = {}) {
this.config = {
host: 'localhost',
port: 6379,
maxConnections: 10,
minConnections: 2,
acquireTimeoutMillis: 3000,
idleTimeoutMillis: 30000,
...config
};
this.pool = [];
this.available = [];
this.waiting = [];
this.initializePool();
}
async initializePool() {
for (let i = 0; i < this.config.minConnections; i++) {
await this.createConnection();
}
}
async createConnection() {
const client = redis.createClient(this.config);
const connection = {
client,
lastUsed: Date.now(),
inUse: false,
id: Math.random().toString(36)
};
client.on('error', (err) => {
console.error(`Connection ${connection.id} error:`, err);
this.removeConnection(connection);
});
client.on('end', () => {
this.removeConnection(connection);
});
await new Promise((resolve) => client.once('ready', resolve));
this.pool.push(connection);
this.available.push(connection);
return connection;
}
async acquire() {
const startTime = Date.now();
while (Date.now() - startTime < this.config.acquireTimeoutMillis) {
const connection = this.available.shift();
if (connection) {
connection.inUse = true;
connection.lastUsed = Date.now();
return connection;
}
if (this.pool.length < this.config.maxConnections) {
const newConnection = await this.createConnection();
newConnection.inUse = true;
return newConnection;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
throw new Error('Connection pool timeout');
}
release(connection) {
if (!connection.inUse) return;
connection.inUse = false;
connection.lastUsed = Date.now();
this.available.push(connection);
// Xử lý waiting requests
if (this.waiting.length > 0) {
const waitingRequest = this.waiting.shift();
waitingRequest.resolve(this.acquire());
}
}
removeConnection(connection) {
const poolIndex = this.pool.indexOf(connection);
const availableIndex = this.available.indexOf(connection);
if (poolIndex > -1) this.pool.splice(poolIndex, 1);
if (availableIndex > -1) this.available.splice(availableIndex, 1);
connection.client.quit();
}
async close() {
for (const connection of this.pool) {
connection.client.quit();
}
this.pool = [];
this.available = [];
}
}
Pipeline và Transactions
// Pipeline - gửi nhiều commands trong một request
async function batchOperations() {
const pipeline = client.pipeline();
// Thêm nhiều operations vào pipeline
for (let i = 0; i < 1000; i++) {
pipeline.set(`key:${i}`, `value:${i}`);
}
// Execute tất cả commands
const results = await pipeline.exec();
console.log(`Executed ${results.length} commands`);
}
// Transaction - MULTI/EXEC
async function transactionExample() {
const multi = client.multi();
// Thêm commands vào transaction
multi.set('account:1:balance', '1000');
multi.set('account:2:balance', '500');
multi.decrby('account:1:balance', 100);
multi.incrby('account:2:balance', 100);
// Execute transaction
const results = await multi.exec();
// Kiểm tra results
const balance1 = await client.get('account:1:balance'); // 900
const balance2 = await client.get('account:2:balance'); // 600
console.log(`Account 1: ${balance1}, Account 2: ${balance2}`);
}
// Optimistic locking với WATCH
async function optimisticLocking() {
await client.watch('counter');
const current = await client.get('counter');
const newValue = parseInt(current) + 1;
const multi = client.multi();
multi.set('counter', newValue);
const results = await multi.exec();
if (results === null) {
console.log('Transaction failed - key was modified');
// Retry hoặc handle conflict
} else {
console.log('Transaction successful');
}
}
So Sánh Redis Với Các Database Khác
Redis vs Memcached
| Tính năng | Redis | Memcached |
|---|---|---|
| Data structures | Strings, lists, sets, sorted sets, hashes, streams | Chỉ strings |
| Persistence | Có (RDB, AOF) | Không |
| Replication | Có | Không |
| Pub/Sub | Có | Không |
| Lua scripting | Có | Không |
| Memory usage | Cao hơn | Thấp hơn |
| Performance | Rất nhanh | Cực nhanh |
Redis vs MongoDB
| Tính năng | Redis | MongoDB |
|---|---|---|
| Storage | In-memory | Disk |
| Data model | Key-value, data structures | Document |
| Querying | Đơn giản | Phức tạp |
| Scaling | Clustering | Sharding |
| Use case | Cache, session, real-time | General purpose |
| Latency | Sub-millisecond | Millisecond |
Redis vs PostgreSQL
| Tính năng | Redis | PostgreSQL |
|---|---|---|
| Storage | In-memory | Disk |
| ACID | Partial | Full |
| SQL support | Không | Có |
| Complex queries | Không | Có |
| Transactions | Có (limited) | Có (full) |
| Use case | Cache, real-time | OLTP, OLAP |
Best Practices
1. Key Design
// Good key design
const keyPatterns = {
user: 'user:{userId}',
session: 'session:{sessionId}',
cache: 'cache:{entity}:{id}',
rateLimit: 'rate_limit:{identifier}:{window}',
leaderboard: 'leaderboard:{game}:{period}'
};
// Sử dụng
const userKey = `user:${userId}`;
const sessionKey = `session:${sessionId}`;
const cacheKey = `cache:product:${productId}`;
2. Memory Optimization
// Sử dụng appropriate data structures
// Thay vì nhiều keys
await client.set('user:1:name', 'John');
await client.set('user:1:email', 'john@example.com');
await client.set('user:1:age', '30');
// Dùng hash để tiết kiệm memory
await client.hset('user:1', 'name', 'John', 'email', 'john@example.com', 'age', '30');
// Sử dụng compression cho large values
const zlib = require('zlib');
const { promisify } = require('util');
const compress = promisify(zlib.gzip);
const decompress = promisify(zlib.gunzip);
async function setCompressed(key, data) {
const compressed = await compress(JSON.stringify(data));
await client.setex(key, 3600, compressed);
}
async function getCompressed(key) {
const compressed = await client.get(key);
if (!compressed) return null;
const decompressed = await decompress(compressed);
return JSON.parse(decompressed.toString());
}
3. Error Handling và Retry
class RedisClient {
constructor(config) {
this.config = config;
this.client = null;
this.connect();
}
async connect() {
this.client = redis.createClient(this.config);
this.client.on('error', (err) => {
console.error('Redis error:', err);
this.handleError(err);
});
this.client.on('connect', () => {
console.log('Connected to Redis');
});
this.client.on('reconnecting', () => {
console.log('Reconnecting to Redis...');
});
}
async executeWithRetry(operation, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error.message);
if (attempt === maxRetries) {
throw error;
}
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 100));
}
}
}
handleError(error) {
// Log error, send alert, etc.
console.error('Redis connection error:', error);
}
}
Kết Luận
Redis là một in-memory database đa năng và mạnh mẽ, phù hợp cho nhiều use case khác nhau trong ứng dụng hiện đại. Với hiệu suất cao, độ linh hoạt lớn và ecosystem phong phú, Redis là lựa chọn tuyệt vời cho:
- Caching: Tăng tốc ứng dụng với sub-millisecond latency
- Session management: Lưu trữ session data cho web applications
- Real-time analytics: Xử lý và phân tích dữ liệu theo thời gian thực
- Pub/Sub messaging: Giao tiếp real-time giữa components
- Rate limiting: Giới hạn requests và prevent abuse
- Leaderboards: Real-time ranking systems
- Queue management: Task queues và job processing
Việc hiểu rõ các data structures, patterns và best practices của Redis sẽ giúp bạn xây dựng ứng dụng hiệu quả và scalable hơn.