Django4 中文入门教程 Django4.0 缓存框架-底层缓存API

2024-02-25 开发教程 Django4 中文入门教程 匿名 3

有时,缓存整个渲染页面并不会带来太多好处,事实上,这样会很不方便。

或许,你的站点包含了一个视图,它的结果依赖于许多费时的查询,而且结果会随着时间变化而改变。在这个情况下,使用站点或视图缓存策略提供的全页面缓存并不理想,因为不能缓存所有结果(一些数据经常变动),不过你仍然可以缓存几乎没有变化的结果。

像这样的情况,Django 公开了一个底层的缓存 API 。你可以使用这个 API 以任意级别粒度在缓存中存储对象。你可以缓存任何可以安全的 pickle 的 Python 对象:模型对象的字符串、字典、列表,或者其他。

访问缓存

django.core.cache.caches

你可以通过类似字典一样的 ​object: django.core.cache.caches​ 对象访问在 ​CACHES ​配置的缓存。重复请求同一个线程里的同一个别名将返回同一个对象。

>>> from django.core.cache import caches
>>> cache1 = caches['myalias']
>>> cache2 = caches['myalias']
>>> cache1 is cache2
True

如果键名不存在,将会引发 ​InvalidCacheBackendError ​错误。
为了支持线程安全,将为每个线程返回缓存后端的不同实例。

django.core.cache.cache

作为快捷方式,默认缓存可以通过 ​django.core.cache.cache​ 引用:

>>> from django.core.cache import cache

这个对象等价于 ​caches['default']

基本用法

基本接口是:

cache.set(key, value, timeout=DEFAULT_TIMEOUT, version=None)

>>> cache.set('my_key', 'hello, world!', 30)

cache.get(key, default=None, version=None)

>>> cache.get('my_key')
'hello, world!'

key ​是一个字符串,​value ​可以任何 picklable 形式的 Python 对象。
timeout ​参数是可选的,默认为 ​CACHES ​中相应后端的 ​timeout ​参数。它是值存在缓存里的秒数。​timeout ​设置为 ​None ​时将永久缓存。​timeout ​为0将不缓存值。
如果对象不在缓存中,​cache.get()​ 将返回 ​None​。

>>> # Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key')
None

如果你需要确定对象是否存在于缓存中,并且你已经存储了一个字面值 ​None​,使用一个前哨对象作为默认:

>>> sentinel = object()
>>> cache.get('my_key', sentinel) is sentinel
False
>>> # Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key', sentinel) is sentinel
True

cache.get() ​可以带一个默认参数。如果对象不在缓存中,将返回指定的值。

>>> cache.get('my_key', 'has expired')
'has expired'

cache.add(key, value, timeout=DEFAULT_TIMEOUT, version=None)

在键不存在的时候,使用 ​add()​ 方法可以添加键。它与 ​set()​ 带有相同的参数,但如果指定的键已经存在,将不会尝试更新缓存。

>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'

如果你想知道通过 ​add()​ 存储的值是否在缓存中,你可以检查返回值。如果值已保存,将返回 ​True ​,否则返回 ​False ​。

cache.get_or_set(key, default, timeout=DEFAULT_TIMEOUT, version=None)

如果你想得到键值或者如果键不在缓存中时设置一个值,可以使用 ​get_or_set()​ 方法。它带有和 ​get()​ 一样的参数,但默认是为那个键设置一个新缓存值,而不是返回:

>>> cache.get('my_new_key')  # returns None
>>> cache.get_or_set('my_new_key', 'my new value', 100)
'my new value'

你也可以传递任何可调用的值作为默认值:

>>> import datetime
>>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)
datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)

cache.get_many(keys, version=None)

这里也有 ​get_many()​ 接口,返回一个字典,其中包含你请求的键,这些键真实存在缓存中(并且没过期):

>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

cache.set_many(dict, timeout)

使用 ​set_many()​ 传递键值对的字典,可以更有效的设置多个值。

>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

类似 ​cache.set()​,​set_many()​ 带有一个可选的 ​timeout ​参数。
在已支持的后端(memcached),​set_many()​ 会返回无法插入的键列表。

cache.delete(key, version=None)

你可以使用 ​delete()​ 显式地删除键,以清空特定对象的缓存:

>>> cache.delete('a')
True

如果键被成功删除,将返回​delete()​ ,否则返回 ​False ​。

cache.delete_many(keys, version=None)

如果你想一次性清除很多键,给 ​delete_many()​ 传递一个键列表即可删除。

>>> cache.delete_many(['a', 'b', 'c'])

cache.clear()

最后,如果你想删除缓存里的所有键,使用 ​cache.clear()​。注意,​clear() ​将删除缓存里的 任何 键,不只是你应用里设置的那些键。

>>> cache.clear()

cache.touch(key, timeout=DEFAULT_TIMEOUT, version=None)

cache.touch()​ 为键设置一个新的过期时间。比如,更新一个键为从现在起10秒钟后过期:

>>> cache.touch('a', 10)
True

和其他方法一样,​timeout ​参数是可选的,并且默认是 ​CACHES ​设置的相应后端的 ​TIMEOUT ​选项。
如果键被成功 ​touch()​,将返回 ​True​,否则返回 ​False​。

cache.incr(key, delta=1, version=None)

cache.decr(key, delta=1, version=None)

你也可以使用分别使用 ​incr()​ 或 ​decr()​ 方法来递增或递减一个已经存在的键的值。默认情况下,存在的缓存值将递增或递减1。通过为递增/递减的调用提供参数来指定其他递增/递减值。如果你试图递增或递减一个不存在的缓存键,将会引发 ​ValueError ​错误。

>>> cache.set('num', 1)
>>> cache.incr('num')
2
>>> cache.incr('num', 10)
12
>>> cache.decr('num')
11
>>> cache.decr('num', 5)
6

cache.close()

如果缓存后端已经实现了 ​close()​ 方法,你可以关闭和缓存的连接。

>>> cache.close()

缓存键前缀

如果你正在服务器之间或者生产/开发缓存之间共享缓存实例,有可能会使得一个服务器使用另一个服务器的缓存数据。如果缓存数据格式是相同的,这会导致一些难以诊断的问题。
为了防止这个问题,Django 为单台服务器提供了为所有缓存键提供前缀的方法。当一个特殊的缓存键被保存或检索时,Django 会为缓存键自动添加 ​KEY_PREFIX ​缓存设置的前缀值。
要确保每个 Django 实例有不同的 ​KEY_PREFIX ​,这样就保证缓存值不会发生冲突。

缓存版本控制

当更改使用缓存值的运行代码时,你可能需要清除任何已存的缓存值。最简单的方法是刷新整个缓存,但这会导致那些仍然有用且有效的缓存值。
Django 提供更好的方式来指向单个缓存值。Django 缓存框架有一个系统范围的版本标识,需要在 ​VERSION ​缓存配置中指定。这个配置的值将自动与缓存前缀和用户提供的缓存键组合起来获取最终的缓存键。
默认情况下,任何键请求将自动包含站点默认缓存键版本。但是,早期的缓存函数都包含一个 ​version ​参数,因此你可以指定 ​set ​还是 ​get ​特定缓存键的版本。举例:

>>> # Set version 2 of a cache key
>>> cache.set('my_key', 'hello world!', version=2)
>>> # Get the default version (assuming version=1)
>>> cache.get('my_key')
None
>>> # Get version 2 of the same key
>>> cache.get('my_key', version=2)
'hello world!'

一个指定键的版本可以使用 ​incr_version()​ 和 ​decr_version() ​方法来递增或递减。这使得特定键会自动获取新版本,而不影响其他键。继续我们前面的例子:

>>> # Increment the version of 'my_key'
>>> cache.incr_version('my_key')
>>> # The default version still isn't available
>>> cache.get('my_key')
None
# Version 2 isn't available, either
>>> cache.get('my_key', version=2)
None
>>> # But version 3 *is* available
>>> cache.get('my_key', version=3)
'hello world!'

缓存键转换

如前面两节所述,用户提供的缓存键不是单独使用的,它是与缓存前缀和键版本组合后获取最终缓存键。默认情况下,使用冒号连接这三部分生成最终的字符串:

def make_key(key, key_prefix, version):
return '%s:%s:%s' % (key_prefix, version, key)

如果你想用不同方式组合,或者应用其他处理来获得最终键(比如,获得关键部分的哈希摘要),那么你可以提供一个自定义的键函数。
KEY_FUNCTION ​缓存设置指定一个与上面的 ​make_key()​ 原型匹配的函数路径。如果提供,这个自定义键函数将代替默认的键组合函数来使用。

缓存键警告

Memcached 作为最常用的缓存后端,不允许缓存键超过250个字符、包含空格或控制字符,并且使用这些键将会导致异常。为了增加代码可移植性和最小惊讶,如果使用会导致 memcached 报错的键,那么其他内置的缓存框架会发出警告​django.core.cache.backends.base.CacheKeyWarning
如果你正在使用的生产后端能接受更大范围的键(自定义后端或非 memcached 的内置后端),并且在没有警告的情况下使用更广的范围,你可以在 ​INSTALLED_APPS ​中的 management 模块里静默 ​CacheKeyWarning ​使用这个代码:

import warnings
from django.core.cache import CacheKeyWarning
warnings.simplefilter("ignore", CacheKeyWarning)

如果你想为某个内置的后端提供自定义的键检验逻辑,你可以将其子类化,只覆盖 ​validate_key ​方法,并且按照 使用自定义缓存后端 的说明操作。比如,想要为 locmem 后端执行此操作,请将下面代码放入模块中:

from django.core.cache.backends.locmem import LocMemCache
class CustomLocMemCache(LocMemCache):
def validate_key(self, key):
"""Custom validation, raising exceptions or warnings as needed."""
...

然后在 ​CACHES ​里的 ​BACKEND ​部分使用路径导入此类。