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

Posted

技术标签:

【中文标题】缓存 GET 调用的 RESTful API 结果的最佳方法【英文标题】:Best way to cache RESTful API results of GET calls 【发布时间】:2012-12-11 03:40:17 【问题描述】:

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

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

我已经想到了很多可能的解决方案,我最好的两个想法:

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

一个网络服务器代理(高度可配置),可能类似于 Squid,但我以前从未在这种情况下使用过代理,对此我完全不确定

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

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

如果有人有关于此主题的提示或好的资源可以阅读,请告诉我。

【问题讨论】:

【参考方案1】:

memcached 是一个很好的选择,我看到你已经提到了这个可能的选择。此外,Redis 似乎在这个级别上作为另一种选择受到了很多赞扬。

在应用程序级别,就逐个文件和/或模块缓存的更细粒度方法而言,本地存储始终是用户可能反复请求的常见对象的一种选择,即使简单到只是将响应对象放入会话中,以便可以重用,而不是进行另一个 http 休息调用和适当的编码。

现在人们就 varnish 和 squid 争论不休,两者似乎各有优缺点,所以我无法评论哪个更好,但很多人说 Varnish 和经过调整的 apache 服务器非常适合动态网站。

【讨论】:

【参考方案2】:

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

对于大量登录用户来说,在完整页面缓存后面拥有某种模型缓存将非常有益,因为即使有些模型不共享,许多模型仍然是共享的(在良好的 OOP 结构中)。

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

在我看来,Varnish 既好又简单,但确实有些人更喜欢 Squid。

【讨论】:

会话信息存储在cookie中,如果调用的方法需要,我会在服务器端检查它们。如果这仍然不是 RESTful 方式,我会感到惊讶,但请告诉我。 好的,我找到了关于这个话题的很好的讨论link【参考方案3】:

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

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

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

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

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

【讨论】:

谢谢,我读了你的帖子和一些关于 Rack::Cache 的内容。这很有趣,我会仔细看看。 在你的块上,我看到你也在使用 Varnish,我猜如果 URI 和“ETag”与存储的结果匹配,Varnish 会返回缓存的结果,对吧?跨度> 我从未明确使用过 Varnish,但看起来 Heroku 现在正在他们的 Cedar 堆栈中使用 Varnish 来缓存内容。所有 ETag 内容仍应适用 "如果 ETag 相同,则使用 Rack::Cache 来提供缓存数据" - 向发送 304 响应的目的不是需要发送响应吗身体? (请帮助我理解!:))【参考方案4】:

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

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

【讨论】:

【参考方案5】:

我已经在我的 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)

【讨论】:

以上是关于缓存 GET 调用的 RESTful API 结果的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

restful API

Restful Api 的好与坏

在restful api 设计中,如果要获得一个资源,一定要用GET方法么

操作 url 参数 Restful API

PHP实现RESTful风格的API实例

springboot的服务端Restful风格 API接口,在不同场景下,设置不同的请求及传参方式的设计,及其他异常场景解决方案