Loading...
Loading...
Compare original and translation side by side
| Use Case | Structure | Example Key |
|---|---|---|
| Simple cache | String | |
| User session | Hash | |
| Leaderboard | Sorted Set | |
| Unique visitors | Set | |
| Activity feed | List | |
| Event stream | Stream | |
| Counters / rate limits | String (INCR) | |
| Bloom filter / HLL | HyperLogLog | |
| 用例 | 数据结构 | 示例键名 |
|---|---|---|
| 简单缓存 | 字符串 | |
| 用户会话 | 哈希 | |
| 排行榜 | 有序集合 | |
| 独立访客统计 | 集合 | |
| 活动信息流 | 列表 | |
| 事件流 | 流 | |
| 计数器/限流 | 字符串(INCR命令) | |
| 布隆过滤器/基数统计 | HyperLogLog | |
import redis
import json
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
def get_product(product_id: int):
cache_key = f"product:{product_id}"
cached = r.get(cache_key)
if cached:
return json.loads(cached)
product = db.query("SELECT * FROM products WHERE id = %s", product_id)
r.setex(cache_key, 3600, json.dumps(product)) # TTL: 1 hour
return productimport redis
import json
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
def get_product(product_id: int):
cache_key = f"product:{product_id}"
cached = r.get(cache_key)
if cached:
return json.loads(cached)
product = db.query("SELECT * FROM products WHERE id = %s", product_id)
r.setex(cache_key, 3600, json.dumps(product)) # TTL: 1 hour
return productdef update_product(product_id: int, data: dict):
# Write to DB first
db.execute("UPDATE products SET ... WHERE id = %s", product_id)
# Immediately update cache
cache_key = f"product:{product_id}"
r.setex(cache_key, 3600, json.dumps(data))def update_product(product_id: int, data: dict):
# 先写入数据库
db.execute("UPDATE products SET ... WHERE id = %s", product_id)
# 立即更新缓存
cache_key = f"product:{product_id}"
r.setex(cache_key, 3600, json.dumps(data))undefinedundefinedundefinedundefinedimport time
import uuid
def create_session(user_id: int, ttl: int = 86400) -> str:
session_id = str(uuid.uuid4())
key = f"session:{session_id}"
pipe = r.pipeline(transaction=True)
pipe.hset(key, mapping={
"user_id": user_id,
"created_at": int(time.time()),
})
pipe.expire(key, ttl)
pipe.execute()
return session_id
def get_session(session_id: str) -> dict | None:
data = r.hgetall(f"session:{session_id}")
return data if data else None
def delete_session(session_id: str):
r.delete(f"session:{session_id}")import time
import uuid
def create_session(user_id: int, ttl: int = 86400) -> str:
session_id = str(uuid.uuid4())
key = f"session:{session_id}"
pipe = r.pipeline(transaction=True)
pipe.hset(key, mapping={
"user_id": user_id,
"created_at": int(time.time()),
})
pipe.expire(key, ttl)
pipe.execute()
return session_id
def get_session(session_id: str) -> dict | None:
data = r.hgetall(f"session:{session_id}")
return data if data else None
def delete_session(session_id: str):
r.delete(f"session:{session_id}")def is_rate_limited(user_id: int, limit: int = 100, window: int = 60) -> bool:
key = f"ratelimit:{user_id}:{int(time.time()) // window}"
pipe = r.pipeline(transaction=True)
pipe.incr(key)
pipe.expire(key, window)
count, _ = pipe.execute()
return count > limitdef is_rate_limited(user_id: int, limit: int = 100, window: int = 60) -> bool:
key = f"ratelimit:{user_id}:{int(time.time()) // window}"
pipe = r.pipeline(transaction=True)
pipe.incr(key)
pipe.expire(key, window)
count, _ = pipe.execute()
return count > limit-- sliding_window.lua
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
-- Use unique member (now + sequence) to avoid collisions within the same millisecond
local seq_key = key .. ':seq'
local seq = redis.call('INCR', seq_key)
redis.call('EXPIRE', seq_key, math.ceil(window / 1000))
redis.call('ZADD', key, now, now .. '-' .. seq)
redis.call('EXPIRE', key, math.ceil(window / 1000))
return 1
end
return 0sliding_window = r.register_script(open('sliding_window.lua').read())
def allow_request(user_id: int) -> bool:
key = f"ratelimit:sliding:{user_id}"
now = int(time.time() * 1000)
return bool(sliding_window(keys=[key], args=[now, 60000, 100]))-- sliding_window.lua
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
-- 使用唯一成员(now + 序列号)避免同一毫秒内的冲突
local seq_key = key .. ':seq'
local seq = redis.call('INCR', seq_key)
redis.call('EXPIRE', seq_key, math.ceil(window / 1000))
redis.call('ZADD', key, now, now .. '-' .. seq)
redis.call('EXPIRE', key, math.ceil(window / 1000))
return 1
end
return 0sliding_window = r.register_script(open('sliding_window.lua').read())
def allow_request(user_id: int) -> bool:
key = f"ratelimit:sliding:{user_id}"
now = int(time.time() * 1000)
return bool(sliding_window(keys=[key], args=[now, 60000, 100]))import uuid
def acquire_lock(resource: str, ttl_ms: int = 5000) -> str | None:
lock_key = f"lock:{resource}"
token = str(uuid.uuid4())
acquired = r.set(lock_key, token, px=ttl_ms, nx=True)
return token if acquired else None
def release_lock(resource: str, token: str) -> bool:
release_script = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
result = r.eval(release_script, 1, f"lock:{resource}", token)
return bool(result)import uuid
def acquire_lock(resource: str, ttl_ms: int = 5000) -> str | None:
lock_key = f"lock:{resource}"
token = str(uuid.uuid4())
acquired = r.set(lock_key, token, px=ttl_ms, nx=True)
return token if acquired else None
def release_lock(resource: str, token: str) -> bool:
release_script = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
result = r.eval(release_script, 1, f"lock:{resource}", token)
return bool(result)
> For multi-node setups use the `redlock-py` library which implements the full Redlock algorithm.
> 多节点部署场景下,请使用实现完整Redlock算法的`redlock-py`库。undefinedundefinedundefinedundefinedundefinedundefined
> Prefer **Streams** over Pub/Sub when you need delivery guarantees, consumer groups, or replay.
> 当你需要投递保障、消费者组或重放功能时,优先选择**Streams**而非Pub/Sub。undefinedundefinedundefinedundefined| Data Type | Suggested TTL |
|---|---|
| User session | 24h ( |
| API response cache | 5–15 min |
| Rate limit window | Match window size |
| Short-lived tokens | 5–10 min |
| Leaderboard | 1h–24h |
| Static/reference data | 1h–1 week |
| 数据类型 | 建议TTL |
|---|---|
| 用户会话 | 24小时 ( |
| API响应缓存 | 5–15分钟 |
| 限流窗口 | 匹配窗口大小 |
| 短期令牌 | 5–10分钟 |
| 排行榜 | 1小时–24小时 |
| 静态/参考数据 | 1小时–1周 |
from redis import ConnectionPool, Redis
pool = ConnectionPool(
host='localhost',
port=6379,
db=0,
max_connections=20,
decode_responses=True,
socket_connect_timeout=2,
socket_timeout=2,
)
r = Redis(connection_pool=pool)from redis import ConnectionPool, Redis
pool = ConnectionPool(
host='localhost',
port=6379,
db=0,
max_connections=20,
decode_responses=True,
socket_connect_timeout=2,
socket_timeout=2,
)
r = Redis(connection_pool=pool)from redis.cluster import RedisCluster
r = RedisCluster(
startup_nodes=[{"host": "redis-1", "port": 6379}],
decode_responses=True,
skip_full_coverage_check=True,
)from redis.cluster import RedisCluster
r = RedisCluster(
startup_nodes=[{"host": "redis-1", "port": 6379}],
decode_responses=True,
skip_full_coverage_check=True,
)from redis.sentinel import Sentinel
sentinel = Sentinel(
[('sentinel-1', 26379), ('sentinel-2', 26379)],
socket_timeout=0.5,
)
master = sentinel.master_for('mymaster', decode_responses=True)
replica = sentinel.slave_for('mymaster', decode_responses=True)from redis.sentinel import Sentinel
sentinel = Sentinel(
[('sentinel-1', 26379), ('sentinel-2', 26379)],
socket_timeout=0.5,
)
master = sentinel.master_for('mymaster', decode_responses=True)
replica = sentinel.slave_for('mymaster', decode_responses=True)| Policy | Behavior | Best For |
|---|---|---|
| Error on write when full | Queues / critical data |
| Evict least recently used | General cache |
| LRU only among keys with TTL | Mixed data store |
| Evict least frequently used | Skewed access patterns |
| Evict soonest-to-expire | Prioritize long-lived data |
redis.confmaxmemory-policy allkeys-lru| 策略 | 行为 | 适用场景 |
|---|---|---|
| 内存满时写入报错 | 队列/关键数据 |
| 淘汰最近最少使用的键 | 通用缓存 |
| 仅淘汰带TTL的键中最近最少使用的 | 混合数据存储 |
| 淘汰最不常使用的键 | 访问模式倾斜的场景 |
| 淘汰即将过期的键 | 优先保留长期数据 |
redis.confmaxmemory-policy allkeys-lru| Anti-Pattern | Problem | Fix |
|---|---|---|
| Keys with no TTL | Memory grows unbounded | Always set TTL |
| Blocks the server (O(N)) | Use |
| Storing large blobs (>100KB) | Slow serialization, memory pressure | Store reference + fetch from object store |
| Single Redis for everything | No isolation between cache & queue | Use separate DBs or instances |
| Ignoring connection pool limits | Connection exhaustion under load | Size pool to workload |
| Not handling cache miss stampede | Thundering herd on cold start | Use locks or probabilistic early expiry |
| Wipes entire instance | Scope deletes by key pattern |
| 反模式 | 问题 | 解决方案 |
|---|---|---|
| 键未设置TTL | 内存无限增长 | 始终设置TTL |
生产环境使用 | 阻塞服务器(O(N)复杂度) | 使用 |
| 存储大对象(>100KB) | 序列化缓慢,内存压力大 | 存储引用,从对象存储中获取 |
| 单一Redis实例处理所有业务 | 缓存与队列无隔离 | 使用独立数据库或实例 |
| 忽略连接池限制 | 高负载下连接耗尽 | 根据工作负载调整池大小 |
| 未处理缓存击穿 | 冷启动时出现惊群效应 | 使用锁或概率性提前过期 |
随意使用 | 清空整个实例 | 按键模式范围删除 |
import threading
_locks: dict[str, threading.Lock] = {}
_locks_mutex = threading.Lock()
def get_with_lock(key: str, fetch_fn, ttl: int = 300):
cached = r.get(key)
if cached:
return json.loads(cached)
with _locks_mutex:
if key not in _locks:
_locks[key] = threading.Lock()
lock = _locks[key]
with lock:
cached = r.get(key) # Re-check after acquiring lock
if cached:
return json.loads(cached)
value = fetch_fn()
r.setex(key, ttl, json.dumps(value))
return valueNote: for multi-process deployments, replace the in-process lock with/acquire_lockfrom the Distributed Locks section above.release_lock
import threading
_locks: dict[str, threading.Lock] = {}
_locks_mutex = threading.Lock()
def get_with_lock(key: str, fetch_fn, ttl: int = 300):
cached = r.get(key)
if cached:
return json.loads(cached)
with _locks_mutex:
if key not in _locks:
_locks[key] = threading.Lock()
lock = _locks[key]
with lock:
cached = r.get(key) # 获取锁后再次检查
if cached:
return json.loads(cached)
value = fetch_fn()
r.setex(key, ttl, json.dumps(value))
return value注意:多进程部署场景下,将进程内锁替换为分布式锁章节中的/acquire_lock。release_lock
setexpipeline(transaction=True)acquire_lockfinallysetexpipeline(transaction=True)acquire_lockfinally| Pattern | When to Use |
|---|---|
| Cache-aside | Read-heavy, tolerate slight staleness |
| Write-through | Strong consistency required |
| Distributed lock | Prevent concurrent access to a resource |
| Sliding window rate limit | Accurate per-user throttling |
| Redis Streams | Durable event queue with consumer groups |
| Pub/Sub | Broadcast with no delivery guarantees needed |
| Sorted Set leaderboard | Ranked scoring, pagination |
| HyperLogLog | Approximate unique count at low memory |
| 模式 | 适用场景 |
|---|---|
| 缓存旁路 | 读密集型,可容忍轻微数据过期 |
| 写穿透缓存 | 需要强一致性 |
| 分布式锁 | 防止资源并发访问 |
| 滑动窗口限流 | 精确的用户限流 |
| Redis Streams | 带消费者组的持久化事件队列 |
| Pub/Sub | 无需投递保障的广播场景 |
| 有序集合排行榜 | 排名计分、分页 |
| HyperLogLog | 低内存消耗下的近似独立计数 |
postgres-patternsbackend-patternsdatabase-migrationsdjango-patternsdatabase-reviewerpostgres-patternsbackend-patternsdatabase-migrationsdjango-patternsdatabase-reviewer