缓存
1 缓存的定义
-
定义:缓存是一类可以更快的读取数据的介质统称,也指其他可以加快数据读取的存储方式,一般用来存储临时数据,常用介质的是读取速度很快的内存。
-
意义:视图渲染有一定成本,数据库的频繁查询过高,所以对于低频变动的页面可以考虑使用缓存技术,减少实际渲染次数,用户拿到响应的时间成本会更低。
-
Django 缓存的实现方法
# Django 官网 # https://docs.djangoproject.com/en/5.0/topics/cache/ # given a URL, try finding that page in the cache if the page is in the cache: return the cached page else: generate the page save the generated page in the cache (for next time) return the generated page
-
缓存场景:
-
博客列表页
-
电商商品详情页
场景特点:缓存的地方,数据变动频率较少。
-
2 缓存的配置
2.1 Mysql缓存
将缓存的数据存储在数据库中。
说明:尽管存储介质没有更换,但是当把一次负责查询的结果直接存储到表里,比如多个条件的过滤查询结果,可避免重复进行复杂的查询,提升效率。
在 settings.py
中添加 CACHES
配置块。
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
"LOCATION": "my_cache_table", # 缓存表的名称
"TIMEOUT": 300, #缓存的保存时间,单位秒,默认值是300
"POTIONS":{
"MAX_ENTRIES": 300, # 缓存最大的数据条数
"CULL_FREQUENCY": 2 # 缓存条数达到最大值时,删除 1/CULL_FREQUENCY 的缓存数据
}
}
}
创建缓存表。
python3 manage.py createcachetable
查看数据库。
使用 mysql 进行缓存,会将数据存储在指定的 mysql 表中。
2.2 Redis缓存
Redis是一个内存数据库,可用于缓存。
在 settings.py
中添加如下 CACHES
块。
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://username:password@127.0.0.1:6379", # username、password需要看redis是否启用身份认证,如未启用,这里则不需要配置。
# "LOCATION": [
# "redis://127.0.0.1:6379", # leader
# "redis://127.0.0.1:6378", # read-replica 1
# "redis://127.0.0.1:6377", # read-replica 2
# ],
# 如果有多个redis服务器时,这样配置即可
}
}
2.3 本地内存缓存
数据缓存到服务器内存中,如果配置文件中没有指定其他缓存,那么这是默认的缓存。如果你想获得内存缓存的速度优势,但又不具备运行 Memcached 的能力,可以考虑使用本地内存缓存后端。这个缓存是每进程所有(见下文)和线程安全的。
在 settings.py
中添加如下 CACHES
块。
# 说明:如下仅供测试,建议将数据存储到内存数据库中,如redis
# 每个进程都会有自己的私有缓存实例,这意味着不可能进行跨进程缓存,也即本地内存缓存的内存效率不是特别高,所以对于生产环境来说,它可能不是一个好的选择。对于开发来说是不错的选择。
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "unique_snowflake",
}
}
2.4 文件系统缓存
将缓存的数据存储到本地文件中。
在 settings.py
中添加如下 CACHES
块。
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
"LOCATION": "/home/euansu/tmp", # 这个是文件夹的路径
# "LOCATION": "c:\test\cache", # windows下示例
}
}
使用文件系统缓存,会在配置的目录下生成对应的缓存文件。
3 整体缓存策略
整体缓存这里指的是直接缓存一个视图函数全部的结果,与下部分的局部缓存形成对照。
3.1 视图函数
from django.views.decorators.cache import cache_page
@cache_page(30)
def my_view(request):
...
代码示例
from django.shortcuts import render
from django.views.decorators.cache import cache_page
from django.http import HttpResponse
# Create your views here.
from datetime import datetime
import pytz
@cache_page(30)
def mysql_cache(request):
"""
MysqlCache
:param request:
:return:
"""
target_timezone = pytz.timezone('Asia/Shanghai')
current_time = datetime.now()
current_time_shanghai = current_time.replace(tzinfo=pytz.utc).astimezone(target_timezone)
time_string = current_time_shanghai.strftime("%Y-%m-%d %H:%M:%S")
return HttpResponse(time_string)
3.2 路由函数
from django.views.decorators.cache import cache_page
urlpatterns = [
path('foo/', cache_page(60)(my_view)),
]
代码示例
from django.urls import path
from django.views.decorators.cache import cache_page
from . import views
urlpatterns = [
path("mysql_cache/", cache_page(60)(views.mysql_cache)),
]
4 局部缓存策略
视图函数局部耗时较多,但视图函数其他区域并不耗时。
4.1 使用场景
-
使用场景
# 局部耗时较多,但视图函数其他区域并不耗时 from django.shortcuts import render def index(request): # 时间复杂度极高的渲染 book_list = Book.objects.all() # 假设此处耗时2s return render(request,'idnex.html',locals())
4.2 局部缓存的使用
# 先引入cache对象
## 方式一:使用caches['CACHE配置key']导入具体对象
from django.core.cache import cache
cache1 = caches['default']
cache2 = caches['custom']
## 方式二:
from django.core.cache import cache
# 相当于直接引入 CACHES 配置项中的'default'项
4.3 局部缓存的方法
-
cache.set(key, value, timeout),存储缓存
key:缓存的key,字符串类型 value:Python对象 timeout:缓存存储时间(s),默认为CACHES中的TIMEOUT值 返回值:None
-
cache.get(key),获取缓存
key:缓存的key 返回值:为key的具体值,如果没有数据,则返回None
-
cache.add(key, value),存储缓存,只在key不存在时生效
返回值:True[存储成功] or False[存储失败]
-
cache.get_or_set(key, value, timeout),如果未获取到数据,则执行set操作
返回值:value
-
cache.set_many(dict, timeout),批量存储缓存
dict:key和value的字典 timout:存储的时间(s) 返回值:插入不成功的key的数组
-
cache.get_many(key_list),批量获取缓存数据。
key_list:包含key的数组 返回值:渠道的key和value的字典
-
cache.delete(key),删除key的缓存数据。
返回值:None
-
cache.delete_many(key_list),批量删除。
返回值:None
4.4 局部缓存测试
代码示例:
# 设置cache
from django.core.cache import cache
cache.set("uname", "euansu", 300)
cache.get("uname")
数据库中出现对应cache记录。
# 修改cache
cache.set("uname", "nange", 300)
cache.get("uname")
# 删除cache
cache.delete("uname")
cache.get("uname")
查询数据库,对应的缓存消失
5 浏览器缓存策略
浏览器也具备缓存技术,对于浏览器来说,每次向服务器发出请求都是耗时的操作,如果本身浏览器内部就具备当前 url 的内容,则一定时间内可以不必给服务器发消息,从而提升网页体验,降低服务器的请求压力。
浏览器缓存原理图
5.1 浏览器缓存-强缓存
不会向服务器发送请求,直接从缓存中读取资源。
服务器和浏览器之间的请求,通过如下两个响应头进行沟通。
-
响应头 - Expires
定义:缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点 样例:Expires:Thu, 02 Apr 2030 05:15:08 GMT
-
响应头 - Cache-Control
在HTTP/1.1中,Cache-Control主要用于控制网页缓存,比如当 Cache-Control:max-age=120 代表请求创建后的120秒,缓存失败 说明:目前服务器都会带着这两个头同时响应给浏览器,浏览器优先使用 Cache-Control
例如如下请求,浏览器中显示已缓存。
刷新页面,但浏览器中仅有一次请求。
查看请求响应,发现返回的响应中有 Cache-Control
和 Expires
两个响应头。
这里的 max-age=60
是通过 cache-page
实现的。
修改 cache_page
的参数,响应头中的 max-age
也发生变化。
5.2 浏览器缓存-协商缓存
图片、静态文件等这类比较费带宽且不易变化的数据,浏览器会跟服务器协商,确认当前的缓存是否可用,如果可用,服务器不需要返回数据,浏览器继续使用原来缓存的数据,如果文件不可用,则返回最新的数据。
-
Last-Modified 响应头和 If-Modified-Since 请求头。
说明:
-
Last-modified 为文件的最近修改时间,浏览器第一次请求静态文件时,服务器第一次请求静态文件时,服务器如果返回Last-Modified响应头,则代表该资源为需要协商的缓存。
-
当缓存到期后,浏览器将获取到的 Last-Modified 值作为请求头 If-Modified-Since 的值,与服务器发请求协商,服务器端返回304响应码[响应体为空],代表缓存继续使用,200响应码代表缓存不可用[响应体为最新资源]。
代码示例
from django.http import HttpResponse from django.utils import timezone def last_modified(request): """ 测试last-modified请求头 :param request: :return: """ last_modified_time = timezone.now() # 检查 If-Modified-Since 头 if request.META.get('HTTP_IF_MODIFIED_SINCE'): # 如果资源没有发生变化,返回 304 Not Modified return HttpResponse(status=304) # 如果资源发生了变化,设置 Last-Modified 响应头 response = HttpResponse("Your content here") response['Last-Modified'] = last_modified_time.strftime('%a, %d %b %Y %H:%M:%S GMT') return response
-
-
ETag 响应头和 If-None-Match 请求头。
说明:
-
ETag时服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,ETag就会重新生成。
-
缓存到期后,浏览器将ETag响应头的值作为If-None-Match请求头的值,给服务器发请求协商,服务器接到请求头后,比对文件标识,不一致则认为资源不可用,返回200响应码[响应体为最新资源],可用则返回304响应码。
代码示例
from django.http import HttpResponse import hashlib def etag_func(request): # 获取或计算资源的内容(假设这里是一个字符串) resource_content = "Your content here" # 计算 ETag(使用 MD5 散列作为示例,你可以根据需要选择其他方法) etag = hashlib.md5(resource_content.encode('utf-8')).hexdigest() # 检查 If-None-Match 头 if request.META.get('HTTP_IF_NONE_MATCH') == etag: # 如果资源没有发生变化,返回 304 Not Modified return HttpResponse(status=304) # 如果资源发生了变化,设置 ETag 响应头 response = HttpResponse(resource_content) response['ETag'] = etag return response
首次访问返回200。
再次请求,响应变为304。
-