web-dev-qa-db-fra.com

Meilleure façon de mettre en cache les résultats de l'API RESTful des appels GET

Je réfléchis à la meilleure façon de créer une couche de cache en avant ou en tant que première couche pour les requêtes GET à mon API RESTful (écrite en Ruby).

Toutes les demandes ne peuvent pas être mises en cache, car même pour certaines demandes GET, l'API doit valider l'utilisateur/l'application demandeuse. Cela signifie que je dois configurer quelle demande peut être mise en cache et combien de temps chaque réponse mise en cache est valide. Pour quelques cas, j'ai besoin d'un délai d'expiration très court, par ex. 15 ans et moins. Et je devrais pouvoir laisser les entrées de cache expirer 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 (même avant le routage), logique de cache par moi-même (pour avoir toutes les options de configuration en main), réponses et date d'expiration stockées dans Memcached

  • un proxy de serveur web (hautement configurable), peut-être quelque chose comme Squid mais je n'ai jamais utilisé de 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 des 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 idé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 demandes.

Si quelqu'un a un indice ou de bonnes sources à lire sur ce sujet, faites-le moi savoir.

22
maddin2code

memcached est une excellente option, et je vois que vous l'avez déjà mentionné comme une option possible. Redis semble également être loué comme une autre option à ce niveau.

Au niveau de l'application, en termes d'approche plus granulaire du cache 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 laisser tomber la réponse objets en session afin de pouvoir les réutiliser par rapport à un autre appel de repos http et au codage approprié.

Maintenant, les gens discutent du vernis contre le calmar, et les deux semblent avoir leurs avantages et leurs inconvénients, donc je ne peux pas dire lequel est le meilleur, mais beaucoup de gens disent que Varnish avec un serveur Apache réglé est idéal pour les sites Web dynamiques.

3
jsteinmann

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 afin de conserver tous les états dans l'URL dont ils ont besoin pour contenir les détails d'authentification. Bien sûr, le taux de réussite sera plus faible ici, mais il peut être mis en cache.

Avec beaucoup d'utilisateurs connectés, il sera très avantageux 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 OOP = structure).

Ensuite, pour un cache de page complète, vous êtes mieux de garder toutes les demandes 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 en effet Squid.

5
Clarence

Puisque REST est une chose HTTP, il se pourrait que la meilleure façon de mettre en cache les requêtes soit d'utiliser la mise en cache HTTP.

Envisagez d'utiliser des ETags dans vos réponses, de vérifier l'ETag dans les demandes de réponse avec "304 non modifié" et d'avoir 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" de contrôle du cache.

Rack :: Cache est mieux configuré pour utiliser memcache pour ses besoins de stockage.

J'ai écrit un article de blog la semaine dernière sur la façon intéressante dont Rack :: Cache utilise ETags pour détecter et renvoyer le contenu mis en cache à de nouveaux clients: http://blog.craz8.com/articles/2012/12/19/ rack-cache-et-etags-pour-encore-plus-rapides-Rails

Même si vous n'utilisez pas Rails, les outils middleware Rack sont assez bons pour ce genre de choses.

4
Tom Fakes

Redis Cache est la meilleure option. vérifiez ici.

C'est open source. Cache et stockage avancés de valeurs-clés.

2
Jill

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)
0
Comp Guy