sql >> Database >  >> NoSQL >> Redis

Caching in Django met Redis

Applicatieprestaties zijn essentieel voor het succes van uw product. In een omgeving waar gebruikers responstijden van websites verwachten van minder dan een seconde, kunnen de gevolgen van een trage applicatie worden gemeten in dollars en centen. Zelfs als u niets verkoopt, verbetert het snel laden van pagina's de ervaring van het bezoeken van uw site.

Alles wat er op de server gebeurt tussen het moment dat het een verzoek ontvangt en het moment dat het een reactie retourneert, verhoogt de hoeveelheid tijd die nodig is om een ​​pagina te laden. Als algemene vuistregel geldt:hoe meer verwerking u op de server kunt elimineren, hoe sneller uw toepassing zal presteren. Het cachen van gegevens nadat het is verwerkt en het vervolgens uit de cache serveren de volgende keer dat het wordt gevraagd, is een manier om de server te ontlasten. In deze zelfstudie zullen we enkele van de factoren onderzoeken die uw toepassing doen vastlopen, en we zullen laten zien hoe u caching kunt implementeren met Redis om hun effecten tegen te gaan.

Gratis bonus: Klik hier om toegang te krijgen tot een gratis Django Learning Resources Guide (PDF) met tips en trucs en veelvoorkomende valkuilen die je moet vermijden bij het bouwen van Python + Django-webapplicaties.


Wat is Redis?

Redis is een in-memory datastructuuropslag die kan worden gebruikt als een caching-engine. Omdat het gegevens in RAM houdt, kan Redis het zeer snel leveren. Redis is niet het enige product dat we kunnen gebruiken voor caching. Memcached is een ander populair in-memory caching-systeem, maar veel mensen zijn het erover eens dat Redis in de meeste gevallen superieur is aan Memcached. Persoonlijk vinden we het leuk hoe gemakkelijk het is om Redis in te stellen en te gebruiken voor andere doeleinden, zoals Redis Queue.



Aan de slag

We hebben een voorbeeldtoepassing gemaakt om u kennis te laten maken met het concept van caching. Onze applicatie gebruikt:

  • Django (v1.9.8)
  • Django Debug-werkbalk (v1.4)
  • django-redis (v4.4.3)
  • Redis (v3.2.0)

Installeer de app

Installeer virtualenvwrapper voordat je de repository kloont, als je die nog niet hebt. Dit is een tool waarmee u de specifieke Python-afhankelijkheden kunt installeren die uw project nodig heeft, zodat u zich kunt richten op de versies en bibliotheken die vereist zijn voor uw app afzonderlijk.

Wijzig vervolgens de mappen naar waar u projecten bewaart en kloon de voorbeeld-app-repository. Als u klaar bent, wijzigt u de mappen naar de gekloonde repository en maakt u vervolgens een nieuwe virtuele omgeving voor de voorbeeld-app met behulp van de mkvirtualenv commando:

$ mkvirtualenv django-redis
(django-redis)$

OPMERKING: Een virtuele omgeving maken met mkvirtualenv activeert het ook.

Installeer alle vereiste Python-afhankelijkheden met pip , en check dan de volgende tag af:

(django-redis)$ git checkout tags/1

Voltooi het instellen van de voorbeeld-app door de database te bouwen en deze te vullen met voorbeeldgegevens. Zorg ervoor dat u ook een superuser aanmaakt, zodat u kunt inloggen op de beheerderssite. Volg de onderstaande codevoorbeelden en probeer vervolgens de app uit te voeren om te controleren of deze correct werkt. Bezoek de beheerderspagina in de browser om te bevestigen dat de gegevens correct zijn geladen.

(django-redis)$ python manage.py makemigrations cookbook
(django-redis)$ python manage.py migrate
(django-redis)$ python manage.py createsuperuser
(django-redis)$ python manage.py loaddata cookbook/fixtures/cookbook.json
(django-redis)$ python manage.py runserver

Zodra je de Django-app hebt uitgevoerd, ga je naar de Redis-installatie.



Installeer Redis

Download en installeer Redis volgens de instructies in de documentatie. Als alternatief kunt u Redis installeren met behulp van een pakketbeheerder zoals apt-get of homebrew afhankelijk van uw besturingssysteem.

Voer de Redis-server uit vanuit een nieuw terminalvenster.

$ redis-server

Start vervolgens de Redis-opdrachtregelinterface (CLI) in een ander terminalvenster en test of deze verbinding maakt met de Redis-server. We zullen de Redis CLI gebruiken om de sleutels te inspecteren die we aan de cache toevoegen.

$ redis-cli ping
PONG

Redis biedt een API met verschillende opdrachten die een ontwikkelaar kan gebruiken om op de gegevensopslag te reageren. Django gebruikt django-redis om opdrachten in Redis uit te voeren.

Als we naar onze voorbeeld-app in een teksteditor kijken, kunnen we de Redis-configuratie zien in de settings.py het dossier. We definiëren een standaardcache met de CACHES instelling, met behulp van een ingebouwde django-redis cache als onze backend. Redis draait standaard op poort 6379 en we verwijzen naar die locatie in onze instelling. Een laatste ding om te vermelden is dat django-redis voegt sleutelnamen toe met een voorvoegsel en een versie om vergelijkbare sleutels te helpen onderscheiden. In dit geval hebben we het voorvoegsel "voorbeeld" gedefinieerd.

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient"
        },
        "KEY_PREFIX": "example"
    }
}

OPMERKING :Hoewel we de cache-backend hebben geconfigureerd, heeft geen van de weergavefuncties caching geïmplementeerd.




App-prestaties

Zoals we aan het begin van deze tutorial vermeldden, vertraagt ​​alles wat de server doet om een ​​verzoek te verwerken, de laadtijd van de applicatie. De verwerkingsoverhead van het uitvoeren van bedrijfslogica en weergavesjablonen kan aanzienlijk zijn. Netwerklatentie is van invloed op de tijd die nodig is om een ​​database op te vragen. Deze factoren spelen een rol telkens wanneer een client een HTTP-verzoek naar de server stuurt. Wanneer gebruikers veel verzoeken per seconde initiëren, worden de effecten op de prestaties merkbaar terwijl de server eraan werkt om ze allemaal te verwerken.

Wanneer we caching implementeren, laten we de server een verzoek één keer verwerken en slaan we het op in onze cache. Omdat verzoeken om dezelfde URL door onze applicatie worden ontvangen, haalt de server de resultaten uit de cache in plaats van ze elke keer opnieuw te verwerken. Meestal stellen we een tijdsduur in voor de resultaten in de cache, zodat de gegevens periodiek kunnen worden vernieuwd, wat een belangrijke stap is om te implementeren om te voorkomen dat verouderde gegevens worden weergegeven.

Overweeg om het resultaat van een verzoek in de cache op te slaan als de volgende gevallen van toepassing zijn:

  • het weergeven van de pagina brengt veel databasequery's en/of bedrijfslogica met zich mee,
  • de pagina wordt vaak bezocht door uw gebruikers,
  • de gegevens zijn voor elke gebruiker hetzelfde,
  • en de gegevens veranderen niet vaak.

Begin met het meten van prestaties

Begin met het testen van de snelheid van elke pagina in uw applicatie door te benchmarken hoe snel uw applicatie een reactie retourneert na ontvangst van een verzoek.

Om dit te bereiken, knallen we elke pagina met een reeks verzoeken met behulp van loadtest, een HTTP-laadgenerator, en letten we vervolgens goed op de verzoeksnelheid. Bezoek de link hierboven om te installeren. Eenmaal geïnstalleerd, test u de resultaten tegen de /cookbook/ URL-pad:

$ loadtest -n 100 -k  http://localhost:8000/cookbook/

Merk op dat we ongeveer 16 verzoeken per seconde verwerken:

Requests per second: 16

Als we kijken naar wat de code doet, kunnen we beslissingen nemen over hoe we wijzigingen kunnen aanbrengen om de prestaties te verbeteren. De applicatie maakt 3 netwerkaanroepen naar een database met elk verzoek aan /cookbook/ en het kost tijd voor elke oproep om een ​​verbinding te openen en een query uit te voeren. Bezoek de /cookbook/ URL in uw browser en vouw het tabblad Django Debug Toolbar uit om dit gedrag te bevestigen. Zoek het menu met het label "SQL" en lees het aantal zoekopdrachten:

cookbook/services.py

from cookbook.models import Recipe


def get_recipes():
    # Queries 3 tables: cookbook_recipe, cookbook_ingredient,
    # and cookbook_food.
    return list(Recipe.objects.prefetch_related('ingredient_set__food'))

kookboek/views.py

from django.shortcuts import render
from cookbook.services import get_recipes


def recipes_view(request):
    return render(request, 'cookbook/recipes.html', {
        'recipes': get_recipes()
    })

De applicatie geeft ook een sjabloon weer met potentieel dure logica.

<html>
<head>
  <title>Recipes</title>
</head>
<body>
{% for recipe in recipes %}
  <h1>{{ recipe.name }}</h1>
    <p>{{ recipe.desc }}</p>
  <h2>Ingredients</h2>
  <ul>
    {% for ingredient in recipe.ingredient_set.all %}
    <li>{{ ingredient.desc }}</li>
    {% endfor %}
  </ul>
  <h2>Instructions</h2>
    <p>{{ recipe.instructions }}</p>
{% endfor %}
</body>
</html>


Caching implementeren

Stelt u zich het totale aantal netwerkoproepen voor dat onze applicatie zal doen wanneer gebruikers onze site beginnen te bezoeken. Als 1.000 gebruikers de API gebruiken die kookboekrecepten ophaalt, zal onze applicatie de database 3.000 keer doorzoeken en zal er bij elk verzoek een nieuwe sjabloon worden weergegeven. Dat aantal groeit alleen maar naarmate onze applicatie groter wordt. Gelukkig is deze weergave een geweldige kandidaat voor caching. De recepten in een kookboek veranderen zelden of nooit. Omdat het bekijken van kookboeken het centrale thema van de app is, wordt gegarandeerd dat de API die de recepten ophaalt, vaak wordt aangeroepen.

In het onderstaande voorbeeld passen we de weergavefunctie aan om caching te gebruiken. Wanneer de functie wordt uitgevoerd, wordt gecontroleerd of de view-sleutel zich in de cache bevindt. Als de sleutel bestaat, haalt de app de gegevens op uit de cache en retourneert deze. Als dat niet het geval is, doorzoekt Django de database en slaat het resultaat vervolgens op in de cache met de view-sleutel. De eerste keer dat deze functie wordt uitgevoerd, zal Django de database doorzoeken en de sjabloon renderen, en vervolgens een netwerkaanroep doen naar Redis om de gegevens in de cache op te slaan. Elke volgende aanroep van de functie zal de database en bedrijfslogica volledig omzeilen en zal de Redis-cache opvragen.

voorbeeld/settings.py

# Cache time to live is 15 minutes.
CACHE_TTL = 60 * 15

kookboek/views.py

from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.shortcuts import render
from django.views.decorators.cache import cache_page
from cookbook.services import get_recipes

CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)


@cache_page(CACHE_TTL)
def recipes_view(request):
    return render(request, 'cookbook/recipes.html', {
        'recipes': get_recipes()
    })

Merk op dat we de @cache_page() . hebben toegevoegd decorateur naar de weergavefunctie, samen met een tijd om te leven. Bezoek de /cookbook/ URL opnieuw en onderzoek de Django Debug Toolbar. We zien dat er 3 databasequery's worden gemaakt en 3 oproepen naar de cache worden gedaan om de sleutel te controleren en deze vervolgens op te slaan. Django bewaart twee sleutels (1 sleutel voor de kop en 1 sleutel voor de weergegeven pagina-inhoud). Laad de pagina opnieuw en kijk hoe de pagina-activiteit verandert. De tweede keer worden er 0 aanroepen gedaan naar de database en 2 aanroepen naar de cache. Onze pagina wordt nu vanuit de cache bediend!

Wanneer we onze prestatietests opnieuw uitvoeren, zien we dat onze applicatie sneller laadt.

$ loadtest -n 100 -k  http://localhost:8000/cookbook/

Caching verbeterde de totale belasting en we lossen nu 21 verzoeken per seconde op, wat 5 meer is dan onze basislijn:

Requests per second: 21


Redis inspecteren met de CLI

Op dit punt kunnen we de Redis CLI gebruiken om te kijken wat er op de Redis-server wordt opgeslagen. Voer in de Redis-opdrachtregel de keys * . in commando, dat alle sleutels retourneert die overeenkomen met een patroon. U zou een sleutel moeten zien met de naam "example:1:views.decorators.cache.cache_page". Onthoud dat "voorbeeld" ons sleutelvoorvoegsel is, "1" de versie is en "views.decorators.cache.cache_page" de naam is die Django de sleutel geeft. Kopieer de sleutelnaam en voer deze in met de get opdracht. U zou de weergegeven HTML-tekenreeks moeten zien.

$ redis-cli -n 1
127.0.0.1:6379[1]> keys *
1) "example:1:views.decorators.cache.cache_header"
2) "example:1:views.decorators.cache.cache_page"
127.0.0.1:6379[1]> get "example:1:views.decorators.cache.cache_page"

OPMERKING: Voer de flushall uit opdracht op de Redis CLI om alle sleutels uit het gegevensarchief te wissen. Vervolgens kunt u de stappen in deze zelfstudie opnieuw doorlopen zonder dat u hoeft te wachten tot de cache is verlopen.




Afronding

Het verwerken van HTTP-verzoeken is kostbaar en die kosten lopen op naarmate uw toepassing in populariteit groeit. In sommige gevallen kunt u de hoeveelheid verwerking die uw server doet aanzienlijk verminderen door caching te implementeren. Deze tutorial ging in op de basisprincipes van caching in Django met Redis, maar het ging slechts over de oppervlakte van een complex onderwerp.

Het implementeren van caching in een robuuste applicatie kent veel valkuilen en valkuilen. Het is moeilijk om te bepalen wat in de cache wordt opgeslagen en voor hoe lang. Cache-invalidatie is een van de moeilijkste dingen in de informatica. Ervoor zorgen dat privégegevens alleen toegankelijk zijn voor de beoogde gebruikers is een beveiligingsprobleem en moet zeer zorgvuldig worden behandeld bij het cachen.

Gratis bonus: Klik hier om toegang te krijgen tot een gratis Django Learning Resources Guide (PDF) met tips en trucs en veelvoorkomende valkuilen die je moet vermijden bij het bouwen van Python + Django-webapplicaties.

Speel wat met de broncode in de voorbeeldtoepassing en denk eraan om, terwijl je doorgaat met ontwikkelen met Django, altijd de prestaties in gedachten te houden.



  1. MongoDB-query's met null-waarde

  2. Fatale fout:Klasse 'MongoDB\Driver\Manager' niet gevonden

  3. Filter de subarray van een array op enkele criteria

  4. Mongodb Aggregation Framework:gebruikt $group index?