在 Django 开发中,你可能早已习惯这样使用缓存:
from django.core.cache import cache
cache.set("user:1001", user_data, timeout=300)
data = cache.get("user:1001")
但你是否好奇:
cache到底是什么?它如何在不修改代码的情况下,无缝切换 Redis、Memcached 或本地内存缓存?
答案就藏在 Django 的一个精巧设计中 —— 属性代理模式(Attribute Proxy Pattern) 。本文将带你揭开这层神秘面纱,并深入理解其在缓存系统中的核心作用。
一、什么是属性代理模式?
属性代理模式 是一种设计模式,通过一个代理对象将对属性或方法的访问转发(delegate) 给另一个真实对象,而调用者对此完全无感知。
核心特点:
- 透明性:客户端像使用普通对象一样使用代理;
- 解耦性:真实对象可动态替换,不影响调用方;
- 延迟初始化:真实对象可在首次访问时才创建。
在 Python 中,可通过重写 __getattr__、__setattr__ 等魔术方法实现。
二、Django 缓存系统的架构设计
Django 支持多种缓存后端:
LocMemCache(本地内存)RedisCacheMemcachedCacheDatabaseCache- 自定义后端
但无论使用哪种,开发者都只与 同一个 cache 对象交互。这是如何做到的?
关键角色:
| 组件 | 作用 |
|---|---|
django.core.cache.cache | 全局代理对象(Proxy) |
django.core.cache.caches | 缓存后端注册表(Registry) |
BaseCache 及其实现类 | 真实缓存后端(Real Subject) |
三、源码剖析:cache 是如何工作的?
步骤1:cache 的定义(django/core/cache/__init__.py)
# django/core/cache/__init__.py
from django.core.cache.backends.base import DEFAULT_CACHE_ALIAS
from django.core.cache.utils import CacheHandler
# 创建一个 CacheHandler 实例
caches = CacheHandler()
# 默认缓存别名(通常为 'default')
cache = caches[DEFAULT_CACHE_ALIAS] # ← 关键!
这里 cache 并不是一个真实的缓存对象,而是 caches['default'] 的结果。
步骤2:CacheHandler —— 属性代理的核心
# django/core/cache/utils.py
class CacheHandler:
def __init__(self):
self._caches = {}
def __getitem__(self, alias):
# 如果该 alias 的缓存未创建,则创建并缓存
if alias not in self._caches:
self._caches[alias] = self.create_cache(alias)
return self._caches[alias]
def create_cache(self, alias):
# 1. 从 settings.CACHES 获取配置
# 2. 动态导入对应的缓存 backend 类
# 3. 实例化并返回
...
✅ 重点:
CacheHandler本身不实现任何缓存逻辑,它只是一个按需创建并缓存真实后端的代理工厂。
步骤3:DefaultCacheProxy —— 真正的“透明代理”
但在旧版本(<4.0)中,Django 使用了一个更典型的代理类:
# 简化版 DefaultCacheProxy
class DefaultCacheProxy:
def __getattr__(self, name):
# 首次访问时,从 caches['default'] 获取真实对象
return getattr(caches[DEFAULT_CACHE_ALIAS], name)
def __setattr__(self, name, value):
setattr(caches[DEFAULT_CACHE_ALIAS], name, value)
def __delattr__(self, name):
delattr(caches[DEFAULT_CACHE_ALIAS], name)
# 全局 cache 对象
cache = DefaultCacheProxy()
工作流程:
- 调用
cache.set(...); - 触发
__getattr__('set'); - 代理自动获取
caches['default'](即真实后端); - 转发调用
real_cache.set(...)。
🔍 这就是属性代理模式的精髓:调用者以为在操作
cache,实际在操作RedisCache或LocMemCache!
四、配置驱动:如何切换缓存后端?
一切的秘密都在 settings.py 中:
# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
},
'session': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}
# 代码无需任何修改!
from django.core.cache import cache
cache.set("key", "value") # 自动使用 Redis
# 如需使用其他后端
from django.core.cache import caches
session_cache = caches['session']
session_cache.set("sess_id", data) # 使用内存缓存
动态切换示例(测试场景):
# tests.py
from django.test import override_settings
@override_settings(CACHES={
'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}
})
def test_without_cache():
# 此时 cache 是 DummyCache(不实际存储)
assert cache.get("any_key") is None
✅ 零代码侵入,仅靠配置即可切换底层实现!
五、为什么使用代理模式?优势何在?
| 优势 | 说明 |
|---|---|
| 解耦 | 业务代码不依赖具体缓存实现 |
| 透明性 | 开发者无需关心 cache 内部是 Redis 还是 Memcached |
| 懒加载 | 真实缓存连接在首次使用时才建立,节省资源 |
| 多后端支持 | 通过 caches[alias] 轻松管理多个缓存实例 |
| 测试友好 | 可轻松 mock 或替换为 dummy 后端 |
六、扩展:自定义缓存后端
Django 允许你实现自己的缓存后端,只需继承 BaseCache:
# myapp/cache_backends.py
from django.core.cache.backends.base import BaseCache
class MyCustomCache(BaseCache):
def get(self, key, default=None, version=None):
# 自定义获取逻辑(如调用外部 API)
...
def set(self, key, value, timeout=300, version=None):
# 自定义存储逻辑
...
配置使用:
CACHES = {
'default': {
'BACKEND': 'myapp.cache_backends.MyCustomCache',
}
}
代理模式确保你的自定义后端能无缝接入现有代码!
七、常见误区澄清
❌ 误区1:“cache 是一个单例缓存实例”
- 纠正:
cache是一个代理对象,背后的真实实例由CacheHandler管理。
❌ 误区2:“修改 cache 属性会影响所有地方”
- 纠正:由于代理转发,
cache.timeout = 60实际修改的是真实后端的属性,但不推荐直接修改,应通过配置控制。
❌ 误区3:“代理会带来性能损耗”
- 纠正:Python 属性查找开销极小,且真实对象会被缓存(
_caches字典),仅首次访问有微小 overhead。
八、总结
Django 通过 属性代理模式 + 注册表模式,构建了一个高度灵活、可插拔的缓存系统:
cache是一个智能代理,隐藏了底层复杂性;CACHES配置 决定了真实后端类型;- 开发者只需关注业务逻辑,无需关心缓存实现细节。
💡 设计启示:
当你需要“同一接口,多种实现,运行时切换”时,属性代理模式是一个优雅的解决方案。
这种模式不仅用于缓存,还广泛应用于 Django 的:
- 数据库路由(
db_router) - 日志处理器
- 邮件后端
- 文件存储(
default_storage)
掌握它,你就掌握了 Django “约定优于配置” 和 “可插拔架构” 的核心思想。
延伸阅读:
- Django 官方文档:Cache Framework
- 《Two Scoops of Django》第 18 章:Caching
- 源码阅读:
django/core/cache/