rest_framework_cache优化接口访问速度
场景
在DRF中,有一些高频的列表接口(如供前端搜索下拉选择的数据源接口),使用缓存可以大幅度的提高接口的响应速度。
项目介绍
django-rest-framework-cache
是一个嵌入Serializer
层去缓存数据的工具,通过重写Serializer
的to_representation
方法来实现缓存。若你重写了ModelSerializer
的to_representation
方法。
安装
settings.py:
urls.py:
配置
缓存源
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
},
'rest_backend': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
REST_FRAMEWORK_CACHE = {
'DEFAULT_CACHE_BACKEND': 'rest_backend',
}
全局超时时长
用法
from rest_framework import serializers
# You must import the CachedSerializerMixin and cache_registry
from rest_framework_cache.serializers import CachedSerializerMixin
from rest_framework_cache.registry import cache_registry
from .models import Comment
class CommentSerializer(serializers.ModelSerializer, CachedSerializerMixin):
class Meta:
model = Comment
cache_registry.register(CommentSerializer)
注意事项:django-rest-framework-cache
通过重写Serializer
的to_representation
方法来实现缓存。若你重写了ModelSerializer
的to_representation
方法,因为Python3的MRO算法的原因,Serializer
使用时应先继承CachedSerializerMixin
:
实测效果
数据列表返回的数据共有310条类似下列的数据:
{
"id": 310,
"name": "芒果",
"no": "600006",
"unit_group": {
"id": 1,
"name": "重量",
"minimum_unit": {
"id": 4,
"name": "g",
"display_name": "克"
}
}
}
在CPU:i7-7700 3.6G
内存:16GB
系统:Win764
位,使用requests
工具访问100次的平均用时如下:
使用Cached
前:0.459s 使用LocMemCache
后:0.197s 使用MemcachedCache
后:0.0538s
在CPU:G4400 0.8G
内存:8GB
系统:Ubuntu14.04
位,使用requests
工具访问100次的平均用时如下:
使用Cached
前:0.639s 使用LocMemCache
后:0.319s 使用MemcachedCache
后:0.0817s
django-rest-framework-cache
使用LocMemCache
优化后可以缩短用时至原先的1/2
,使用MemcachedCache
优化后可以缩短用时至原先的1/8
。
Issue:嵌套的FK数据或者M2M数据修改后不生效问题(Updated in 2018/08/02)
经过一个多月的使用,发现此插件有一个不完美的地方。以上述的评论模型Comment
为例,假设评论有一个外键叫所属人owner
,在列表评论的时候,使用的Serializer
如下:
class CommentSerializer(serializers.ModelSerializer, CachedSerializerMixin):
owner = UserSerializer(read_only=True)
class Meta:
model = Comment
当评论列表的数据被缓存下来以后,再去修改评论中对应owner
的信息,再次访问该评论列表取到的依旧是未修改前的用户信息。这是因为缓存的serializer
信息,只有在该serializer
对应的Model
实例被修改后才会刷新,也就是说只有在Comment
实例被修改时缓存才会被刷新,修改Comment
对应的外键User
实例并不会去刷新缓存。
对此插件做如下改进,重写CachedSerializerMixin
,使其可以选择某一些字段不进行缓存:
from collections import OrderedDict
from django.utils.functional import cached_property
from rest_framework import serializers
from rest_framework.fields import SkipField
from rest_framework.relations import PKOnlyObject
from rest_framework_cache.cache import cache
from rest_framework_cache.settings import api_settings
from rest_framework_cache.utils import get_cache_key
class CachedSerializerMixin(serializers.ModelSerializer):
def _get_cache_key(self, instance):
request = self.context.get('request')
protocol = request.scheme if request else 'http'
return get_cache_key(instance, self.__class__, protocol)
@cached_property
def _not_cache_fields(self):
not_cache_fields = getattr(self.Meta, 'not_cache_fields', [])
return [
field for field in self.fields.values()
if field.field_name in not_cache_fields
]
@cached_property
def _cacheable_fields(self):
not_cache_fields = getattr(self.Meta, 'not_cache_fields', [])
return [
field for field in self.fields.values()
if not field.write_only and field.field_name not in not_cache_fields
]
def fields_to_representation(self, instance, fields, ret):
for field in fields:
try:
attribute = field.get_attribute(instance)
except SkipField:
continue
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
if check_for_none is None:
ret[field.field_name] = None
else:
ret[field.field_name] = field.to_representation(attribute)
return ret
def to_representation(self, instance):
"""
Checks if the representation of instance is cached and adds to cache
if is not.
"""
key = self._get_cache_key(instance)
cached = cache.get(key)
if cached:
ret = cached
else:
ret = OrderedDict()
ret = self.fields_to_representation(instance, self._cacheable_fields, ret)
cache.set(key, ret, api_settings.DEFAULT_CACHE_TIMEOUT)
ret = self.fields_to_representation(instance, self._not_cache_fields, ret)
return ret
用法:
class UserSerializer(serializers.ModelSerializer, CachedSerializerMixin):
class Meta:
model = User
cache_registry.register(UserSerializer)
class CommentSerializer(serializers.ModelSerializer, CachedSerializerMixin):
owner = UserSerializer(read_only=True)
class Meta:
model = Comment
not_cache_fields = ('owner',)
cache_registry.register(CommentSerializer)
使用not_cache_fields
选项指定CommentSerializer
不缓存owner
字段,该字段由UserSerializer
的缓存读取,这样就可以解决上述问题了。
已提交PR,不过按作者的情况来看可能不会合并了。我Fork出来修改好的库见此处,需要注意的是因为精力有限的原因该改进仅在djangorestframework==3.8.2
的基础上进行了测试。