Applikationsprestanda är avgörande för framgången för din produkt. I en miljö där användarna förväntar sig webbsvarstider på mindre än en sekund kan konsekvenserna av en långsam applikation mätas i dollar och cent. Även om du inte säljer något, förbättrar snabba sidladdningar upplevelsen av att besöka din webbplats.
Allt som händer på servern från det att den tar emot en förfrågan till det att den returnerar ett svar ökar den tid det tar att ladda en sida. Som en allmän tumregel gäller att ju mer bearbetning du kan eliminera på servern, desto snabbare kommer din applikation att fungera. Att cachelagra data efter att den har bearbetats och sedan servera den från cachen nästa gång den efterfrågas är ett sätt att minska stressen på servern. I den här självstudien kommer vi att utforska några av de faktorer som försvårar din applikation, och vi kommer att visa hur man implementerar cachning med Redis för att motverka deras effekter.
Gratis bonus: Klicka här för att få tillgång till en gratis guide för Django Learning Resources (PDF) som visar dig tips och tricks samt vanliga fallgropar att undvika när du bygger Python + Django webbapplikationer.
Vad är Redis?
Redis är ett datastrukturlager i minnet som kan användas som en cachningsmotor. Eftersom den håller data i RAM-minnet kan Redis leverera den mycket snabbt. Redis är inte den enda produkten som vi kan använda för cachning. Memcached är ett annat populärt cachingsystem i minnet, men många är överens om att Redis är överlägset Memcached i de flesta fall. Personligen gillar vi hur enkelt det är att ställa in och använda Redis för andra ändamål, som Redis Queue.
Komma igång
Vi har skapat en exempelapplikation för att introducera dig till konceptet cachning. Vår applikation använder:
- Django (v1.9.8)
- Django Debug Toolbar (v1.4)
- django-redis (v4.4.3)
- Redis (v3.2.0)
Installera appen
Innan du klona förvaret, installera virtualenvwrapper, om du inte redan har det. Det här är ett verktyg som låter dig installera de specifika Python-beroenden som ditt projekt behöver, så att du kan rikta in de versioner och bibliotek som krävs av din app isolerat.
Ändra sedan kataloger till där du förvarar projekt och klona exempelappförrådet. När du är klar, ändra kataloger till det klonade förvaret och skapa sedan en ny virtuell miljö för exempelappen med hjälp av mkvirtualenv
kommando:
$ mkvirtualenv django-redis
(django-redis)$
OBS: Skapa en virtuell miljö med
mkvirtualenv
aktiverar den också.
Installera alla nödvändiga Python-beroenden med pip
, och kolla sedan in följande tagg:
(django-redis)$ git checkout tags/1
Avsluta konfigureringen av exempelappen genom att bygga databasen och fylla i den med exempeldata. Se till att skapa en superanvändare också, så att du kan logga in på administratörssidan. Följ kodexemplen nedan och försök sedan köra appen för att se till att den fungerar korrekt. Besök administratörssidan i webbläsaren för att bekräfta att data har laddats korrekt.
(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
När du har kört Django-appen, gå vidare till Redis-installationen.
Installera Redis
Ladda ner och installera Redis med hjälp av instruktionerna i dokumentationen. Alternativt kan du installera Redis med en pakethanterare som apt-get eller hembrygga beroende på ditt operativsystem.
Kör Redis-servern från ett nytt terminalfönster.
$ redis-server
Starta sedan Redis kommandoradsgränssnitt (CLI) i ett annat terminalfönster och testa att det ansluter till Redis-servern. Vi kommer att använda Redis CLI för att inspektera nycklarna som vi lägger till i cachen.
$ redis-cli ping
PONG
Redis tillhandahåller ett API med olika kommandon som en utvecklare kan använda för att agera på datalagret. Django använder django-redis för att utföra kommandon i Redis.
När vi tittar på vår exempelapp i en textredigerare kan vi se Redis-konfigurationen i settings.py fil. Vi definierar en standardcache med CACHES
inställning, med en inbyggd django-redis cache som vår backend. Redis körs på port 6379 som standard, och vi pekar på den platsen i vår inställning. En sista sak att nämna är att django-redis lägger till nyckelnamn med ett prefix och en version för att hjälpa till att skilja liknande nycklar. I det här fallet har vi definierat prefixet som "exempel".
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"
}
}
OBS :Även om vi har konfigurerat cachebackend, har ingen av vyfunktionerna implementerat cachelagring.
Appprestanda
Som vi nämnde i början av denna handledning, saktar allt som servern gör för att bearbeta en förfrågan ner applikationens laddningstid. Bearbetningsoverheaden för att driva affärslogik och rendering av mallar kan vara betydande. Nätverkslatens påverkar tiden det tar att söka efter en databas. Dessa faktorer spelar in varje gång en klient skickar en HTTP-förfrågan till servern. När användare initierar många förfrågningar per sekund blir effekterna på prestandan märkbara eftersom servern arbetar för att bearbeta dem alla.
När vi implementerar caching låter vi servern behandla en begäran en gång och sedan lagrar vi den i vår cache. Eftersom förfrågningar om samma URL tas emot av vår applikation, hämtar servern resultaten från cachen istället för att bearbeta dem på nytt varje gång. Vanligtvis anger vi en tid för att leva på de cachade resultaten, så att data kan uppdateras med jämna mellanrum, vilket är ett viktigt steg att implementera för att undvika att visa inaktuella data.
Du bör överväga att cachelagra resultatet av en begäran när följande fall är sanna:
- att rendera sidan involverar många databasfrågor och/eller affärslogik,
- sidan besöks ofta av dina användare,
- data är samma för alla användare,
- och data ändras inte ofta.
Börja med att mäta prestanda
Börja med att testa hastigheten på varje sida i din ansökan genom att jämföra hur snabbt din ansökan returnerar ett svar efter att ha mottagit en förfrågan.
För att uppnå detta kommer vi att spränga varje sida med en skur av förfrågningar med loadtest, en HTTP-belastningsgenerator och sedan ägna stor uppmärksamhet åt förfrågningsfrekvensen. Besök länken ovan för att installera. När det är installerat testar du resultaten mot /cookbook/
URL-sökväg:
$ loadtest -n 100 -k http://localhost:8000/cookbook/
Observera att vi behandlar cirka 16 förfrågningar per sekund:
Requests per second: 16
När vi tittar på vad koden gör kan vi fatta beslut om hur vi ska göra ändringar för att förbättra prestandan. Applikationen gör 3 nätverksanrop till en databas med varje begäran till /cookbook/
, och det tar tid för varje samtal att öppna en anslutning och utföra en fråga. Besök /cookbook/
URL i din webbläsare och expandera fliken Django Debug Toolbar för att bekräfta detta beteende. Hitta menyn märkt "SQL" och läs antalet frågor:
kokbok/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'))
kokbok/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()
})
Applikationen återger också en mall med en potentiellt dyr logik.
<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>
Implementera cachelagring
Föreställ dig det totala antalet nätverkssamtal som vår applikation kommer att göra när användare börjar besöka vår webbplats. Om 1 000 användare träffar API:et som hämtar kokboksrecept, kommer vår applikation att fråga databasen 3 000 gånger och en ny mall renderas med varje begäran. Den siffran bara växer när vår ansökan skalas. Lyckligtvis är denna vy en bra kandidat för cachning. Recepten i en kokbok ändras sällan, om någonsin. Dessutom, eftersom att titta på kokböcker är det centrala temat för appen, kommer API:et som hämtar recepten garanterat att anropas ofta.
I exemplet nedan ändrar vi visningsfunktionen för att använda caching. När funktionen körs kontrollerar den om vynyckeln finns i cachen. Om nyckeln finns hämtar appen data från cachen och returnerar den. Om inte, frågar Django databasen och lagrar sedan resultatet i cachen med vynyckeln. Första gången den här funktionen körs kommer Django att fråga databasen och rendera mallen och sedan ringa ett nätverk till Redis för att lagra data i cachen. Varje efterföljande anrop till funktionen kommer helt att kringgå databasen och affärslogiken och kommer att fråga Redis-cachen.
example/settings.py
# Cache time to live is 15 minutes.
CACHE_TTL = 60 * 15
kokbok/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()
})
Observera att vi har lagt till @cache_page()
dekoratör till utsiktsfunktionen, tillsammans med en tid att leva. Besök /cookbook/
URL igen och undersök Django Debug Toolbar. Vi ser att 3 databasförfrågningar görs och 3 anrop görs till cachen för att leta efter nyckeln och sedan spara den. Django sparar två nycklar (1 nyckel för sidhuvudet och 1 nyckel för det renderade sidinnehållet). Ladda om sidan och observera hur sidaktiviteten förändras. Andra gången görs 0 anrop till databasen och 2 anrop till cachen. Vår sida serveras nu från cachen!
När vi kör våra prestandatester igen ser vi att vår applikation laddas snabbare.
$ loadtest -n 100 -k http://localhost:8000/cookbook/
Cachning förbättrade den totala belastningen och vi löser nu 21 förfrågningar per sekund, vilket är 5 mer än vår baslinje:
Requests per second: 21
Inspektera Redis med CLI
Vid det här laget kan vi använda Redis CLI för att titta på vad som lagras på Redis-servern. På Redis-kommandoraden anger du keys *
kommando, som returnerar alla nycklar som matchar alla mönster. Du bör se en nyckel som heter "example:1:views.decorators.cache.cache_page". Kom ihåg att "exempel" är vårt nyckelprefix, "1" är versionen och "views.decorators.cache.cache_page" är namnet som Django ger nyckeln. Kopiera nyckelnamnet och ange det med get
kommando. Du bör se den renderade HTML-strängen.
$ 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"
OBS: Kör
flushall
kommandot på Redis CLI för att rensa alla nycklar från datalagret. Sedan kan du gå igenom stegen i den här handledningen igen utan att behöva vänta på att cachen ska ta slut.
Avslutning
Att bearbeta HTTP-förfrågningar är kostsamt, och den kostnaden ökar när din applikation växer i popularitet. I vissa fall kan du avsevärt minska mängden bearbetning som din server gör genom att implementera cachelagring. Denna handledning berörde grunderna för cachelagring i Django med Redis, men den skummade bara ytan av ett komplext ämne.
Att implementera cachning i en robust applikation har många fallgropar och gotchas. Det är svårt att kontrollera vad som cachelagras och hur länge. Cache-invalidering är en av de svåra sakerna inom datavetenskap. Att säkerställa att privat data endast kan nås av de avsedda användarna är ett säkerhetsproblem och måste hanteras mycket försiktigt vid cachning.
Gratis bonus: Klicka här för att få tillgång till en gratis guide för Django Learning Resources (PDF) som visar dig tips och tricks samt vanliga fallgropar att undvika när du bygger Python + Django webbapplikationer.
Lek med källkoden i exempelapplikationen och när du fortsätter att utveckla med Django, kom ihåg att alltid ha prestanda i åtanke.