Django缓存机制介绍

概述:Django缓存机制介绍

Django缓存机制介绍

1.什么是缓存

缓存是一类可以更快地读取数据的介质统称,亦指其它可以加快数据读取的存储方式,通常用来存储临时数据,常用介质的是读取速度很快的内存。一般来说从数据库多次把所需要的数据提取出来,要比从内存或者硬盘等一次读出来付出的成本大很多。对于中大型网站而言,使用缓存减少对数据库的访问次数可以提升网站性能。

2.为什么使用缓存

以Django为例,当用户请求到达视图(View)后,视图会先从数据库提取数据放到模板中进行动态渲染,渲染后的结果就是用户看到的网页。如果用户每次请求都从数据库提取数据并渲染,将极大降低性能,不仅服务器压力大,而且客户端也无法即时获得响应。如果能将渲染后的结果放到速度更快的缓存中,每次有请求过来,先检查缓存中是否有对应的资源,如果有,直接从缓存中取出来返回响应,节省取数据和渲染的时间,不仅能大大提高系统性能,还能提高用户体验。

缓存的优势:降低服务器负载、避免重复计算工作、提升系统性能

3.Django缓存机制架构图

系统架构图

4.缓存方式

(1).开发调试

Dummy caching (for development) ,假缓存,仅开发过程中使用,以调试缓存接口,数据并没有真正缓存。

1
2
3
4
5
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}

(2).本地内存缓存

Local-memory caching,使用系统内存缓存。

1
2
3
4
5
6
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}

(3).文件系统缓存

Filesystem caching,文件系统缓存,将缓存会把键值存储到独立的文件。

1
2
3
4
5
6
7
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',
#'LOCATION': 'c:/foo/bar',#绝对路径
}
}

(4).数据库缓存

Database caching,数据库缓存,这个指把数据缓存到数据表。

1
2
3
4
5
6
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}
}

(预先准备)创建数据缓存表:

1
python manage.py createcachetable [my_cache_table_name]

(5).Memcache缓存

Memcached,完全基于内存的缓存服务器,是Django本地支持的最快速,最高效的高速缓存类型,最初开发用于处理LiveJournal.com的高负载,随后由Danga Interactive开源,Facebook和维基百科等网站使用它来减少数据库访问并显着提高网站性能。它用于动态Web应用以减轻数据库负载从而显著提供网站性能,也是django中到目前为止最有效率的可用缓存。

特性:本身不支持持久化,不支持分布式。

缺点:缓存的数据存储在内存中,当服务器崩溃会导致数据丢失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 使用IP和端口连接远端单个Memcache服务器
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '10.10.10.10:11211', # 远端Memcache服务器IP和端口
}
}

# 使用本地socket文件(Unix套接字文件)连接本地Memcache服务器
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': 'unix:/tmp/memcached.sock', # 本地socket文件
}
}

# 连接远端多个Memcache服务器,做分布式缓存
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [
('10.10.10.10:11211', 10), # 设置远端Memcache服务器IP和端口以及优先级
('10.10.10.11:11211', 20),
]
}
}
1
2
# pylibmc模块的使用方法同上面相同,仅需修改BACKEND
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache'

(6).自定义缓存

Using a custom cache backend,自定义缓存后端,比如使用Redis。

1
2
3
4
5
CACHES = {
'default': {
'BACKEND': 'path.to.backend',
}
}

缓存(Cache)其它参数

参数名 参数关键字 含义
TIMEOUT 用于缓存的默认超时时间(以秒为单位),默认是300秒,当值为0将导致密钥立即过期(实际上“不缓存”)
OPTIONS MAX_ENTRIES 缓存允许的最大条目数,超过这个数则旧值会被删除,默认是300。
OPTIONS CULL_FREQUENCY 当达到MAX_ENTRIES时被删除的条目比例,实际比率是1/CULL_FREQUENCY,默认是3。
OPTIONS KEY_PREFIX 缓存key的前缀,默认为空。
OPTIONS VERSION 缓存key的版本,默认为1。
OPTIONS KEY_FUNCTION 生成key点函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 示例
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': os.path.join(BASE_DIR, "cache"),
'TIMEOUT': 300,
'OPTIONS':{
'MAX_ENTRIES': 300,
'CULL_FREQUENCY': 3,
},
'KEY_PREFIX': '',
'VERSION': 1,
'KEY_FUNCTION': function,
}
}

5.缓存粒度

(1).Per-site cache

1
2
3
4
5
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware', # 第一个
# 其他中间件...
'django.middleware.cache.FetchFromCacheMiddleware', # 最后一个
]

(2).Per-view cache

views.py

1
2
3
4
5
6
7
from django.views.decorators.cache import cache_page
import time

@cache_page(10) # 缓存10秒
def cache(req):
time_now = time.time()
return render(req, "cache.html", locals())

cache.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ time_now }}
</body>
</html>

(3).Template fragment caching

views.py

1
2
3
4
5
import time

def cache2(req):
time_now = time.time()
return render(req, "cache2.html", locals())

cache.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{% load cache %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ time_now }}
<hr>
{% cache 5 k1 %}
{{ time_now }}
{% endcache %}
</body>
</html>
1
其中{% cache cache_time cache_key %} cache_value {% endcache %}中,cache_value为填入的缓存内容,cache_time是缓存的时间,以秒为单位。

(4).Low-level cache API

缓存修改较少的变量,包括:字符串、字典、模型对象等

1
2
3
from django.core.cache import cache
cache.set(key, value) # 写入
myname = cache.get("myname") # 读取

(5).Downstream cache

在用户请求抵达网站时预先进行缓存,例如ISP(互联网服务提供商)会缓存部分页面数据,该种方式可以极大地提升效率,但是存在一定的危险,由于部分页面内容信息存在敏感数据(基于身份验等),若盲目地保存页面数据可能暴露给其他访问者,不过可以通过设置来限制缓存机制对指定的页面数据的缓存。

6.Redis相关操作

(1)下载&安装

a.下载

https://redis.io/download

b.安装

1
2
3
4
5
6
7
8
# 启动终端
tar -zxvf redis-6.0.5.tar.gz
sudo cp -rf redis-6.0.5 /usr/local/
cd /usr/local/redis-6.0.5
sudo make test
sudo make install
sudo mkdir bin etc db
sudo cp src/mkreleasehdr.sh src/redis-benchmark src/redis-check-rdb src/redis-cli src/redis-server bin/

(2)启动服务&客户端

a.启动服务:redis-server

b.启动客户端:redis-cli(需要重新开启一个新的命令行窗口)

(3)查看Redis信息状态

1
info

包含以下信息:

  • server:服务器信息;

  • clients:已连接客户端信息;

  • memory:内存信息;

  • persistence:持久化信息;

  • stats:一般统计信息

  • replication:主/从复制信息

  • CPU: CPU 的计算量统计信息

  • cluster:集群相关信息

  • keyspace:数据库相关的统计信息

(4)支持的五种数据类型及操作

  • string(字符串)
  • hash(哈希)
  • list(列表)
  • set(集合)
  • zset(有序集合)

(5)官方命令帮助文档

  • keys * :查看所有key的内容

  • exists key:查看key是否存在

  • flushall:清空所有信息

  • ……

https://redis.io/commands

(6)python连接redis

a.安装

1
pip install redis

b.传统连接

1
2
3
4
5
import redis
conn=redis.Redis(host='127.0.0.1',port=6379,password='pwd')
conn.set('name','jimmy')
name=conn.get('name')
print(name)

c.连接池连接

redis_pool.py

1
2
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, password='pwd', max_connections=1000)

file.py

1
2
3
4
5
6
7
8
9
10
11
12
13
import redis
from redis_pool import pool
while True:
key=input("请输入key:")
val=input("请输入val:")
conn = redis.Redis(connection_pool=pool)
conn.set(key, val)

# 管道(以原子性方式实现一次请求指定多个命令)
pipe = r.pipeline(transaction=True)
pipe.set('name','ale')
pipe.set('role','ab')
pipe.execute()

(7)基本命令

keys - 查看所有key的内容

exists key - 查看key是否存在

set key value - 设置key的内容

get key - 获取key的内容

flushall - 清空所有信息

(8)可视化工具

(9)查看端口/进程

查看端口:lsof -i:6379

查看进程:ps -ef | grep redis

7.django-redis的配置

(1)安装

1
pip install django-redis

(2)settings配置

a.使用Redis缓存网站数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
REDIS_DEFAULT_LOCATION = env('REDIS_DEFAULT_LOCATION', 'redis://127.0.0.1:6379/1') #redis服务器地址
CACHE_DEFAULT_TIMEOUT = env('REDIS_DEFAULT_TIMEOUT', 7 * 24 * 60 * 60)
'''
default是连接池的名称(名称可以修改,还可以在往后面加连接池)
'''
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": REDIS_DEFAULT_LOCATION,
'TIMEOUT': CACHE_DEFAULT_TIMEOUT,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"max_connections": 100}, #最大连接池100
"PASSWORD": "pwd", #若有密码需要该项
}
}
}

配置好后可进行测试缓存是否成功:

1
2
python manager.py shell
>>> cache.set("key","value")

b.缓存Celery

1
2
3
4
5
# Redis连接配置
CELERY_URL=redis://127.0.0.1:6379
# Celery配置
CELERY_BROKER_URL=redis://127.0.0.1:6379/1 # 使用Redis 1作为消息代理
CELERY_RESULT_BACKEND=redis://127.0.0.1:6379/2 # 任务结果存储于Redis 2

c.使用Redis缓存Channel layers

1
2
3
4
5
6
7
8
9
CHANNEL_LAYERS = {
"default":{
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG":{
"hosts":[f'{env("REDIS_URL",default="redis://127.0.0.1:6379")}/3'],
# 使用Redis 3 缓存channel layers
}
}
}

d.激活压缩(默认:关闭)

1
2
3
4
5
6
7
8
CACHES = {
"default": {
# ...
"OPTIONS": {
"COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor",
}
}
}

(3)不同级别的缓存

视图指定部分缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
import redis
from django.shortcuts import render,HttpResponse
from django_redis import get_redis_connection #导入连接池

def index(request):
conn=get_redis_connection('default') #拿到defalut这个redis连接池
conn.set("name","egon") #设置值
return HttpResponse("设置成功!")

def order(request):
conn=get_redis_connection('default')
name=conn.get("name")
return HttpResponse(name) #返回值

全站缓存

1
2
3
'django.middleware.cache.UpdateCacheMiddleware'      #最上面
...其他中间件
'django.middleware.cache.FetchFromCacheMiddleware' #最下面

单视图缓存

1
2
3
4
5
6
from django.views.decorators.cache import cache_page

@cache_page(60*15) #15秒
def index(request):
ctime=str(time.time())
return HttpResponse(ctime)

8.缓存性能评估工具——Django Debug Toolbar

Django Debug Toolbar 不仅仅可以评估Django中的Cache性能,还可以监控 SQL语句操作、CPU 运行情况、静态文件使用情况、模板使用情况等,提供了前端面板,操作简单方便。

(1)安装及配置

a.下载依赖库

1
pip install django-debug-toolbar

b.修改配置信息(settings.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 打开DEBUG模式
DEBUG = True

# 在INSTALLED_APPS加入debug-toolbar
INSTALLED_APPS = (
......
'debug_toolbar',
)

# 添加中间件
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
......
]

c.配置URL(urls.py)

1
2
3
4
5
if settings.DEBUG:
import debug_toolbar
urlpatterns = [
url(r'^__debug__/', include(debug_toolbar.urls)),
] + urlpatterns

其它配置参考文档:链接

Django Debug Toolbar 中的 Cache 部分提供了以下信息:

  • 总次数
  • 总耗时
  • 命中次数
  • 未命中次数
  • 各操作执行次数
  • 具体请求的耗时、类型、参数、关键字参数、Backend

(2)动手实践

操作系统:macOS Catalina

内存: 16GB 2667 MHz DDR4

缓存方式:自定义缓存——redis;

缓存粒度:Low level cache API

实践代码:

a.安装

1
pip install django-redis

b.CACHES设置

1
2
3
4
5
6
7
8
9
10
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": 'redis://127.0.0.1:6379',
'TIMEOUT': 60,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}

c.视图中进行缓存

1
2
3
4
5
6
7
8
from django.core.cache import cache

if cache.get("age"):
print(cache.get("age"))
else:
cache.set("age", 18) # 写入
cache.get("name")
cache.get("age")

d.实践效果

first

从实践效果图发现第一次读取耗时较后续读写操作更多,主要原因是django-redis 默认使用Django setting 中 DJANGO_REDIS_CONNECTION_FACTORY 参数指定,在没提供该参数的前提下默认使用 django_redis.pool.ConnectionFactory 类产生连接:

1
2
3
4
5
6
7
8
9
10
11
# 源码: django_redis/pool.py
def get_connection_factory(path=None, options=None):
if path is None:
path = getattr(
settings,
"DJANGO_REDIS_CONNECTION_FACTORY",
"django_redis.pool.ConnectionFactory",
)

cls = import_string(path)
return cls(options or {})

其中连接池的连接方式保证了数据库的连接得以重用,避免了较为频繁的创建与释放连接导致的大量性能开销,减少系统消耗的基础并提升了系统运行环境的平稳性,减少内存碎片和数据库临时进程和线程的数量。

因而第一次请求耗时较长的原因包含了连接耗时,后续的读写请求将重用数据库的连接,将耗时大大降低。

8.python 连接 redis

安装

1
pip install redis

连接

  • 传统方式
1
2
3
4
5
6
7
import redis

conn=redis.Redis(host='127.0.0.1',port=6379,password='pwd')
conn.set('name','jimmy')

name=conn.get('name')
print(name)
  • 连接池方式

与传统方式连接不同之处:链接不断开且长时间保持连接

1
2
3
4
import redis
pool=redis.ConnectionPool(host='127.0.0.1',port=6379,password='pwd,max_connections=1000)
conn=redis.Redis(connection_pool=pool)
conn.set('foo','Bar')
  • 单例模式使用连接池

redis_pool.py

1
2
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, password='pwd', max_connections=1000)

file.py

1
2
3
4
5
6
7
import redis
from redis_poll import pool
while True:
key=input("请输入key:")
val=input("请输入val:")
conn = redis.Redis(connection_pool=pool)
conn.set(key, val)

处理字典数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import redis
pool=redis.ConnectionPool(host='47.99.191.149',port=6379,password="cyy520",max_connections=1000)
conn=redis.Redis(connection_pool=pool)

# 字典操作方式(一):
conn.hset('k1','username','alex')
conn.hset('k1','age',18)

# 字典操作方式(二)
conn.hmset('k2',{'username': 'jim', 'age': '19'})

'''
redis={
k1:{
username:alex,
age:18
}
}
'''

# 获取指定key
val=conn.hget('k1','username')
print(val)

# 获取多个key
val2=conn.hmget('k2','username','age')

# 获取所有键值对
vals=conn.hgetall('k1')
print(vals) #{b'username': b'alex', b'age': b'18'}

# 迭代读取(以每次读取100个为例)
ret = conn.hscan_iter('k1',count=100)
for item in ret:
print(item)

事务操作

1
2
3
4
5
6
7
pipe=conn.pipeline(transaction=True)#创建一个pipe,事务为True
pipe.multi()

pipe.set('k1',123)
pipe.hset('k2','num',666)

pipe.execute()

:paperclip:问题及解决方法

(1)Redis编译过程报错

Q:执行sudo make test编译过程可能报错

A:执行命令清理再编译:sudo make distclean && sudo make && sudo make test

(2)Redis存储过程失败

Q:存储过程中出现“Failed opening the RDB file dump.rdb (in server root dir /usr/local/redis-6.0.5) for saving: Permission denied”

A:未赋予权限,需要对所安装的redis目录给予777权限:sudo chmod -R 0777 redis-6.0.5

其它补充参考

redis cookbook

redis五大难题解决方法:雪崩,穿透,并发

微服务架构下的缓存系统设计

互联网架构中缓存介绍

最全面的缓存架构设计

Redis集群

Install and Configures Redis server instances

A Django Channels channel layer

使用第三方组件(django-redis)创建连接池

redis python client