Meilleur moyen de mettre en cache les résultats de L'API RESTful des appels GET
Je pense à la meilleure façon de créer une couche de cache en face ou en première couche pour les requêtes GET à mon API RESTful (écrite en Ruby).
Toutes les requêtes ne peuvent pas être mises en cache, car même pour certaines requêtes GET, L'API doit valider l'utilisateur / l'application demandeur. Cela signifie que je dois configurer quelle requête est cacheable et combien de temps chaque réponse mise en cache est valide. Pour quelques cas, j'ai besoin d'un temps d'expiration très court de par exemple 15s et ci-dessous. Et je devrais être capable de laisser cache les entrées expirent par L'application API même si la date d'expiration n'est pas encore atteinte.
J'ai déjà pensé à de nombreuses solutions possibles, mes deux meilleures idées:
-
Première couche de L'API (avant même le routage), logique de cache par moi-même (pour avoir toutes les options de configuration dans ma main), réponses et date d'expiration stockées dans Memcached
-
Un proxy de serveur web (Haute configurable), peut-être quelque chose comme Squid mais je n'ai jamais utilisé un proxy pour un cas comme celui-ci auparavant et je n'en suis absolument pas sûr
J'ai aussi pensé à une solution de cache comme Varnish, J'ai utilisé Varnish pour les applications web "habituelles" et c'est impressionnant mais la configuration est un peu spéciale. Mais je l'utiliserais si c'est la solution la plus rapide.
Une autre pensée était de mettre en cache l'Index Solr, que j'utilise déjà dans la couche de données pour ne pas interroger la base de données pour la plupart des requêtes.
Si quelqu'un a un indice ou de bonnes sources à lire sur ce sujet, laissez-moi savoir.
5 réponses
Memcached est une excellente option, et je vois que vous l'avez déjà mentionné comme une option possible. Redis semble également être loué beaucoup comme une autre option à ce niveau.
Au niveau de l'application, en termes d'approche plus granulaire du cache sur une base de fichier par fichier et/ou module, le stockage local est toujours une option pour les objets communs qu'un utilisateur peut demander encore et encore, même aussi simple que de simplement déposer des objets de réponse dans la session afin de pouvoir être réutilisés et le codage de manière appropriée.
Maintenant, les gens vont et viennent débattre de varnish vs squid, et les deux semblent avoir leurs avantages et leurs inconvénients, donc je ne peux pas commenter lequel est le meilleur, mais beaucoup de gens disent que vernis avec un serveur Apache accordé est idéal pour les sites Web dynamiques.
Tout d'abord, créez votre API RESTful pour Qu'elle soit RESTful. Cela signifie que les utilisateurs authentifiés peuvent également obtenir du contenu mis en cache pour conserver tout l'état dans l'URL dont ils ont besoin pour contenir les détails d'authentification. Bien sûr, le taux de réussite sera plus bas ici, mais il est cacheable.
Avec beaucoup d'utilisateurs connectés, il sera très bénéfique d'avoir une sorte de cache de modèle derrière un cache de page complète car de nombreux modèles sont toujours partagés même si certains ne le sont pas (dans une bonne structure POO).
Puis pour un cache de page complète il est préférable de garder toutes les requêtes hors du serveur web et surtout loin du traitement dynamique à l'étape suivante (dans votre cas Ruby). Le moyen le plus rapide de mettre en cache des pages complètes à partir d'un serveur web normal est toujours un proxy de mise en cache devant les serveurs web.
Le vernis est à mon avis aussi bon et facile que possible, mais certains préfèrent le calmar en effet.
Puisque REST est une chose HTTP, il se peut que la meilleure façon de mettre en cache les requêtes soit d'utiliser la mise en cache HTTP.
Regardez dans l'utilisation D'ETags sur vos réponses, en vérifiant L'ETag dans les demandes de réponse avec '304 non modifié' et en ayant Rack:: Cache pour servir les données mises en cache si les ETags sont les mêmes. Cela fonctionne très bien pour le contenu "public" du contrôle du cache.
Rack:: Cache est mieux configuré pour utiliser memcache pour ses besoins de stockage.
J'ai écrit un billet de blog la semaine dernière sur la façon intéressante Ce Rack:: Cache utilise ETags pour détecter et renvoyer le contenu mis en cache aux nouveaux clients: http://blog.craz8.com/articles/2012/12/19/rack-cache-and-etags-for-even-faster-rails
Même si vous n'utilisez pas Rails, les outils de middleware Rack sont assez bons pour ce genre de choses.
Le Cache Redis est la meilleure option. vérifiez ici.
C'est open source. Avancée clé-valeur cache et magasin.
J'ai utilisé redis avec succès de cette façon dans ma vue REST:
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)
Dans mes mises à jour de modèle D'événement, j'efface tous les caches d'événements. Cela n'est presque jamais effectué (seuls les administrateurs créent des événements, et pas si souvent), donc, j'efface toujours tous les caches d'événements
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)