Xây dựng Session Store với Redis
Bạn có biết?
Khi bạn đăng nhập Facebook, hệ thống tạo một session — một “vé thông hành” lưu trạng thái đăng nhập của bạn. Nếu lưu session trên file server, bạn không thể scale sang nhiều server. Nếu lưu trên database, mỗi request đều phải query database → chậm. Redis giải quyết cả 2 vấn đề: nhanh, và mọi server đều truy cập được.
Session là gì?
Session là cơ chế lưu trữ trạng thái user giữa các request HTTP (vốn stateless). Khi user đăng nhập, server tạo session chứa thông tin user và trả về session ID qua cookie.
Tại sao dùng Redis làm Session Store?
- Nhanh — In-memory, đọc/ghi trong microsecond
- Shared — Mọi server đều truy cập cùng dữ liệu
- TTL tự động — Session hết hạn tự động xóa
- Scalable — Hàng triệu session không vấn đề
Cấu trúc dữ liệu Session
Mỗi session là một Hash trong Redis:
# Key: session:{session_id}
# Value: Hash chứa thông tin session
127.0.0.1:6379> HSET session:abc123 user_id 42 username "tan" role "admin"
(integer) 3
127.0.0.1:6379> HSET session:abc123 login_time "2026-03-30T12:00:00Z"
(integer) 1
127.0.0.1:6379> HSET session:abc123 ip "192.168.1.1" user_agent "Mozilla/5.0"
(integer) 1
# Đặt TTL — session hết hạn sau 30 phút
127.0.0.1:6379> EXPIRE session:abc123 1800
(integer) 1
# Lấy thông tin session
127.0.0.1:6379> HGETALL session:abc123
1) "user_id"
2) "42"
3) "username"
4) "tan"
5) "role"
6) "admin"
7) "login_time"
8) "2026-03-30T12:00:00Z"
Python Implementation
Session Manager
import redis
import uuid
import time
import json
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
class RedisSession:
def __init__(self, session_id=None, ttl=1800):
self.ttl = ttl
if session_id and self.exists(session_id):
self.session_id = session_id
else:
self.session_id = str(uuid.uuid4())
def key(self):
return f"session:{self.session_id}"
def exists(self, session_id):
return r.exists(f"session:{session_id}")
def create(self, user_data):
"""Tạo session mới"""
pipe = r.pipeline()
pipe.hset(self.key(), mapping={
'user_id': str(user_data['user_id']),
'username': user_data['username'],
'role': user_data.get('role', 'user'),
'login_time': str(time.time()),
'ip': user_data.get('ip', ''),
})
pipe.expire(self.key(), self.ttl)
pipe.execute()
return self.session_id
def get(self, field=None):
"""Lấy thông tin session"""
if field:
return r.hget(self.key(), field)
return r.hgetall(self.key())
def set(self, field, value):
"""Cập nhật session"""
r.hset(self.key(), field, value)
r.expire(self.key(), self.ttl) # Reset TTL
def delete(self):
"""Xóa session (logout)"""
r.delete(self.key())
def refresh(self):
"""Gia hạn session"""
r.expire(self.key(), self.ttl)
Flask Integration
from flask import Flask, request, jsonify, make_response
import functools
app = Flask(__name__)
def login_required(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
session_id = request.cookies.get('session_id')
if not session_id:
return jsonify({'error': 'Chưa đăng nhập'}), 401
session = RedisSession(session_id)
user = session.get()
if not user:
return jsonify({'error': 'Session hết hạn'}), 401
request.user = user
session.refresh() # Gia hạn session
return f(*args, **kwargs)
return wrapper
@app.route('/login', methods=['POST'])
def login():
data = request.json
# Xác thực user (giả sử thành công)
user_data = {
'user_id': 42,
'username': data['username'],
'role': 'user',
'ip': request.remote_addr,
}
session = RedisSession(ttl=1800)
session_id = session.create(user_data)
resp = make_response(jsonify({'message': 'Đăng nhập thành công'}))
resp.set_cookie('session_id', session_id, httponly=True, max_age=1800)
return resp
@app.route('/profile')
@login_required
def profile():
return jsonify({'user': request.user})
@app.route('/logout', methods=['POST'])
def logout():
session_id = request.cookies.get('session_id')
if session_id:
RedisSession(session_id).delete()
resp = make_response(jsonify({'message': 'Đã đăng xuất'}))
resp.delete_cookie('session_id')
return resp
Quản lý Session nâng cao
Session cho nhiều thiết bị
# Lưu danh sách session của mỗi user
127.0.0.1:6379> SADD user:sessions:42 session:abc123 session:def456
(integer) 2
# Lấy tất cả session của user
127.0.0.1:6379> SMEMBERS user:sessions:42
1) "session:abc123"
2) "session:def456"
# Đăng xuất tất cả thiết bị
127.0.0.1:6379> DEL session:abc123 session:def456 user:sessions:42
(integer) 3
Session Activity Tracking
# Cập nhật thời gian hoạt động cuối
127.0.0.1:6379> HSET session:abc123 last_activity "1711785600"
(integer) 1
# Đếm số session đang hoạt động
127.0.0.1:6379> SCAN 0 MATCH session:* COUNT 100
# Duyệt qua tất cả session keys
# Hoặc dùng Sorted Set để track theo thời gian
127.0.0.1:6379> ZADD active_sessions 1711785600 session:abc123
(integer) 1
# Xóa session không hoạt động > 30 phút
127.0.0.1:6379> ZREMRANGEBYSCORE active_sessions 0 1711783800
(integer) 5
So sánh Session Store
| Tiêu chí | File | Database | Redis |
|---|---|---|---|
| Tốc độ | Nhanh | Chậm | Nhanh nhất |
| Shared giữa server | Không | Có | Có |
| TTL tự động | Không | Không | Có |
| Scalability | Kém | Tốt | Tốt nhất |
| Persistence | Có | Có | Tùy cấu hình |
| Complexity | Thấp | Trung bình | Trung bình |
Best Practices
- HttpOnly cookie — Ngăn XSS attack
- Secure cookie — Chỉ gửi qua HTTPS
- Đặt TTL hợp lý — 30 phút cho web, 7 ngày cho mobile
- Không lưu sensitive data — Không lưu password, token trong session
- Rotate session ID — Tạo session mới sau khi đăng nhập lại
- Logout xóa session — Dùng
DELthay vì để TTL hết hạn

Bước tiếp theo
Bạn đã xây dựng Session Store hoàn chỉnh! Tiếp theo, chúng ta sẽ xây dựng Leaderboard — hệ thống xếp hạng realtime với Redis Sorted Sets.