缓存GET调用的RESTful API结果的最佳方法

时间:2012-12-24 22:07:56

标签: api rest caching memcached varnish

我正在考虑在前面创建缓存层的最佳方法,或者作为我的RESTful API的GET请求的第一层(用Ruby编写)。

并非每个请求都可以缓存,因为即使对于某些GET请求,API也必须验证请求的用户/应用程序。这意味着我需要配置哪个请求是可缓存的以及每个缓存的答案有效多长时间。在少数情况下,我需要很短的到期时间,例如: 15岁及以下。即使尚未达到到期日期,我也应该能够让缓存条目通过API应用程序到期。

我已经考虑过许多​​可能的解决方案,我最好的两个想法:

  • API的第一层(甚至在路由之前),我自己缓存逻辑(手中有所有配置选项),答案和存档到Memcached的到期日期

  • 一个网络服务器代理(高可配置),也许像Squid这样的东西,但我之前从未使用代理这样的情况而且我完全不确定

我还考虑过像Varnish这样的缓存解决方案,我将Varnish用于“常用”Web应用程序并且令人印象深刻,但配置有点特别。但如果它是最快的解决方案,我会使用它。

另一个想法是缓存到Solr索引,我已经在数据层中使用它来查询数据库以查找大多数请求。

如果某人有关于此主题的提示或良好来源,请与我们联系。

5 个答案:

答案 0 :(得分:5)

首先,将RESTful API构建为RESTful。这意味着经过身份验证的用户还可以获取缓存内容,以便将所有状态保留在包含身份验证详细信息所需的URL中。当然,这里的命中率会更低,但它可以缓存。

有大量登录用户,在整页缓存后面有一些模型缓存是非常有益的,因为许多模型仍然是共享的,即使有些模型不是(在一个良好的OOP结构中)。

然后,对于整页缓存,您最好将所有请求保留在Web服务器上,特别是在下一步(在您的情况下为Ruby)中远离动态处理。从普通Web服务器缓存完整页面的最快方法始终是在Web服务器前面的缓存代理。

Varnish在我看来是好的和容易的,但有些人更喜欢鱿鱼。

答案 1 :(得分:4)

由于REST是一种HTTP,因此缓存请求的最佳方式可能是使用HTTP缓存。

在您的响应中使用ETag,检查请求中的ETag以“304 Not Modified”回复,如果ETag相同,则使用Rack :: Cache来提供缓存数据。这适用于缓存控制“公共”内容。

Rack :: Cache最好配置为使用内存缓存来满足其存储需求。

我上周写了一篇关于Rack :: Cache使用ETag来检测缓存内容并将其返回给新客户端的有趣方式:http://blog.craz8.com/articles/2012/12/19/rack-cache-and-etags-for-even-faster-rails

即使您没有使用Rails,Rack中间件工具也非常适合这些东西。

答案 2 :(得分:3)

memcached是一个很好的选择,我看到你已经提到过这个可能的选择。此外,Redis似乎在这个级别上被另一个选择称赞。

在应用程序级别上,就文件和/或模块基于文件缓存的更细粒度方法而言,本地存储始终是用户可以一遍又一遍地请求的公共对象的选项,即使是如此简单只需将响应对象放入会话中,这样就可以重复使用,而不是进行另一次http休息调用和编码。

现在人们来回讨论清漆与鱿鱼,两者似乎都有其优点和缺点,所以我不能评论哪一个更好但很多人说Varnish配有一个优化的apache服务器非常适合动态网站。

答案 3 :(得分:2)

Redis缓存是最佳选择。 check here.

它是开源的。高级键值缓存和存储。

答案 4 :(得分:0)

我在REST视图中以这种方式成功使用了redis:

from django.conf import settings
import hashlib
import json
from redis import StrictRedis
from django.utils.encoding import force_bytes

def get_redis():
    #get redis connection from RQ config in settings
    rc = settings.RQ_QUEUES['default']
    cache = StrictRedis(host=rc['HOST'], port=rc['PORT'], db=rc['DB'])
    return cache



class EventList(ListAPIView):
    queryset = Event.objects.all()
    serializer_class = EventSerializer
    renderer_classes = (JSONRenderer, )


    def get(self, request, format=None):
        if IsAdminUser not in self.permission_classes:  # dont cache requests from admins


            # make a key that represents the request results you want to cache
            #  your requirements may vary
            key = get_key_from_request()

            #  I find it useful to hash the key, when query parms are added
            #  I also preface event cache key with a string, so I can clear the cache
            #   when events are changed
            key = "todaysevents" + hashlib.md5(force_bytes(key)).hexdigest()        

            # I dont want any cache issues (such as not being able to connect to redis)
            #  to affect my end users, so I protect this section
            try:
                cache = get_redis()
                data = cache.get(key)
                if not data:
                    #  not cached, so perform standard REST functions for this view
                    queryset = self.filter_queryset(self.get_queryset())
                    serializer = self.get_serializer(queryset, many=True)
                    data = serializer.data

                    #  cache the data as a string
                    cache.set(key, json.dumps(data))

                    # manage the expiration of the cache 
                    expire = 60 * 60 * 2  
                    cache.expire(key, expire)
                else:
                    # this is the place where you save all the time
                    #  just return the cached data 
                    data = json.loads(data)

                return Response(data)
            except Exception as e:
                logger.exception("Error accessing event cache\n %s" % (e))

        # for Admins or exceptions, BAU
        return super(EventList, self).get(request, format)

在我的事件模型更新中,我清除了任何事件缓存。 这几乎不会被执行(只有管理员创建事件,而不是经常), 所以我总是清除所有事件缓存

class Event(models.Model):

...

    def clear_cache(self):
        try:
            cache = get_redis()
            eventkey = "todaysevents"
            for key in cache.scan_iter("%s*" % eventkey):
                cache.delete(key)
        except Exception as e:
            pass


    def save(self, *args, **kwargs):
        self.clear_cache()
        return super(Event, self).save(*args, **kwargs)