摘要:Redis不只是缓存,它是一把瑞士军刀。本文用Python实现Redis在缓存、消息队列、分布式锁、限流器等场景的最佳实践,每个场景都有生产级代码。
连接Redis
import redis
import json
from functools import wraps
# 连接池(推荐)
pool = redis.ConnectionPool(
host='localhost',
port=6379,
db=0,
decode_responses=True, # 自动解码为字符串
max_connections=20,
)
r = redis.Redis(connection_pool=pool)
# 测试连接
r.ping() # True
场景1:缓存装饰器
def redis_cache(prefix='cache', ttl=300):
"""Redis缓存装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 生成缓存key
key_parts = [prefix, func.__name__] + [str(a) for a in args]
if kwargs:
key_parts += [f'{k}={v}' for k, v in sorted(kwargs.items())]
cache_key = ':'.join(key_parts)
# 尝试读缓存
cached = r.get(cache_key)
if cached is not None:
return json.loads(cached)
# 执行函数
result = func(*args, **kwargs)
# 写入缓存
r.setex(cache_key, ttl, json.dumps(result, ensure_ascii=False))
return result
# 手动清除缓存
def invalidate(*args, **kwargs):
key_parts = [prefix, func.__name__] + [str(a) for a in args]
cache_key = ':'.join(key_parts)
r.delete(cache_key)
wrapper.invalidate = invalidate
return wrapper
return decorator
# 使用
@redis_cache(prefix='user', ttl=600)
def get_user_info(user_id):
"""模拟数据库查询"""
import time
time.sleep(0.5) # 模拟慢查询
return {'id': user_id, 'name': f'用户{user_id}'}
# 第一次调用:0.5s(查数据库)
info = get_user_info(123)
# 第二次调用:<1ms(读缓存)
info = get_user_info(123)
# 清除缓存
get_user_info.invalidate(123)
场景2:简易消息队列
class RedisQueue:
"""基于Redis List的消息队列"""
def __init__(self, name, redis_client=None):
self.r = redis_client or r
self.key = f'queue:{name}'
self.processing_key = f'queue:{name}:processing'
def push(self, message):
"""发送消息"""
self.r.lpush(self.key, json.dumps(message, ensure_ascii=False))
def pop(self, timeout=0):
"""接收消息(阻塞)"""
result = self.r.brpoplpush(self.key, self.processing_key, timeout)
if result:
return json.loads(result)
return None
def ack(self, message):
"""确认消息已处理"""
self.r.lrem(self.processing_key, 1, json.dumps(message, ensure_ascii=False))
def size(self):
return self.r.llen(self.key)
def processing_count(self):
return self.r.llen(self.processing_key)
# 生产者
queue = RedisQueue('tasks')
queue.push({'type': 'send_email', 'to': 'user@test.com', 'subject': '通知'})
queue.push({'type': 'generate_report', 'report_id': 42})
# 消费者
import signal
import sys
running = True
def shutdown(sig, frame):
global running
running = False
signal.signal(signal.SIGINT, shutdown)
while running:
msg = queue.pop(timeout=5)
if msg:
try:
print(f'处理消息: {msg}')
# process(msg)
queue.ack(msg)
except Exception as e:
print(f'处理失败: {e}')
场景3:分布式锁
import uuid
import time
class RedisLock:
"""Redis分布式锁"""
def __init__(self, name, timeout=10, redis_client=None):
self.r = redis_client or r
self.key = f'lock:{name}'
self.timeout = timeout
self.token = str(uuid.uuid4())
def acquire(self, blocking=True, retry_interval=0.1):
"""获取锁"""
while True:
if self.r.set(self.key, self.token, nx=True, ex=self.timeout):
return True
if not blocking:
return False
time.sleep(retry_interval)
def release(self):
"""释放锁(Lua脚本保证原子性)"""
lua_script = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
self.r.eval(lua_script, 1, self.key, self.token)
def __enter__(self):
self.acquire()
return self
def __exit__(self, *args):
self.release()
# 使用
with RedisLock('update_inventory', timeout=30):
# 这段代码同一时刻只有一个进程能执行
current = int(r.get('inventory:item_1') or 0)
if current > 0:
r.set('inventory:item_1', current - 1)
场景4:滑动窗口限流器
class RateLimiter:
"""滑动窗口限流器"""
def __init__(self, name, max_requests, window_seconds, redis_client=None):
self.r = redis_client or r
self.key = f'ratelimit:{name}'
self.max_requests = max_requests
self.window = window_seconds
def is_allowed(self, identifier):
"""检查是否允许请求"""
key = f'{self.key}:{identifier}'
now = time.time()
pipe = self.r.pipeline()
# 移除窗口外的记录
pipe.zremrangebyscore(key, 0, now - self.window)
# 统计窗口内的请求数
pipe.zcard(key)
# 添加当前请求
pipe.zadd(key, {f'{now}:{uuid.uuid4().hex[:8]}': now})
# 设置过期时间
pipe.expire(key, self.window)
results = pipe.execute()
current_count = results[1]
return current_count < self.max_requests
# 使用:每个IP每分钟最多30次请求
limiter = RateLimiter('api', max_requests=30, window_seconds=60)
def handle_request(ip):
if not limiter.is_allowed(ip):
return {'error': '请求过于频繁,请稍后再试'}, 429
return {'data': 'ok'}, 200
场景5:排行榜
class Leaderboard:
"""Redis Sorted Set实现排行榜"""
def __init__(self, name, redis_client=None):
self.r = redis_client or r
self.key = f'leaderboard:{name}'
def update_score(self, user_id, score):
"""更新分数"""
self.r.zadd(self.key, {user_id: score})
def increment_score(self, user_id, delta=1):
"""增加分数"""
return self.r.zincrby(self.key, delta, user_id)
def get_rank(self, user_id):
"""获取排名(从0开始)"""
rank = self.r.zrevrank(self.key, user_id)
return rank + 1 if rank is not None else None
def get_score(self, user_id):
return self.r.zscore(self.key, user_id)
def top_n(self, n=10):
"""获取前N名"""
results = self.r.zrevrange(self.key, 0, n - 1, withscores=True)
return [{'user_id': uid, 'score': int(score), 'rank': i + 1}
for i, (uid, score) in enumerate(results)]
def around_me(self, user_id, n=5):
"""获取某用户附近的排名"""
rank = self.r.zrevrank(self.key, user_id)
if rank is None:
return []
start = max(0, rank - n)
end = rank + n
results = self.r.zrevrange(self.key, start, end, withscores=True)
return [{'user_id': uid, 'score': int(score), 'rank': start + i + 1}
for i, (uid, score) in enumerate(results)]
# 使用
lb = Leaderboard('weekly_score')
lb.update_score('user_1', 1500)
lb.update_score('user_2', 2300)
lb.increment_score('user_1', 100)
print(lb.top_n(10))
print(lb.get_rank('user_1'))
场景6:发布订阅
import threading
# 发布者
def publish_event(channel, message):
r.publish(channel, json.dumps(message, ensure_ascii=False))
# 订阅者
def subscriber(channels):
pubsub = r.pubsub()
pubsub.subscribe(channels)
for message in pubsub.listen():
if message['type'] == 'message':
data = json.loads(message['data'])
print(f"[{message['channel']}] {data}")
# 后台运行订阅者
thread = threading.Thread(target=subscriber, args=(['notifications', 'alerts'],))
thread.daemon = True
thread.start()
# 发布消息
publish_event('notifications', {'type': 'new_order', 'order_id': 12345})
总结
Redis的核心应用场景:
- 缓存:减少数据库压力,提升响应速度
- 队列:异步任务处理,削峰填谷
- 分布式锁:多进程/多机器的互斥操作
- 限流:保护后端服务不被打垮
- 排行榜:实时排名,Sorted Set天然适合
- 发布订阅:简单的事件通知
选择Redis数据结构的原则:String做缓存,List做队列,Set做去重,Sorted Set做排行,Hash做对象存储。