Niffler

X2.19 16340286-Django缓存使用初探

Posted By 16340286

对于Web服务来说,对客户端发来的每个请求都需要计算,如果在短时间内有多个相同的请求,就要进行多次不必要的重复计算,使用缓存能有效地解决这个问题,如果请求的值存在缓存中,就直接返回缓存的值而不需要计算,能够减少计算和时延。

缓存位置

缓存数据可以存放在数据库,文件系统或内存中,使用缓存服务器还能实现多个服务器共享缓存,参考官方文档在settings.py中配置缓存。

Django默认配置了本地缓存,该缓存是多进程的,且线程安全。

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}

如果只有一本地内存缓存,可以省略LOCTION配置,如果有多个,则至少要给其中一个配置LOCATION以区分。

需要注意的是,Django是多进程部署时,每个进程都有自己私有的缓存实例,意味着跨缓存是不可能发生的,这样降低了命中率,而且如果其中一个进程修改了数据,其他进程的缓存没有更新之前就会返回错误的数据。

缓存粒度

Django提供了不同级别的缓存粒度,可以缓存整个站点、特定视图或部分难生成的内容,参考官方文档

视图缓存

django.views.decorators.cache定义了一个cache_page装饰器,用于自动缓存视图的响应。

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
    ...

cache_page的参数指定了缓存过期时间,以秒为单位。

如果多个URL指向相同的视图,每个URL将被单独缓存,如果URLconf是这样的:

urlpatterns = [
    path('foo/<int:code>/', my_view),
]

那么/foo/1//foo/23/的请求将被分别缓存。

但这种给my_view函数加装饰器的方法改变了函数,使my_view函数难以复用,可以改成在URLconf中再加cache_page装饰器。

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
]

任意级别粒度

Django提供了底层的缓存API,用于缓存任意级别粒度的对象,可以缓存任何可以安全的 pickle的Python对象:字符串、字典、列表,或者其他。

使用django.core.cache.cache引用缓存,基本用法:

from django.core.cache import cache

# 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!'

# 30秒后'my_key'过期
cache.get('my_key')
# None

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

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

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

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

# cache.add(key, value, timeout=DEFAULT_TIMEOUT, version=None)
cache.set('add_key', 'Initial value')
cache.add('add_key', 'New value')
cache.get('add_key')
# 'Initial value'

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

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

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)

get_many()接口,对多个值返回一个字典。

# cache.get_many(keys, version=None)
cache.set('a', 1)
cache.set('b', 2)
cache.set('c', 3)
cache.get_many(['a', 'b', 'c'])
# {'a': 1, 'b': 2, 'c': 3}

set_many()传递键值对的字典,可以设置多个值。

# cache.set_many(dict, timeout)¶
cache.set_many({'a': 1, 'b': 2, 'c': 3})
cache.get_many(['a', 'b', 'c'])
# {'a': 1, 'b': 2, 'c': 3}

delete()清除特定对象缓存。

# cache.delete(key, version=None)
cache.delete('a')

delete_many()一次性清除多个键。

# cache.delete_many(keys, version=None)
cache.delete_many(['a', 'b', 'c'])

使用cache.clear()可以删除缓存里的所有键。

cache.touch()为键设置一个新的过期时间。

# cache.touch(key, timeout=DEFAULT_TIMEOUT, version=None)
cache.touch('a', 10)
# True

分别使用incr()decr()可以递增或递减一个已经存在的键的值。是否为原子方法依赖于后端支持。

# cache.incr(key, delta=1, version=None)
# cache.decr(key, delta=1, version=None)
cache.set('num', 1)
cache.incr('num')
# 2
cache.incr('num', 10)
# 12
cache.decr('num')
# 11
cache.decr('num', 5)
# 6

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