缓存 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 设计中,如果要获得一个资源,一定要用GET方法么
springboot的服务端Restful风格 API接口,在不同场景下,设置不同的请求及传参方式的设计,及其他异常场景解决方案