Django 内置的缓存框架非常灵活,支持多种后端,但在生产环境中,本地内存缓存往往不够用。比如用 Gunicorn 启动多个 worker 进程时,每个进程都有自己独立的内存缓存,一份数据可能会被重复缓存多次,而且无法跨进程失效。Redis 作为独立的内存数据库,天然支持多进程共享,还能持久化到磁盘,于是成了 Django 项目里最有价值的缓存后端之一。

这篇文章会带你走一遍用 Redis 替换默认本地缓存的完整流程,同时聊一聊缓存粒度、失效策略和调优经验。
为什么生产环境更推荐 Redis 缓存
本地内存缓存(LocMemCache)是 Django 默认的缓存后端,优点是零配置、速度极快,但它的数据只存在于当前进程中。如果项目通过 Gunicorn、uWSGI 启动了多个 worker,每个 worker 都会维护一份独立缓存,缓存命中率会随着进程数增加而降低。更麻烦的是,当某个缓存项需要更新时,你很难通知所有进程同时失效,容易出现不同 worker 返回不同数据的情况。
Redis 则完全不同。它是一个独立的键值存储服务,所有 worker 进程、甚至多台服务器上的 Django 实例都连接到同一个 Redis 节点,共享同一份缓存数据。这样一来,缓存命中率不再受进程数影响,失效操作也能立即对所有实例生效。此外,Redis 支持数据持久化(RDB 和 AOF),即使重启服务,缓存数据仍然可能恢复,这对一些不经常变化但读取量极大的配置数据尤为有用。
另一个容易被忽略的优势是 Redis 提供丰富的数据结构。虽然 Django 的缓存抽象只使用字符串操作,但你可以同时利用同一个 Redis 实例做计数器、排行榜、分布式锁等功能,避免为不同场景维护多套存储组件。从运维角度看,这也降低了基础设施的复杂度。
在 Django 中配置 Redis 缓存的完整步骤
要在 Django 中使用 Redis,推荐安装 django-redis 这个第三方包。它封装了 redis-py 的连接管理和序列化逻辑,并且完全兼容 Django 的缓存 API。安装命令如下:
pip install django-redis
安装完成后,在 settings.py 中修改 CACHES 配置:
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'PASSWORD': 'your_redis_password',
'CONNECTION_POOL_KWARGS': {
'max_connections': 100,
},
'SERIALIZER': 'django_redis.serializers.json.JSONSerializer',
},
'KEY_PREFIX': 'myproject',
'TIMEOUT': 300,
}
}
上面配置中,BACKEND 指定使用 django-redis 提供的缓存后端,LOCATION 中的 redis://127.0.0.1:6379/1 表示连接本机 Redis 的 1 号数据库。django-redis 默认使用 pickle 序列化缓存值,但为了可读性和安全性,建议改成 JSON 序列化器。不过要注意,JSON 不能序列化所有 Python 对象,如果缓存内容是 QuerySet 或自定义类,则需要额外处理。
OPTIONS 里的 CLIENT_CLASS 保持 DefaultClient 即可,它内部维护了连接池,CONNECTION_POOL_KWARGS 可以调整最大连接数。KEY_PREFIX 用于给所有缓存键加上统一前缀,防止多个项目共用同一个 Redis 实例时发生键冲突。TIMEOUT 则设置默认过期时间,单位为秒。
配置完成后,可以写一个简单的测试代码验证:
from django.core.cache import cache
cache.set('test_key', 'hello redis', timeout=60)
value = cache.get('test_key')
print(value) # 输出 hello redis
如果控制台打印出 hello redis,说明 Redis 缓存已经成功接入。
缓存粒度与失效策略该怎么设计
接入 Redis 后,下一个问题就是缓存什么、缓存多久、何时失效。Django 提供了多层级的缓存 API,从整页缓存到视图缓存、模板片段缓存,再到最底层的 cache.set 和 cache.get。对于大多数动态接口,建议采用底层 API 手动控制缓存,因为整页缓存容易把用户相关数据也缓存进去,引发安全问题。
缓存键的设计很关键。除了统一前缀,最好在键中加入业务标识和版本号,例如 user_profile_123_v2。这样当模型字段或序列化逻辑发生变化时,只需递增版本号即可批量让旧缓存失效,而不必去逐条删除。Django 的 cache.set 支持 version 参数,也可以直接写在键名里。
失效策略方面,最简单的是设置合理的超时时间,让数据自动过期。对于频繁更新的数据,可以在更新操作后主动删除相关缓存,或者采用双写模式同步更新缓存。但要注意缓存穿透问题:如果大量请求查询一个不存在的键,每次都落到数据库,压力会很大。解决办法是缓存空值并设置较短过期时间,或者使用布隆过滤器提前拦截。
缓存雪崩和缓存击穿也需要防范。雪崩指大量缓存同一时间过期,导致请求集中打到数据库。可以在设置过期时间时加上一个随机值,比如 base_timeout + random.randint(0, 60)。击穿指某个热点键突然过期,可以在缓存缺失时加互斥锁,只允许一个请求去加载数据并回填缓存。
下面是一个带互斥锁的缓存获取函数示例:
import time
from django.core.cache import cache
def get_data_with_lock(key):
value = cache.get(key)
if value is not None:
return value
lock_key = key + '_lock'
if cache.add(lock_key, 'locked', timeout=10):
try:
value = expensive_query()
cache.set(key, value, timeout=300)
finally:
cache.delete(lock_key)
else:
time.sleep(0.1)
value = cache.get(key)
return value
这个示例利用 cache.add 的原子性来实现简单的分布式锁,避免多个请求同时回源数据库。实际项目中可以结合 Redis 的 SETNX 命令实现更健壮的锁。
调优 Redis 缓存后端的常见经验
缓存上线后,监控命中率是最直接的优化依据。可以通过 Redis 的 INFO 命令查看 keyspace_hits 和 keyspace_misses,计算命中率。如果命中率低于 80%,要么是缓存时间太短,要么是键设计不合理,需要结合业务调整。
序列化方式对性能也有影响。JSON 序列化可读性好但速度稍慢,pickle 序列化快但跨语言兼容性差。如果缓存数据主要给 Python 使用,pickle 是更高效的选择;如果需要其他语言读取,则用 JSON 或 MessagePack。django-redis 还支持自定义序列化器,可以针对大对象做压缩。
连接池配置同样重要。max_connections 不宜设置得过大,否则会占用 Redis 大量文件描述符;也不宜过小,否则并发请求会等待连接。一般根据应用并发量设置为 50 到 200 之间,并用压测验证。此外,设置 socket_timeout 和 socket_connect_timeout 防止某个操作长时间阻塞。
最后提醒一点:Redis 的内存是有限的。如果缓存项无限制增长,可能触发 maxmemory 淘汰策略,导致重要数据被清除。在 Django 中可以为不同业务配置不同的 TIMEOUT,并定期扫描大键。对于特别大的对象(如图片、长文本),不要直接放入缓存,只缓存文件路径或生成结果。

