前言
Django自带了缓存机制,djangorestframework也可以使用drf-extensions扩展进行接口缓存,但这两种缓存都是基于url, 基于request.get_full_path(),这就导致管理缓存不是很方便。比如要删除一个带参数的url缓存,就要cache.deletepattern(“url*“), 如果reids key很多的话,会很影响性能(redis是单线程要阻塞,keys命令会遍历整个key,会很慢,可以考虑scan,但这不在本文讨论范围内)。
一种方案
因为redis有丰富的数据结构,所以分页缓存可以有一种解决方案:对于不带参数的页面或接口缓存,可以直接用前面提到的方式,对于带参数的页面缓存,可以用reids的hash结构,其中url作为key,url的参数作为hash的key,response作为value,这样要删除缓存的话,只要删除作为url的key就可以了。
django&drf-extensions缓存
django基于redis的缓存只用到了redis的string数据结构,比较简单,其原理
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
其中视图缓存用@cache_page(timeout, cache, key_prefix)
>>> from django.core.cache import cache
>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
# Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key')
None
>>> cache.get('my_key', 'has expired')
'has expired'
>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'
>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
>>> cache.delete_maney(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
其他的api,详见django/core/cache/backends/base.py drf-extensions缓存原理大体也差不多,其可以通过key_func自定义key的名字
分页缓存的实现
# -*- coding: utf-8 -*-
""""
Name: 分页缓存装饰器
Author: itswcg
Datetime: 2018-9-20
Desc: fork drf-extensions/cache/decorators.py 基于drf-extentions修改,以下注释的地方即表示修改的地方
"""
import json # json
from functools import wraps
from rest_framework.response import Response # 使用restframework响应
from django.utils.decorators import available_attrs
from rest_framework_extensions.settings import extensions_api_settings
from django.utils import six
def get_page_cache():
from django_redis import get_redis_connection # 使用django_redis操作redis其他数据结构
return get_redis_connection()
class CacheResponse(object):
def __init__(self,
timeout=None,
key_func=None,
cache=None,
cache_errors=None,
page=False): # 加一个参数是否分页
if timeout is None:
self.timeout = extensions_api_settings.DEFAULT_CACHE_RESPONSE_TIMEOUT
else:
self.timeout = timeout
if key_func is None:
self.key_func = extensions_api_settings.DEFAULT_CACHE_KEY_FUNC
else:
self.key_func = key_func
if cache_errors is None:
self.cache_errors = extensions_api_settings.DEFAULT_CACHE_ERRORS
else:
self.cache_errors = cache_errors
self.page = page
self.cache = get_page_cache()
def __call__(self, func):
this = self
@wraps(func, assigned=available_attrs(func))
def inner(self, request, *args, **kwargs):
return this.process_cache_response(
view_instance=self,
view_method=func,
request=request,
args=args,
kwargs=kwargs,
)
return inner
def process_cache_response(self,
view_instance,
view_method,
request,
args,
kwargs):
key = self.calculate_key(
view_instance=view_instance,
view_method=view_method,
request=request,
args=args,
kwargs=kwargs
)
params = request.get_full_path().replace(request.path, '') # 获取参数
hash_key = params if params else '?page=1'
if self.page:
response = self.cache.hget(key, hash_key) # 如果使用分页缓存,用hash
else:
response = self.cache.get(key) # 否则,用string
if not response:
response = view_method(view_instance, request, *args, **kwargs)
response = view_instance.finalize_response(request, response, *args, **kwargs)
response.render() # should be rendered, before picklining while storing to cache
if not response.status_code >= 400 or self.cache_errors:
response_dict = (
response.rendered_content,
response.status_code,
response._headers
)
content = response_dict[0] # 只缓存内容
if self.page:
self.cache.hset(key, hash_key, content) # 设置缓存
self.cache.expire(key, self.timeout)
else:
self.cache.set(key, content, self.timeout)
else:
content = response
response = Response(json.loads(content)) # 解析
if not hasattr(response, '_closable_objects'):
response._closable_objects = []
return response
def calculate_key(self,
view_instance,
view_method,
request,
args,
kwargs):
if isinstance(self.key_func, six.string_types):
key_func = getattr(view_instance, self.key_func)
else:
key_func = self.key_func
return key_func(
view_instance=view_instance,
view_method=view_method,
request=request,
args=args,
kwargs=kwargs,
)
cache_response = CacheResponse
# 使用
@cache_response(timeout=60 * 60 * 1, cache='default', page=True, key_func=calculate_key)
def main(request, *args, **kwargs):
pass
参考
https://docs.djangoproject.com/en/2.1/topics/cache/
https://www.v2ex.com/t/491596#reply41
https://blog.csdn.net/permike/article/details/53217742