16.Django Redis Caches

Django Redis Caches

Django 使用 redis缓存

  • pip install django_redis
  • pip install hiredis 非必要
# 前端\django\myweb\myweb\settings.py  文件增加CACHES配置  缓存存储session会话
# redis缓存 pip install django_redis ;pip install hiredis
CACHES = {
    "default": {
        "BACKEND": 'django_redis.cache.RedisCache',      # django_redis的cache模块
        # "LOCATION": "redis://10.96.149.205:6379/10",
        "LOCATION": [
            "redis://:xxxxxxxx@redis-master.operation.svc.cluster.local:6379/10",  # 主
            "redis://:xxxxxxxx@redis-replicas.operation.svc.cluster.local:6379/10", # 从
        ],   # 远程redis的ip:端口
        "TIMEOUT": 300,
        "MAX_ENTRIES": 1000,
        "OPTIONS": {
            # "db": "10",
            "parser_class": "redis.connection.HiredisParser",  # hiredis是C客户端,性能更高
            "CLIENT_CLASS": "django_redis.client.DefaultClient",  # 默认redis-py 客户端
            "PASSWORD": "xxxxxxxx",       # 密码
            "PICKLE_VERSION": -1,   # 指定pickle的序列化版本
            "SOCKET_TIMEOUT": 3,       # 单位秒,执行redis命令的超时时间
            "SOCKET_CONNECT_TIMEOUT": 3,   # 单位秒,连接redis的超时时间
            "CONNECTION_POOL_KWARGS": {"max_connections": 1000},  # 最大连接数
        },
        'CONNECTION_POOL_CLASS': 'redis.connection.BlockingConnectionPool', # 自定义连接池
    },
    "test": {
        "BACKEND": 'django.core.cache.backends.redis.RedisCache',      # django的cache模块
        # "LOCATION": "redis://:xxxxxxxx@10.96.149.205:6379",
        "LOCATION": [
            "redis://:xxxxxxxx@redis-master.operation.svc.cluster.local:6379",  # 主
            "redis://:xxxxxxxx@redis-replicas.operation.svc.cluster.local:6379",  # 从
        ],   # 远程redis的ip:端口
        "TIMEOUT": 300,
        "MAX_ENTRIES": 1000,
        "OPTIONS": {
            "db": "10",
            # "parser_class": "redis.connection.PythonParser",
            # "parser_class": "redis.connection.HiredisParser",
            "pool_class": "redis.BlockingConnectionPool",   # 默认连接池
        },
    }
}

# 缓存存储session会话
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'

# 使用的缓存别名(默认内存缓存)
SESSION_CACHE_ALIAS = 'default'

测试redis是否连通正常

# python manage.py shell
# cache是django提供的代理对象,根据配置的BACKEND进行操作
from django.core.cache import cache

cache.set("test2","11",3600)
# True

cache.get("test2")
# '11'

cache.set('foo', 'value', timeout=25)
cache.set('foo', 'value', timeout=None)
cache.ttl('foo')
cache.persist('foo')    # 永不过期
cache.expire("foo", timeout=5)
cache.keys("foo_*")
cache.iter_keys("foo_*")
cache.delete_pattern("foo_*")
cache.set("key", "value1", nx=True) # 实现 SETNX原子操作
# key必须存在,否则报错
cache.incr("key")
cache.incr("key")
# 获得所使用的客户端对象
from django_redis import get_redis_connection
con = get_redis_connection("default")

使用缓存

模板缓存

具体的使用方式如下:首先加载cache过滤器,然后使用模板标签语法把需要缓存的片段包围起来即可

{% load cache %}
{% cache 3600 'cache_name' %}
    <div>container</div>
{% endcache %}

视图缓存

  • 通过装饰器
    • 给视图添加缓存是有风险的,如果视图所展示的网页中经常动态变动的信息,那么被添加缓存不可取缓存整个视图最实用的场景应该是这个视图所展示的网页的内容基本上不怎么变动,或者说很长一段时间内不需要变动,这样使用缓存就非常有效
  • 通过url
    • URLconf 使用缓存和视图函数使用缓存需要注意的地方是一样的,因为它们都是缓存整个页面,所以需要考虑是否整个页面都应该缓存
### 通过装饰器
from django.views.decorators.cache import cache_page

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

### 通过URL
from django.urls import path
from . import views
from django.views.decorators.cache import cache_page
from blog import test

# 设置命名空间名称
app_name = 'index_app'
urlpatterns = [
    path('index_app/', cache_page(60 * 2)(test.index_app), name='index_app_test'),
]

中间件缓存

使用中间件,经过一系列的谁等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,

判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存

  • FetchFromCacheMiddleware :从缓存中读取数据
    • 缓存状态为200的GET和HEAD请求的响应(除非响应头中设置不进行缓存)
    • 对具有不同查询参数的相同URL的请求的响应被认为是各自不同的页面,并且被分别单独缓存。
    • 该中间件会使用与对应的GET请求相同的响应头来回答HEAD请求,即可以为HEAD请求返回缓存的GET响应。
  • UpdateCacheMiddleware :将数据更新到缓存中
    • 该中间件会自动在每个响应中设置几个headers:
    • 设置Expires为当前日期/时间 加上 定义的CACHE_MIDDLEWARE_SECONDS值,GMT时间
    • 设置响应的Cache-Control的max-age,值是定义的CACHE_MIDDLEWARE_SECONDS值。
    • 如果视图设置了自己的缓存时间(即设置了Cache-Control的max age),那么页面将被缓存直到到期时间,而不是CACHE_MIDDLEWARE_SECONDS。
    • 如果USE_I18N设置为True,则生成的缓存key将包含当前语言的名称,这样可以轻松缓存多语言网站,而无需自己创建缓存密钥。
    • 如果USE_L10N设置为True并且 USE_TZ被设置为True,缓存key也会包括当前语言

配置解释如下:

  • CACHE_MIDDLEWARE_ALIAS:用于存储的缓存别名
  • CACHE_MIDDLEWARE_SECONDS:每个页面应缓存的秒数
  • CACHE_MIDDLEWARE_KEY_PREFIX:用于生成缓存key的前缀,如果使用相同的Django安装在多个站点之间共享缓存,请将其设置为站点名称或此Django实例特有的其他字符串,以防止发生密钥冲突。如果你不在乎,请使用空字符串。
CACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 10     # 全站缓存10秒测试
CACHE_MIDDLEWARE_KEY_PREFIX = "cache_redis_demo_first"

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',    # 放在最上面,重写了response
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'blog.middleware.auth.AuthMiddleware',
    # 'blog.middleware.auth.M1',
    # 'blog.middleware.auth.M2',
    'django.middleware.cache.FetchFromCacheMiddleware'  # 放在最下面,重写了request
]

低级缓存

有时我们不想缓存整个页面数据,而只是想缓存某些费时查询并且基本不会改变的数据,可以通过一个简单的低级缓存API实现,该API可以缓存任何可以安全pickle的Python对象:字符串,字典,模型对象列表等

from django.core.cache import caches
cache1 = caches['myalias']
cache2 = caches['myalias']
# 判断为True
if cache1 is cache2: 
    ...

说明:

  • 可以通过CACHES类似字典一样的方式访问settings中配置的缓存,在同一个线程中重复请求相同的别名将返回相同的对象
  • 如果指定的myalias不存在,将引发InvalidCacheBackendError
  • 为了线程安全性,为会每个线程返回缓存的不同实例
  • 作为快捷方式, 默认缓存(default)可以使用 django.core.cache.cache:
# 使用 default 缓存
from django.core.cache import cache

# 上面的cache等同于下面的写法
from django.core.cache import caches
cache = caches['default']
# 测试redis连接是否正常
# python manage.py shell ; from django.core.cache import cache ; cache.set("foo", "value", timeout=25)
# cache是django提供的代理对象,根据配置的BACKEND进行操作
import os
import sys

if __name__ == '__main__':
    sys.path.append(r"D:\git-python\前端\django\myweb")
    os.environ["DJANGO_SETTINGS_MODULE"] = "myweb.settings"
    import django
    django.setup()

    from django.core.cache import cache

    # 使用 redis 的一般用法
    cache.set('manul_set', 'ok')
    manul_set = cache.get('manul_set')

    # 可以手动设置 timeout,如果不指定timeout,默认是 300秒,如果指定为None,则代表没有过期时间
    cache.set("key", "value", timeout=None)

    # 可以获取key的超时设置(ttl:time to live)
    # 返回值的3种情况:
    # 0: key 不存在 (或已过期)
    # None: key 存在但没有设置过期
    # ttl: 任何有超时设置的 key 的超时值
    cache.set("foo", "value", timeout=25)
    cache.ttl("foo") # 得到 25 
    cache.ttl("not-existent") # 得到 0

    # 让一个值永久存在
    cache.persist("foo")
    cache.ttl("foo") # 得到 None

    # 指定一个新的过期时间
    cache.set("foo", "bar", timeout=22)
    cache.ttl("foo") # 得到 22
    cache.expire("foo", timeout=5)
    cache.ttl("foo") # 得到 5

    # 支持 redis 分布式锁, 使用 上下文管理器 分配锁
    with cache.lock("somekey"):
        pass     # do_some_thing()

    # 使用全局通配符的方式来检索或者删除键
    cache.keys("foo_*")  # 返回所有匹配的值, 如 ["foo_1", "foo_2"]

    # 删除 键
    cache.delete_pattern("foo_*")  # 支持通配符

    print(cache.keys("*"))   # 查看所有缓存
['views.decorators.cache.cache_page.cache_redis_demo_first.GET.7cc7ce8d011447c1f3412a54fca4653f.9830506c591f9852e97526154ba2aed1.zh-hans', 'views.decorators.cache.cache_header.cache_redis_demo_first.35437eae000241f3689ff61d7c790c07.zh-hans', 'views.decorators.cache.cache_page.cache_redis_demo_first.GET.5b0e7ad25b938229d019479f498ca797.9830506c591f9852e97526154ba2aed1.zh-hans', 'views.decorators.cache.cache_header.cache_redis_demo_first.a7503bde4e59a36ad639dd72df3a94cd.zh-hans', 'django.contrib.sessions.cacheiq94k42wj9pg5zmkx249tdtf8i85ocb6', 'views.decorators.cache.cache_header.cache_redis_demo_first.5b0e7ad25b938229d019479f498ca797.zh-hans', 'views.decorators.cache.cache_page.cache_redis_demo_first.GET.f00e8060194304a5816606102a08aca3.9830506c591f9852e97526154ba2aed1.zh-hans', 'views.decorators.cache.cache_page.cache_redis_demo_first.GET.77cffce2fcac3d445bc7b7e77af3b503.9830506c591f9852e97526154ba2aed1.zh-hans', 'views.decorators.cache.cache_header.cache_redis_demo_first.f00e8060194304a5816606102a08aca3.zh-hans', 'views.decorators.cache.cache_page.cache_redis_demo_first.GET.b416477bf79307f9d7fb4763912fa94e.9830506c591f9852e97526154ba2aed1.zh-hans', "template.cache.'layout_navbar'.d41d8cd98f00b204e9800998ecf8427e", 'views.decorators.cache.cache_header.cache_redis_demo_first.869feaf57b3b8357b2623301762a8538.zh-hans', 'views.decorators.cache.cache_header.cache_redis_demo_first.77cffce2fcac3d445bc7b7e77af3b503.zh-hans', 'views.decorators.cache.cache_header.cache_redis_demo_first.7cc7ce8d011447c1f3412a54fca4653f.zh-hans', 'key', 'foo', 'views.decorators.cache.cache_page.cache_redis_demo_first.GET.869feaf57b3b8357b2623301762a8538.9830506c591f9852e97526154ba2aed1.zh-hans', 'views.decorators.cache.cache_header.cache_redis_demo_first.fd7d86799fb1ff9dc70ec37565ced2df.zh-hans', 'views.decorators.cache.cache_header.cache_redis_demo_first.2033af321df06c8f24a64ab57b267d89.zh-hans', 'views.decorators.cache.cache_header.cache_redis_demo_first.19bc4b855af39eae1eaab3fbe71ddfc4.zh-hans', 'views.decorators.cache.cache_page.cache_redis_demo_first.GET.35437eae000241f3689ff61d7c790c07.9830506c591f9852e97526154ba2aed1.zh-hans', 'manul_set', 'views.decorators.cache.cache_page.cache_redis_demo_first.GET.fd7d86799fb1ff9dc70ec37565ced2df.9830506c591f9852e97526154ba2aed1.zh-hans', 'views.decorators.cache.cache_page.cache_redis_demo_first.GET.a7503bde4e59a36ad639dd72df3a94cd.9830506c591f9852e97526154ba2aed1.zh-hans', 'views.decorators.cache.cache_page.cache_redis_demo_first.GET.19bc4b855af39eae1eaab3fbe71ddfc4.9830506c591f9852e97526154ba2aed1.zh-hans', 'views.decorators.cache.cache_header.cache_redis_demo_first.b416477bf79307f9d7fb4763912fa94e.zh-hans', 'views.decorators.cache.cache_page.cache_redis_demo_first.GET.2033af321df06c8f24a64ab57b267d89.9830506c591f9852e97526154ba2aed1.zh-hans']

session缓存

# 配置session的引擎为cache
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
# 此处别名依赖缓存的设置
SESSION_CACHE_ALIAS = 'default' 

函数缓存实战案例

# 前端\django\myweb\blog\utils\redis_cache.py

import os
import sys
sys.path.append(r"D:\git-python\前端\django\myweb")
os.environ["DJANGO_SETTINGS_MODULE"] = "myweb.settings"

from django.core.cache import cache

def get_cache_or_exc_func(key, func, *args, **kwargs):
    """
    根据传入的key和func,先获取缓存内容,没有则使用func计算并保存结果
    :param key: 缓存的key
    :param func: 计算函数
    :param args: 可变参数
    :param kwargs: 可变字典
    :return: 缓存的n内容或func计算的结果
    """
    # 加上cache锁
    with cache.lock(key+'lock'):
        # 获取缓存中的变量
        result = cache.get(key)
        if result and len(cache.get(key)) > 0:
            # 存在,则直接返回缓存结果
            return result
        else:
            # 不存在,则计算数据,得到结果
            result = func(*args, **kwargs)
            # 将结果保存到缓存中
            cache.set(key, result, timeout=10)
            # 返回结果
            return result
# 前端\django\myweb\blog\views\redis_cache_test.py

from blog.utils.redis_cache import get_cache_or_exc_func
from django.http import JsonResponse
import time

def get_result():
    """做一些费时但不经常变更的操作,这里模拟等待3秒"""
    time.sleep(3)
    return 'ok'

def lower_level_cache(request):
    result = get_cache_or_exc_func('redis_cache_test_key', get_result)
    return JsonResponse({"result": result})

# 前端\django\myweb\myweb\urls.py
from blog.views import redis_cache_test
urlpatterns = [
    # redis_cache 测试
    path('redis_cache/', redis_cache_test.lower_level_cache, name="lower_level_cache"),
]

编辑/新增/删除 缓存清理通用

import os
import sys
sys.path.append(r"D:\git-python\前端\django\myweb")
os.environ["DJANGO_SETTINGS_MODULE"] = "myweb.settings"

from django.core.cache import cache

def clear_cache():

    # print(cache.keys("views.decorators.cache.cache_header.cache_redis_demo_first*"))
    cache_keys = cache.keys("views.decorators.cache.cache_*.cache_redis_demo_first*")
    # print(cache_keys)
    if cache_keys and len(cache_keys) > 0:
        for key in cache_keys:
            cache.delete(key)

if __name__ == '__main__':
    clear_cache()