这就算是第一篇博客了,我老大陈如杰(一个牛逼的程序员)讲过,只有自己研究了的东西才能写到博客上,我谨遵教诲。

  

  环境: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的东西,有什么错误,欢迎大家指正!