Django缓存

 一 缓存

由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5分钟内再有人来访问时,则不再去执行view中的操作,而是直接从内存或者memcache中之前缓存的内容拿到,并返回。

Django缓存框架的管档,写的很好,非常值得一读:https://docs.djangoproject.com/en/3.2/topics/cache/

Django缓存框架的核心目标:https://docs.djangoproject.com/en/3.2/misc/design-philosophies/#cache-design-philosophy

缓存的好处无需多言,所以Django提供了专门的缓存框架来支持各种缓存:

  • 不同的缓存粒度,推荐程度由高到低排序:
    • 指定视图缓存,粒度适中。
    • 模板片段缓存,对模板文件的局部内容进行缓存,粒度较细。
    • 全站缓存,粒度最大。
    • 更低级别的缓存API,用来缓存Python中的对象(list、dict、模型对象...),没用过,但在某些情况下(我暂时还没想到)很有用的样子。
  • 不同的缓存方式,推荐程度由高到低排序:
    • 第三方缓存数据库(redis、Memcache),专业的才是最好的嘛。另外,随着内存数据库的发展演变,Memcache不如redis用的多了。
    • 本地文件缓存,将渲染好的字符串缓存到文件中,下次查询省了渲染的步骤了,内存不大的小站点非常有用。
    • 本地数据库缓存,套路同本地文件缓存。
    • 本地内存缓存,内存大了啥都好说。
    • 虚拟缓存,用于开发,Django 带有一个实际上并不缓存的“虚拟”缓存——它只是实现了缓存接口而不做任何事情。

Django中提供了6种缓存方式:

  • 开发调试
  • 内存
  • 文件
  • 数据库
  • Memcache缓存(python-memcached模块)
  • Memcache缓存(pylibmc模块)

缓存参数

这里来列一下公共的缓存参数:

  • TIMEOUT:用于缓存的默认超时,以秒为单位。此参数默认为300秒(5 分钟)。您可以设置TIMEOUTNone,默认情况下,缓存键永不过期。值0会导致键立即过期(实际上是“不缓存”)。
  • OPTIONS:应该传递给缓存后端的任何选项。每个后端的有效选项列表会有所不同,第三方库支持的缓存后端会将其选项直接传递给底层缓存库。
    • MAX_ENTRIES:删除旧值之前缓存中允许的最大条目数。此参数默认为300
    • CULL_FREQUENCY:当缓存条目达到MAX_ENTRIES时,剔除缓存中的比例,这个值必须是整数,默认是3,即剔除三分之一,如果是即剔除一半。存疑的是剔除的策略,我只查到了基于本地内存的缓存剔除策略是LRU,其它的貌似都是随机剔除。
  • KEY_PREFIX: 一个字符串,将自动包含(默认添加)到 Django 服务器使用的所有缓存键中。
  • VERSION:Django 服务器生成的缓存键的默认版本号。
  • KEY_FUNCTION 一个字符串,其中包含一个函数的虚线路径,该函数定义了如何将前缀、版本和密钥组合成最终的缓存密钥。

redis

现在redis数据库用的较多,所以放到最上面!但是django的缓存框架中并没有实现.....但django-redis模块实现了,所以:

先下载django-redis模块:

pip install django-redis

然后在settings.py中进行配置:

复制代码
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        # "LOCATION": "redis://IP:PORT/可选的数据库参数",
        # "LOCATION": "redis://127.0.0.1:6379/0",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {
                "max_connections": 100
            },
            "DECODE_RESPONSES": True,  # True返回的数据自动decode
            "PASSWORD": "",  # 访问redis是否需要密码
        }
    }
}
复制代码

1、配置

a、虚拟缓存(开发调试)

项目中使用了缓存,但在开发和调试时,则希望跳过缓存,那么这个虚拟缓存就有用了,你只需要在配置文件中进行配置即可:

复制代码
# 此为开始调试用,实际内部不做任何操作
    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.dummy.DummyCache',     # 引擎
                'TIMEOUT': 300,                                               # 缓存超时时间(默认300,None表示永不过期,0表示立即过期)
                'OPTIONS':{
                    'MAX_ENTRIES': 300,                                       # 最大缓存个数(默认300)
                    'CULL_FREQUENCY': 3,                                      # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
                },
                'KEY_PREFIX': '',                                             # 缓存key的前缀(默认空)
                'VERSION': 1,                                                 # 缓存key的版本(默认1)
                'KEY_FUNCTION' 函数名                                          # 生成key的函数(默认函数会生成为:【前缀:版本:key】)
            }
        }


    # 自定义key
    def default_key_func(key, key_prefix, version):
        """
        Default function to generate keys.

        Constructs the key used by all other methods. By default it prepends
        the `key_prefix'. KEY_FUNCTION can be used to specify an alternate
        function with custom key making behavior.
        """
        return '%s:%s:%s' % (key_prefix, version, key)

    def get_key_func(key_func):
        """
        Function to decide which key function to use.

        Defaults to ``default_key_func``.
        """
        if key_func is not None:
            if callable(key_func):
                return key_func
            else:
                return import_string(key_func)
        return default_key_func
 
复制代码

b、本地内存(django默认配置)

复制代码
# 此缓存将内容保存至内存的变量中
    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
                'LOCATION': 'unique-snowflake',                               #这是一个唯一标示,写啥都行
                'TIMEOUT': 300,                                               # 缓存超时时间(默认300,None表示永不过期,0表示立即过期)
                'OPTIONS':{
                    'MAX_ENTRIES': 300,                                       # 最大缓存个数(默认300)
                    'CULL_FREQUENCY': 3,                                      # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
                },
        } } 

# 注:其他配置同开发调试版本
复制代码

c、文件

复制代码
    # 此缓存将内容保存至文件,比如已经经过模版渲染完之后的html文件内容,如果缓存到文件中,以后就不需要再去进行模版渲染了,直接拿缓存中的要快一些
    # settings配置文件中写上以下配置:

        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
                'LOCATION': '/var/tmp/django_cache', #缓存文件存放路径
            }
        }
    # 注:其他配置同开发调试版本
复制代码

目录路径应该是绝对路径——也就是说,它应该从文件系统的根目录开始。是否在设置的末尾加上斜线并不重要。

确保此设置指向的目录存在并且可读可写,或者它可以由运行 Web 服务器的系统用户创建。

d、数据库

复制代码
    # 此缓存将内容保存至数据库

    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
                'LOCATION': 'my_cache_table', # 数据库表
            }
        }

    # 注:执行创建表命令 python manage.py createcachetable,数据库中会自动生成名字为my_cache_table的表作为缓存表


注意:如果我们自己手动创建这个缓存表的时候,数据库表的三个字段是必须要有的:看下面的表,并且不需要制定上面的createcachetable指令就可以使用。
from django.db import models

# Create your models here.

class CacheTalbe(models.Model):

cache_key = models.CharField(max_length=2000)
value = models.CharField(max_length=2000,null=True)
expires = models.DateTimeField(null=True)
复制代码

与其他缓存后端不同,数据库缓存不支持在数据库级别自动剔除过期条目。相反,每次调用add()set()或时都会剔除过期的缓存条目。

完事之后,就要创建缓存表了:

python manage.py createcachetable

这将在您的数据库中创建一个表格,该表格具有 Django 的数据库缓存系统所期望的正确格式。表名取自 LOCATION

如果是多数据库的话,需要单独配置,参考管档吧:https://docs.djangoproject.com/en/3.2/topics/cache/#multiple-databases

e、Memcache缓存(python-memcached模块)

复制代码
# 此缓存使用python-memcached模块连接memcache

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': 'unix:/tmp/memcached.sock',
        }
    }   

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': [
                '172.19.26.240:11211',
                '172.19.26.242:11211',
            ]
        }
    }
复制代码

f、Memcache缓存(pylibmc模块)

复制代码
    # 此缓存使用pylibmc模块连接memcache
    
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': '/tmp/memcached.sock',
        }
    }   

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': [
                '172.19.26.240:11211',
                '172.19.26.242:11211',
            ]
        }
    }
复制代码

2、应用

a. 全站使用

复制代码
   使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存
   缓存多长时间等配置默认是按照你上面的配置部分来的
    MIDDLEWARE = [
        'django.middleware.cache.UpdateCacheMiddleware', #中间件第一个
        # 其他中间件...
        'django.middleware.cache.FetchFromCacheMiddleware', #中间件最后一个
    ]

    CACHE_MIDDLEWARE_ALIAS = ""   用于存储的缓存别名。
    CACHE_MIDDLEWARE_SECONDS = ""  应该缓存每个页面的秒数
    CACHE_MIDDLEWARE_KEY_PREFIX = "" 如果使用同一django安装跨多个站点共享缓存,请将其设置为站点名称或此django实例唯一的其他字符串,以防止密钥冲突。如果你不在乎,就用空字符串。 
复制代码

看图解

  

 

 

 django默认配置为:

复制代码
from django.conf import global_settings #在这个配置文件中我们能够看到
#以下配置是django缓存的默认配置,如果我们自己没有进行配置,那么下面的使用,默认会使用我们的默认配置,也就是本地内存
# The cache backends to use.
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    }
}
CACHE_MIDDLEWARE_KEY_PREFIX = ''
CACHE_MIDDLEWARE_SECONDS = 600
CACHE_MIDDLEWARE_ALIAS = 'default'
复制代码
复制代码
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
        'OPTIONS': {
            'MAX_ENTRIES': 300,  # 最大缓存个数(默认300)
            'CULL_FREQUENCY': 3,  # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
        },
    }
}
# 默认潮湿时间是300秒,我们可以通过CACHE_MIDDLEWARE_SECONDS来修改
CACHE_MIDDLEWARE_SECONDS = 5
# 下面两个是关于key的,保持默认就完了
CACHE_MIDDLEWARE_KEY_PREFIX = ""
CACHE_MIDDLEWARE_ALIAS = ""
复制代码

 

b. 单独视图缓存(先看这个)

复制代码
  方式一:
        from django.views.decorators.cache import cache_page
        import time
        @cache_page(5)
        def my_view(request):
tm = time.time() ...
all_book = models.Book.objects.all()
print(all_book) #也是5秒之后打印一次,因为5秒内都是从缓存中拿的,没有执行我们的视图函数,所有没有去数据库中取,注意,对实时性数据要求很高的,不要做缓存
       return render(request,'index.html',{'tm':tm }) # 当我们刷新页面的时候,你会发现tm在5秒之后才会发生变化 方式二:
from django.views.decorators.cache import cache_page urlpatterns = [ url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)), #装饰器原始用法 ]
复制代码

c、局部模板使用

复制代码
    a. 引入TemplateTag

        {% load cache %}

    b. 使用缓存

        {% cache 5000 缓存key %}
    
      {% cache 5000 'unique-snowflake' %}
      缓存内容 {% endcache %}
 
复制代码

局部使用示例:

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>哈哈哈</h1>
<h2>{{ tm }}</h2>


{% load cache %}
{% cache 5 'c1' %}
<h2>{{ tm }}</h2>
{% endcache %}


</body>
</html>
复制代码

更多参考管档吧:https://docs.djangoproject.com/en/3.2/topics/cache/#template-fragment-caching 

django-redis配置缓存详见文档: https://django-redis-chs.readthedocs.io/zh_CN/latest/

see also: https://www.cnblogs.com/wupeiqi/articles/5246483.html

更多:猛击这里

 

django-redis模块 

django项目中想要使用redis作为缓存工具,则需要下载专门的模块django-redis来完成。

django-redis&&redis软件&&redis-py模块,他们之间的关系

python中直接操作redis软件,需要使用redis-py模块,而django-redis是对redis-py模块的基础上进行封装,从而方便在django项目中使用。

我们可以使用django-redis来完成:

  • 结合django的session组件使用。
  • 作为django的缓存工具。

install

# 之前如果有低版本的可以先卸载
pip uninstall django-redis -y

# 然后下载最新版的django-redis
pip install django-redis

# 我这里以5.3.0版本为例,不同的版本之间,可能略有差异,所以要版本问题要注意
pip install django-redis==5.3.0

提前说下小结吧

Python操作Redis,可以通过redis-py模块,这个是功能最全的,把这个掌握好就行。

Django项目中为了更方便的使用Redis,搞出来了django-redis模块,但这个模块毕竟是二次封装的,所以有些功能实现不了,也不用纠结,我们完全可以在Django中使用redis-py模块就完了。

作为 session backend 使用配置

在你的django的settings.py中,可以对django-redis进行相关配置,使其更好的支持django的session组件。

# session配置
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
# 设置session保存的位置对应的缓存配置项
SESSION_CACHE_ALIAS = 'session'
# 过期时间
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7

作为 cache backend 使用配置

基本的配置

为了使用 django-redis , 你应该将你的 django cache setting 改成这样:

# cache缓存
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://:@127.0.0.1:6379/0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "",   # 密码,如果没有设置密码,这个参数可以注视掉
        }
    }
}

一个CACHES里可以配置多个redis连接对象,每一个都有自己的别名(alias),上面的"default"就是别名。

我们可以设置多个不同的连接对象,更细粒度的进行功能划分。

# cache缓存
CACHES = {
    # 默认缓存
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        # 项目上线时,需要调整这里的路径
        # "LOCATION": "redis://:密码@IP地址:端口/库编号",
        "LOCATION": "redis://:@127.0.0.1:6379/0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 10},
        }
    },
    # 提供给admin运营站点的session存储
    "session": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://:@127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 10},
        }
    },
    # 提供存储短信验证码
    "sms_code":{
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://:@127.0.0.1:6379/2",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 10},
        }
    }
}

连接池

# cache缓存
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://www.neeo.cc:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            # 连接池的配置都在CONNECTION_POOL_KWARGS对应的字典中
            "CONNECTION_POOL_KWARGS": {
                "max_connections": 100,
                "encoding": "utf-8",
                "decode_responses": True,
                "timeout": 5,
            },
            "PASSWORD": "",
        }
    }
}

Django的缓存对象cache

在Django中了redis作为缓存之后,我们可以在项目中通过调用Django提供的全局缓存对象cache,来做一些事情,不过这个对象能干的事情有限,这里仅作为了解即可。

# 这么导入Django提供的全局缓存对象cache,它默认读取的是settings文件中CACHES中的 'default' ,如果没没有配置default,这个cache默认是用不了的
from django.core.cache import cache

# 之所以说它能干的事情有限,是因为它真的不好用,我随手举两个例子,其它的姿势我没尝试,所以就看看就完了
# 设置键值对,同时也可以指定超时时间
# cache.set("k1", "v1", timeout=25)
# print(cache.get("k1"))
# print(cache.ttl("k1"))
"""
ttl返回值
    0    表示 key 不存在
    300  表示 key 存在但没有设置过期或者已经过期了
    或者返回一个具体的超时时间
"""
# 如果一个key最开始没有设置超时时间,那么,我们可以手动通过expire给它设置一个超时时间
# cache.set("k2", "v2")
# cache.expire("k2", timeout=25)
# print(111, cache.ttl("k2"))


# 看着也能用,但是你用它搞点其它的,比如操作列表和集合......就报错了
# cache.sadd('s1', 'a', 'b', 'c', 1, 2, 3, 1)  # AttributeError: 'RedisCache' object has no attribute 'sadd'. Did you mean: 'add'?
# print(cache.smembers('s1'))
# cache.lpush('l1', 1, 2, 3)   # AttributeError: 'RedisCache' object has no attribute 'lpush'
# print(cache.lrange('l1', 0, -1))

我个人的建议时,如果使用redis作为Django的缓存,那么在项目中就不用这个Django默认的缓存对象cache。

使用原生Redis对象

官档:https://django-redis-chs.readthedocs.io/zh_CN/latest/

既然,Django默认的缓存对象不好用,那么我们用啥呢?答案就是用原生的Redis连接对象。

基本使用

import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day06.settings')
django.setup()  # 伪造让django启动

# 导包
from django_redis import get_redis_connection

# 先通过别名拿到Redis连接对象,get_redis_connection(alias)
# alias:可以是Django的settings中CACHES中配置的多个别名
# conn = get_redis_connection()  # 不传值,默认使用的是default
conn = get_redis_connection('sms_code')  # 或者显示的使用某个别名

# 最基本的用法,获取连接对象,并设置值,同时指定过期时间,单位: 秒
conn.set('18211101111', '8842', ex=10)
# 在需要的地方在通过连接对象获取值
print(conn.get("18211101111"))

使用管道

import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day06.settings')
django.setup()  # 伪造让django启动

from django_redis import get_redis_connection

# 获取连接对象
conn = get_redis_connection('sms_code')
# 使用管道
pipe = conn.pipeline()
pipe.set('k1', 'v1')
pipe.set('k2', 'v2')
pipe.set('k3', 'v3')
res = pipe.execute()
print(res)

ttl相关

import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day06.settings')
django.setup()  # 伪造让django启动


from django_redis import get_redis_connection
conn = get_redis_connection('default')

# 设置键值对的时候,同时指定过期时间
conn.set('k2', 'v2', ex=25)
print(conn.get("k2"))
print(conn.ttl("k2"))
"""
conn.ttl("k111")的返回值
    如果返回 具体的数字 表示该key的剩余过期时间
    如果返回 -2       表示key不存在
    如果返回 -1       表示key存在,但没有设置过期时间
"""
# 如果一个key最开始没有设置超时时间,那么,我们可以手动通过expire给它设置一个超时时间
conn.set('k3', 'v3')
conn.expire('k3', 20)  # 设置过期时间
print(conn.ttl("k3"))
conn.persist('k3')    # 取消过期时间
print(conn.ttl("k3"))

其它用法

import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day06.settings')
django.setup()  # 伪造让django启动


from django_redis import get_redis_connection
conn = get_redis_connection('default')

# 其它数据类型操作就按照redis-py模块中讲的那样操作就好了
# ------------------ hash ------------------
# data = {"id": 1, "user": "zhangkai", 'age': 18}
#
# # conn.hmset('user1', mapping=data)  # 这种写法,有问题
# conn.hset(name='user2', key="name", value='zhangkai')  # 这种写法没问题
# # conn.hset(name='user3', mapping=data)  # 这种写法,有问题
# print(conn.hget('user2', 'name'))

# ------------------ list ------------------
# l1 = ['a', 'b', 'c', 'd']
# # conn.lpush('l1', 'a', 'b', 'c')
# # 批量插入列表
# conn.lpush('l1', *l1)
# print(conn.lrange('l1', 0, 2))

# ------------------ set ------------------
# # 声明集合s1,并添加多个元素,也可以是一个元素,也可以批量添加多个元素
# conn.sadd('s1', 'a')
# # 对于整型同样转为str, 且元素不能重复,且是无序的
# conn.sadd('s1', 'a', 'b', 'c', 1, 2, 3, 1)
# # 返回 s1 中所有元素
# print(conn.smembers('s1'))

# ------------------ zset ------------------
# # 增
# score = {
#     "zhangkai": 108,
#     "likai": 99,
#     "wangkai": 100,
#     "zhaokai": 102,
# }
# conn.zadd('score', mapping=score)
# # 查, withscores是可选参数,表示同时返回所有成员和分数
# print(conn.zrange('score', 0, -1))
# print(conn.zrange('score', 0, -1, withscores=True))
posted @   silencio。  阅读(209)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示