Производителността на приложението е жизненоважна за успеха на вашия продукт. В среда, в която потребителите очакват време за реакция на уебсайта по-малко от секунда, последиците от бавното приложение могат да се измерват в долари и центове. Дори и да не продавате нищо, бързото зареждане на страници подобрява изживяването при посещение на вашия сайт.
Всичко, което се случва на сървъра между момента на получаване на заявка до момента, в който връща отговор, увеличава времето, необходимо за зареждане на страница. Като общо правило, колкото повече обработка можете да премахнете на сървъра, толкова по-бързо ще се изпълнява вашето приложение. Кеширането на данни, след като са били обработени и след това обслужването им от кеша следващия път, когато бъдат поискани, е един от начините за облекчаване на стреса върху сървъра. В този урок ще проучим някои от факторите, които блокират вашето приложение, и ще демонстрираме как да приложите кеширане с Redis, за да противодействате на ефектите им.
Безплатен бонус: Щракнете тук, за да получите достъп до безплатно ръководство за учебни ресурси на Django (PDF), което ви показва съвети и трикове, както и често срещани клопки, които трябва да избягвате, когато създавате уеб приложения на Python + Django.
Какво е Redis?
Redis е хранилище за структура на данни в паметта, което може да се използва като кеширащо устройство. Тъй като съхранява данни в RAM, Redis може да ги достави много бързо. Redis не е единственият продукт, който можем да използваме за кеширане. Memcached е друга популярна система за кеширане в паметта, но много хора са съгласни, че Redis е по-добър от Memcached при повечето обстоятелства. Лично на нас ни харесва колко лесно е да настроите и използвате Redis за други цели, като Redis Queue.
Първи стъпки
Създадохме примерно приложение, за да ви запознаем с концепцията за кеширане. Нашето приложение използва:
- Django (v1.9.8)
- Django Debug Toolbar (v1.4)
- django-redis (v4.4.3)
- Redis (v3.2.0)
Инсталирайте приложението
Преди да клонирате хранилището, инсталирайте virtualenvwrapper, ако все още го нямате. Това е инструмент, който ви позволява да инсталирате специфичните зависимости на Python, от които се нуждае вашият проект, като ви позволява да насочвате изолирано към версиите и библиотеките, изисквани от приложението ви.
След това променете директориите към мястото, където съхранявате проекти, и клонирайте хранилището за примерни приложения. След като приключите, променете директориите към клонираното хранилище и след това направете нова виртуална среда за примерното приложение, като използвате mkvirtualenv
команда:
$ mkvirtualenv django-redis
(django-redis)$
ЗАБЕЛЕЖКА: Създаване на виртуална среда с
mkvirtualenv
също го активира.
Инсталирайте всички необходими зависимости на Python с pip
, след което проверете следния маркер:
(django-redis)$ git checkout tags/1
Завършете настройката на примерното приложение, като изградите базата данни и я попълните с примерни данни. Не забравяйте да създадете и суперпотребител, за да можете да влезете в администраторския сайт. Следвайте примерите за код по-долу и след това опитайте да стартирате приложението, за да се уверите, че работи правилно. Посетете страницата на администратора в браузъра, за да потвърдите, че данните са заредени правилно.
(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
След като стартирате приложението Django, преминете към инсталацията на Redis.
Инсталирайте Redis
Изтеглете и инсталирайте Redis, като използвате инструкциите, предоставени в документацията. Като алтернатива можете да инсталирате Redis с помощта на мениджър на пакети като apt-get или homebrew в зависимост от вашата ОС.
Стартирайте Redis сървъра от нов прозорец на терминала.
$ redis-server
След това стартирайте интерфейса на командния ред на Redis (CLI) в различен терминален прозорец и проверете дали се свързва със сървъра на Redis. Ще използваме Redis CLI за проверка на ключовете, които добавяме към кеша.
$ redis-cli ping
PONG
Redis предоставя API с различни команди, които разработчикът може да използва, за да действа върху хранилището на данни. Django използва django-redis за изпълнение на команди в Redis.
Разглеждайки нашето примерно приложение в текстов редактор, можем да видим конфигурацията на Redis в settings.py файл. Дефинираме кеш по подразбиране с CACHES
настройка, като използвате вграден django-redis кеш като наш бекенд. Redis работи на порт 6379 по подразбиране и ние сочим към това място в нашата настройка. Последното нещо, което трябва да споменем е, че django-redis добавя имена на ключове с префикс и версия, за да помогне за разграничаването на подобни ключове. В този случай сме дефинирали префикса като „пример“.
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"
}
}
ЗАБЕЛЕЖКА :Въпреки че сме конфигурирали задната част на кеша, никоя от функциите за изглед не е внедрила кеширане.
Ефективност на приложението
Както споменахме в началото на този урок, всичко, което сървърът прави, за да обработи заявка, забавя времето за зареждане на приложението. Разходите за обработка на изпълнение на бизнес логика и шаблони за изобразяване могат да бъдат значителни. Закъснението на мрежата влияе върху времето, необходимо за запитване на база данни. Тези фактори влизат в игра всеки път, когато клиент изпрати HTTP заявка до сървъра. Когато потребителите инициират много заявки в секунда, ефектите върху производителността стават забележими, тъй като сървърът работи, за да ги обработи всички.
Когато внедряваме кеширане, оставяме сървъра да обработи заявка веднъж и след това я съхраняваме в нашия кеш. Тъй като заявките за същия URL се получават от нашето приложение, сървърът изтегля резултатите от кеша, вместо да ги обработва всеки път наново. Обикновено задаваме време за използване на кешираните резултати, така че данните да могат периодично да се опресняват, което е важна стъпка за прилагане, за да се избегне обслужването на остарели данни.
Трябва да помислите за кеширане на резултата от заявка, когато са верни следните случаи:
- изобразяването на страницата включва много заявки за база данни и/или бизнес логика,
- страницата се посещава често от вашите потребители,
- данните са еднакви за всеки потребител,
- и данните не се променят често.
Започнете с измерване на производителността
Започнете с тестване на скоростта на всяка страница във вашето приложение, като сравните колко бързо приложението ви връща отговор след получаване на заявка.
За да постигнем това, ще разбием всяка страница с поредица от заявки, използвайки loadtest, HTTP генератор на натоварване и след това ще обърнем голямо внимание на скоростта на заявки. Посетете връзката по-горе, за да инсталирате. След като инсталирате, тествайте резултатите спрямо /cookbook/
URL път:
$ loadtest -n 100 -k http://localhost:8000/cookbook/
Забележете, че обработваме около 16 заявки в секунда:
Requests per second: 16
Когато погледнем какво прави кодът, можем да вземем решения как да правим промени, за да подобрим производителността. Приложението прави 3 мрежови обаждания към база данни с всяка заявка към /cookbook/
, и е необходимо време за всяко повикване, за да отвори връзка и да изпълни заявка. Посетете /cookbook/
URL във вашия браузър и разгънете раздела Django Debug Toolbar, за да потвърдите това поведение. Намерете менюто с надпис „SQL“ и прочетете броя на заявките:
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'))
cookbook/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()
})
Приложението също така изобразява шаблон с някаква потенциално скъпа логика.
<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>
Внедряване на кеширане
Представете си общия брой мрежови обаждания, които нашето приложение ще направи, когато потребителите започнат да посещават нашия сайт. Ако 1000 потребители ударят API, който извлича рецепти от готварска книга, тогава нашето приложение ще запита базата данни 3000 пъти и нов шаблон ще бъде изобразен с всяка заявка. Този брой нараства само с увеличаването на нашето приложение. За щастие този изглед е чудесен кандидат за кеширане. Рецептите в готварска книга рядко се променят, ако изобщо се променят. Освен това, тъй като разглеждането на готварски книги е централната тема на приложението, API, който извлича рецептите, гарантирано ще се извиква често.
В примера по-долу модифицираме функцията за преглед, за да използва кеширане. Когато функцията се изпълнява, тя проверява дали ключът за преглед е в кеша. Ако ключът съществува, приложението извлича данните от кеша и ги връща. Ако не, Django прави заявки към базата данни и след това съхранява резултата в кеша с ключа view. Първият път, когато се стартира тази функция, Django ще потърси базата данни и ще изобрази шаблона, а след това ще направи и мрежово повикване към Redis, за да съхрани данните в кеша. Всяко следващо извикване на функцията напълно ще заобиколи базата данни и бизнес логиката и ще отправя заявка към кеша на Redis.
example/settings.py
# Cache time to live is 15 minutes.
CACHE_TTL = 60 * 15
cookbook/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()
})
Забележете, че сме добавили @cache_page()
декоратор към функцията за преглед, заедно с време за живот. Посетете /cookbook/
URL отново и разгледайте лентата с инструменти за отстраняване на грешки на Django. Виждаме, че са направени 3 заявки към базата данни и са направени 3 извиквания към кеша, за да се провери за ключа и след това да се запише. Django запазва два ключа (1 ключ за заглавката и 1 ключ за изобразеното съдържание на страницата). Презаредете страницата и наблюдавайте как се променя активността на страницата. Вторият път се извършват 0 повиквания към базата данни и 2 извиквания към кеша. Нашата страница вече се обслужва от кеша!
Когато стартираме отново нашите тестове за производителност, виждаме, че нашето приложение се зарежда по-бързо.
$ loadtest -n 100 -k http://localhost:8000/cookbook/
Кеширането подобри общото натоварване и сега разрешаваме 21 заявки в секунда, което е с 5 повече от нашето изходно ниво:
Requests per second: 21
Проверка на Redis с CLI
В този момент можем да използваме Redis CLI, за да разгледаме какво се съхранява на сървъра Redis. В командния ред на Redis въведете keys *
команда, която връща всички ключове, съответстващи на всеки шаблон. Трябва да видите ключ, наречен „example:1:views.decorators.cache.cache_page“. Не забравяйте, че „example“ е нашият ключов префикс, „1“ е версията, а „views.decorators.cache.cache_page“ е името, което Django дава ключа. Копирайте името на ключа и го въведете с get
команда. Трябва да видите изобразения HTML низ.
$ 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"
ЗАБЕЛЕЖКА: Стартирайте
flushall
команда на Redis CLI, за да изчистите всички ключове от хранилището на данни. След това можете отново да преминете през стъпките в този урок, без да се налага да чакате изтичането на кеша.
Заключение
Обработката на HTTP заявки е скъпа и тази цена се увеличава с нарастването на популярността на приложението ви. В някои случаи можете значително да намалите обема на обработката, която вашият сървър извършва чрез внедряване на кеширане. Този урок засегна основите на кеширането в Django с Redis, но само бегло разгледа повърхността на сложна тема.
Внедряването на кеширане в стабилно приложение има много клопки и проблеми. Контролирането на това, което се кешира и за колко време е трудно. Невалидирането на кеша е едно от трудните неща в компютърните науки. Осигуряването на достъп до лични данни само от предназначените им потребители е проблем със сигурността и трябва да се работи много внимателно при кеширане.
Безплатен бонус: Щракнете тук, за да получите достъп до безплатно ръководство за учебни ресурси на Django (PDF), което ви показва съвети и трикове, както и често срещани клопки, които трябва да избягвате, когато създавате уеб приложения на Python + Django.
Поиграйте си с изходния код в примерното приложение и докато продължавате да развивате с Django, не забравяйте винаги да имате предвид производителността.