这就算是第一篇博客了,我老大陈如杰(一个牛逼的程序员)讲过,只有自己研究了的东西才能写到博客上,我谨遵教诲。
环境:Django==1.8.2、django-tastypie==0.12.2-dev、redis==2.10.3、django-redis==4.1.0
django配置了使用redis作为后台缓存
在ubuntu中安装了redis-server(sudo apt-get install redis-server),在django的settings中加入如下配置:(具体可以参考http://niwinz.github.io/django-redis/latest/)
# to set django's default cache add by minkedong CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } }
在写tastypie自己的ModelResource时,在Meta中加入:
cache = SimpleCache(timeout=8*60*60)
当我请求此ModelResource的时候,发出GET请求“/api/v1/author/1/”后,在ubuntu的bash中使用命令:redis-cli -n 1 进入redis,在redis中键入命令:keys *,查看到已经缓存了此API的请求结果。
但当我发出GET请求"/api/v1/author/"后,进入redis查看,发现并没有缓存我的结果。
果断进入tastypie的源码查看,在"tastypie.resources.ModelResource"中有如下两个方法:
def get_list(self, request, **kwargs): """ Returns a serialized list of resources. Calls ``obj_get_list`` to provide the data, then handles that result set and serializes it. Should return a HttpResponse (200 OK). """ # TODO: Uncached for now. Invalidation that works for everyone may be # impossible. base_bundle = self.build_bundle(request=request) objects = self.obj_get_list(bundle=base_bundle, **self.remove_api_resource_names(kwargs)) sorted_objects = self.apply_sorting(objects, options=request.GET) paginator = self._meta.paginator_class(request.GET, sorted_objects, resource_uri=self.get_resource_uri(), limit=self._meta.limit, max_limit=self._meta.max_limit, collection_name=self._meta.collection_name) to_be_serialized = paginator.page() # Dehydrate the bundles in preparation for serialization. bundles = [] for obj in to_be_serialized[self._meta.collection_name]: bundle = self.build_bundle(obj=obj, request=request) bundles.append(self.full_dehydrate(bundle, for_list=True)) to_be_serialized[self._meta.collection_name] = bundles to_be_serialized = self.alter_list_data_to_serialize(request, to_be_serialized) return self.create_response(request, to_be_serialized) def get_detail(self, request, **kwargs): """ Returns a single serialized resource. Calls ``cached_obj_get/obj_get`` to provide the data, then handles that result set and serializes it. Should return a HttpResponse (200 OK). """ basic_bundle = self.build_bundle(request=request) try: obj = self.cached_obj_get(bundle=basic_bundle, **self.remove_api_resource_names(kwargs)) except ObjectDoesNotExist: return http.HttpNotFound() except MultipleObjectsReturned: return http.HttpMultipleChoices("More than one resource is found at this URI.") bundle = self.build_bundle(obj=obj, request=request) bundle = self.full_dehydrate(bundle) bundle = self.alter_detail_data_to_serialize(request, bundle) return self.create_response(request, bundle)
使用pdb打断点,发现“/api/v1/author/1/”请求调用get_detail方法,而"/api/v1/author/"请求调用get_list方法,从源码中不难看出,get_detail中调用了cached_obj_get方法,其从cache中拿数据,而get_list调用了obj_get_list方法(源码中有cached_obj_get_list方法,其并未调用),即未从cache中拿数据(其实get_list方法的注释中已经给出了解释
# TODO: Uncached for now. Invalidation that works for everyone may be impossible.),好吧,那解决方式就比较简单了,直接在自己的ModelResource中覆盖掉此方法,更改成:
def get_list(self, request, **kwargs): """ Returns a serialized list of resources. Calls ``obj_get_list`` to provide the data, then handles that result set and serializes it. Should return a HttpResponse (200 OK). """ # TODO: Uncached for now. Invalidation that works for everyone may be # impossible. base_bundle = self.build_bundle(request=request) objects = self.cached_obj_get_list(bundle=base_bundle, **self.remove_api_resource_names(kwargs)) sorted_objects = self.apply_sorting(objects, options=request.GET) paginator = self._meta.paginator_class(request.GET, sorted_objects, resource_uri=self.get_resource_uri(), limit=self._meta.limit, max_limit=self._meta.max_limit, collection_name=self._meta.collection_name) to_be_serialized = paginator.page() # Dehydrate the bundles in preparation for serialization. bundles = [] for obj in to_be_serialized[self._meta.collection_name]: bundle = self.build_bundle(obj=obj, request=request) bundles.append(self.full_dehydrate(bundle, for_list=True)) to_be_serialized[self._meta.collection_name] = bundles to_be_serialized = self.alter_list_data_to_serialize(request, to_be_serialized) return self.create_response(request, to_be_serialized)
再次请求"/api/v1/author/"后,发现redis中已经缓存了此结果!
但是,这里面后面的实际请求会有一个问题,那就是当你请求“/api/v1/author/”和请求“/api/v1/author/?format=json&name=jack”(假设有name筛选参数),其返回的json数据都是全量的数据,为什么呢?因为他在redis中生成的cache key都是一样的,我们要针对不同的请求参数生成不同的cache key,相当于不同的请求参数也cache一份数据。我的做法如下,直接在自己的ModelResource中覆盖掉cached_obj_get_list方法,更改成:
def cached_obj_get_list(self, bundle, **kwargs): """ 更改get_list的cache key,使其对不同参数存不同的cache key """ if hasattr(bundle.request, 'GET'): # Grab a mutable copy. filters = bundle.request.GET.copy() filters.update(kwargs) applicable_filters = self.build_filters(filters=filters) applicable_str = ':'.join(sorted(["%s=%s" % (key, ','.join(value) if type(value) == type([]) else value, ) for key, value in applicable_filters.items()])) cache_key = self.generate_cache_key('list', applicable_str, **kwargs) obj_list = self._meta.cache.get(cache_key) if obj_list is None: obj_list = self.obj_get_list(bundle=bundle, **kwargs) self._meta.cache.set(cache_key, obj_list) return obj_list
因为对redis的机制不太了解,后面自己将研究一下redis的东西,有什么错误,欢迎大家指正!