автор:HelloGitHub — Искатель мечты
В настоящее время пользователю в основном необходимо запрашивать базу данных для работы интерфейса. Для получения списка статей требуется запрос из базы данных, для получения отдельной статьи требуется запрос из базы данных, а для получения списка комментариев также требуется запрос данных. Однако для многих ресурсов блога их контент редко обновляется в течение определенного периода времени. Например, детали статьи, после публикации статьи, если содержимое не будет изменено, содержимое не изменится. Также есть список комментариев, который не меняется, если никто не публикует новый комментарий.
Чтобы знать, что операция запроса к базе данных относительно медленная, а чтение данных непосредственно из памяти происходит намного быстрее, поэтому возникла система кэширования. Кэшировать данные, которые изменяются реже, в память. Данные в памяти эквивалентны копии в базе данных. Когда пользователи запрашивают данные, они не запрашивают данные из базы данных, а считывают их непосредственно из кэша. Когда данные в базе данных изменяются, затем обновите кеш, так что производительность запроса данных значительно повышается.
Конечно, производительность базы данных не так плоха, как говорят, для большинства личных блогов с небольшим доступом достаточно любой реляционной базы данных. Но мы изучаем django-rest-framework не только для ведения блога, может быть вы работаете на системе с очень большим трафиком, и кеширование в это время незаменимо.
Определить интерфейс для кэширования
Давайте сначала разберем наши существующие интерфейсы и посмотрим, какие интерфейсы нужно кэшировать:
имя интерфейса | URL | нужно кэшировать |
---|---|---|
Список статей | /api/posts/ | да |
Детали статьи | /api/posts/:id/ | да |
Список категорий | /categories/ | да |
список тегов | /tags/ | да |
Список архивных дат | /posts/archive/dates/ | да |
список комментариев | /api/posts/:id/comments/ | да |
Результаты поиска статьи | /api/search/ | нет |
Дополнительные инструкции
- Список статей: требуется кеш, но если какие-либо статьи будут изменены, добавлены или удалены, кеш должен быть аннулирован.
- Сведения о статье: требуется кеш, но если содержание статьи изменено или удалено, кеш должен быть аннулирован.
- Категория, метка, дата архива: Кэш возможен, но также необходимо сделать кеш недействительным при изменении соответствующих данных.
- Список комментариев: можно кэшировать, и кэш должен быть аннулирован при добавлении или удалении комментариев.
- Интерфейс поиска: Поскольку ключевые слова поиска разнообразны, результаты поиска общих ключевых слов поиска могут быть кэшированы, но как определить общие ключевые слова поиска является сложной проблемой оптимизации, и мы не делаем никакого кэширования здесь.
Настроить кеш
Django предоставляет нам готовый кеширующий фреймворк, который абстрагирует операцию кеширования и предоставляет унифицированный интерфейс для чтения и записи кешей. Независимо от того, какая служба кэширования используется на нижнем уровне (например, обычно используемые Redis, Memcached, файловая система и т. д.), для приложения верхнего уровня логика работы и вызывающий интерфейс одинаковы.
При настройке кеширования django самое главное — выбрать сервис кеширования, то есть, где хранятся и читаются кешированные результаты. В этом проекте мы решили использовать службу кэширования локальной памяти (Local Memory) в среде разработки, а кэш Redis использовать в онлайн-среде.
Конфигурация среды разработки
Добавьте следующие элементы конфигурации в файл конфигурации settings/local.py среды разработки, чтобы включить службу кэширования локальной памяти.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}
Конфигурация онлайн-среды
Онлайн-среда использует службу кеша Redis. В Django нет встроенной поддержки службы кеша Redis, но, конечно, нет недостатка в поддержке сторонних библиотек для Redis. Мы выбираем django-redis-cache и устанавливаем его первым. :
$ pipenv install django-redis-cache
Затем добавьте следующую конфигурацию в файл конфигурации онлайн-среды проекта settings/production.py:
CACHES = {
"default": {
"BACKEND": "redis_cache.RedisCache",
"LOCATION": "redis://:UJaoRZlNrH40BDaWU6fi@redis:6379/0",
"OPTIONS": {
"CONNECTION_POOL_CLASS": "redis.BlockingConnectionPool",
"CONNECTION_POOL_CLASS_KWARGS": {"max_connections": 50, "timeout": 20},
"MAX_CONNECTIONS": 1000,
"PICKLE_VERSION": -1,
},
},
}
Таким образом, функция кэширования django включена. Чтобы узнать, как запустить службу Redis, обратитесь к разделу службы Redis в конце руководства.
drf-extensions Cache
Фреймворк кэширования django является относительно низкоуровневым. Основанный на каркасе кэширования django, drf-extensions инкапсулирует больше вспомогательных функций и классов, связанных с кэшированием, для django-rest-framework. Мы будем использовать эту стороннюю библиотеку, чтобы значительно упростить реализацию кэширования логика. .
Сначала установите его:
$ pipenv install drf-extensions
Итак, какие вспомогательные функции и классы предоставляет drf-extensions для кэширования? Основные из них, которые нам нужно использовать:
KeyConstructor
Его можно понимать как класс генерации ключей кэша. Сначала рассмотрим логику кеширования интерфейса API, псевдокод выглядит следующим образом:
给定一个 URL, 尝试从缓存中查找这个 URL 接口的响应结果
if 结果在缓存中:
return 缓存中的结果
else:
生成响应结果
将响应结果存入缓存 (以便下一次查询)
return 生成的响应结果
Кэшированные результаты хранятся в виде пар ключ-значение Ключевым моментом здесь является то, что при сохранении или запросе кешированных результатов необходимо сгенерировать соответствующий ключ. Например, мы можем использовать URL-адрес запроса API в качестве ключа кэша, чтобы тот же запрос API возвращал тот же кэшированный контент. Однако в более сложных сценариях вы не можете просто использовать URL в качестве ключа, например, даже для одного и того же API-запроса результаты, полученные аутентифицированными и неаутентифицированными пользователями, вызывающими интерфейс, различаются, поэтому drf-extensions использует вспомогательный KeyConstructor базовый класс для обеспечения гибкого метода генерации ключей.
KeyBit
Его можно понимать как определение правила в правилах генерации ключей, определенных KeyConstructor. Например, для одного и того же запроса API аутентифицированные и неаутентифицированные пользователи получат разные результаты ответа.Мы можем определить правило генерации ключа как запрошенный URL + идентификатор аутентификации пользователя. Тогда URL-адрес можно рассматривать как KeyBit, а идентификатор пользователя — это другой KeyBit.
декоратор cache_response
Этот декоратор используется для украшения представлений django-rest-framework (функции одного представления, действия в наборе представлений и т. д.), а украшенные представления будут иметь возможности кэширования.
Кэшировать посты в блоге
Давайте сначала используем декоратор cache_response для кэширования интерфейса списка статей Код выглядит следующим образом:
blog/views.py
from rest_framework_extensions.cache.decorators import cache_response
class PostViewSet(
mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
# ...
@cache_response(timeout=5 * 60, key_func=PostListKeyConstructor())
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@cache_response(timeout=5 * 60, key_func=PostObjectKeyConstructor())
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
Здесь мы украшаем список (действие для получения списка статей) и извлекаем (для получения одной статьи),timeout
Параметр используется для указания времени инвалидации кеша,key_func
Укажите класс генерации ключа кэша (например, KeyConstructor), конечноPostListKeyConstructor
,а такжеPostObjectKeyConstructor
Еще не определено, давайте определим эти два класса генерации ключей кэша:
blog/views.py
from rest_framework_extensions.key_constructor.bits import (
ListSqlQueryKeyBit,
PaginationKeyBit,
RetrieveSqlQueryKeyBit,
)
from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor
class PostListKeyConstructor(DefaultKeyConstructor):
list_sql = ListSqlQueryKeyBit()
pagination = PaginationKeyBit()
updated_at = PostUpdatedAtKeyBit()
class PostObjectKeyConstructor(DefaultKeyConstructor):
retrieve_sql = RetrieveSqlQueryKeyBit()
updated_at = PostUpdatedAtKeyBit()
PostListKeyConstructor
Используется для генерации ключа кэша интерфейса списка статей, он наследуется отDefaultKeyConstructor
, этот базовый класс определяет KeyBit 3 ключей кэша:
- Идентификатор метода представления, вызываемого интерфейсом, например, blog.views.PostViewSet.list.
- Формат данных, возвращаемый интерфейсом, запрошенным клиентом, например json, xml.
- Тип языка, запрошенный клиентом.
Кроме того, мы также добавили 3 пользовательских KeyBits ключа кеша:
- оператор запроса sql для выполнения запроса к базе данных
- Параметры запроса для запросов на разбивку на страницы
- Время последнего обновления ресурса Post
Вышеупомянутые 6 элементов соответствуют KeyBit, и KeyBit предоставит значение, необходимое для создания ключа кэша.Если значение, предоставляемое любым KeyBit, изменится, сгенерированный ключ кэша будет другим, и результат запроса кэша также будет другим. Этот метод предоставляет нам эффективный механизм аннулирования кеша. НапримерPostUpdatedAtKeyBit
Это KeyBit, который мы настроили, который предоставляет последнее время обновления ресурса Post.Если ресурс обновляется, возвращаемое значение изменится, и сгенерированный ключ кеша будет другим, так что интерфейс не будет читать старый кеш. стоимость.PostUpdatedAtKeyBit
Код выглядит следующим образом:
blog/views.py
from .utils import UpdatedAtKeyBit
class PostUpdatedAtKeyBit(UpdatedAtKeyBit):
key = "post_updated_at"
Поскольку KeyBit времени обновления ресурса является более общим (позже мы будем использовать его для комментирования ресурсов), поэтому мы определяем базовый классUpdatedAtKeyBit
, код показан ниже:
blog/utils.py
from datetime import datetime
from django.core.cache import cache
from rest_framework_extensions.key_constructor.bits import KeyBitBase
class UpdatedAtKeyBit(KeyBitBase):
key = "updated_at"
def get_data(self, **kwargs):
value = cache.get(self.key, None)
if not value:
value = datetime.utcnow()
cache.set(self.key, value=value)
return str(value)
get_data
Метод возвращает значение, соответствующее этому KeyBit,UpdatedAtKeyBit
Сначала прочитать время последнего обновления ресурса из кеша по заданному ключу, если не удается прочитать, установить время последнего обновления ресурса как текущее время, а затем вернуть это время.
Конечно, нам нужно автоматически поддерживать время обновления ресурса, записанное в кэше, что можно сделать с помощью сигнала django:
blog/models.py
from django.db.models.signals import post_delete, post_save
def change_post_updated_at(sender=None, instance=None, *args, **kwargs):
cache.set("post_updated_at", datetime.utcnow())
post_save.connect(receiver=change_post_updated_at, sender=Post)
post_delete.connect(receiver=change_post_updated_at, sender=Post)
Всякий раз, когда сообщение (Post) добавляется, изменяется или удаляется, django отправляет сигнал post_save или post_delete.Post_save.connect и post_delete.connect устанавливают получателя этих двух сигналов на change_post_updated_at.После отправки сигнала будет вызван метод Вызывается для записи времени обновления ресурса статьи в кеш.
Разберем логику кешируемого запроса:
- Запрос интерфейса списка статей
- согласно с
PostListKeyConstructor
Сгенерировать ключ кеша. Если кешированный результат читается с использованием этого ключа, результат чтения будет возвращен напрямую. В противном случае результат будет запрошен из базы данных, а результат запроса будет записан в кеш. - Запросите интерфейс списка статей еще раз,
PostListKeyConstructor
Будет сгенерирован тот же ключ кэша, и результат можно будет прочитать непосредственно из кэша и вернуть.
Логика обновления кеша:
- Добавляйте, изменяйте или удаляйте статьи, запускайте
post_delete
,post_save
Сигнал, время обновления ресурса статьи будет изменено. - Запросите интерфейс списка статей еще раз,
PostListKeyConstructor
Будет сгенерирован другой ключ кеша, этого нового ключа нет в кеше, поэтому последний результат будет запрошен из базы данных, а результат запроса будет записан в кеш. - Запросите интерфейс списка статей еще раз,
PostListKeyConstructor
Будет сгенерирован тот же ключ кэша, и результат можно будет прочитать непосредственно из кэша и вернуть.
PostObjectKeyConstructor
Используется для генерации ключа кэша интерфейса сведений о статье, логическая суммаPostListKeyConstructor
точно такой же.
кешировать список комментариев
С кешем списка статей кэш списка комментариев должен выполнять только шаги.
Определение бита ключа:
blog/views.py
class CommentUpdatedAtKeyBit(UpdatedAtKeyBit):
key = "comment_updated_at"
Определение KeyConstructor:
blog/views.py
class CommentListKeyConstructor(DefaultKeyConstructor):
list_sql = ListSqlQueryKeyBit()
pagination = PaginationKeyBit()
updated_at = CommentUpdatedAtKeyBit()
Вид:
@cache_response(timeout=5 * 60, key_func=CommentListKeyConstructor())
@action(
methods=["GET"],
detail=True,
url_path="comments",
url_name="comment",
pagination_class=LimitOffsetPagination,
serializer_class=CommentSerializer,
)
def list_comments(self, request, *args, **kwargs):
# ...
Кэшировать другие интерфейсы
Кэширование других интерфейсов можно выполнить описанными выше методами, и это остается в качестве упражнения.
Служба Redis
Конфигурация службы кэширования локальной памяти проста и подходит для использования в среде разработки, но не может адаптироваться к среде, подходящей для многопоточности и многопроцессорности.В онлайн-среде мы используем Redis для кэширования. С Docker запустить службу Redis очень просто.
Добавьте службу Redis в файл оркестровки контейнера production.yml в онлайн-среде:
version: '3'
volumes:
static:
database:
esdata:
redis_data:
services:
hellodjango.rest.framework.tutorial:
...
depends_on:
- elasticsearch
- redis
redis:
image: 'bitnami/redis:5.0'
container_name: hellodjango_rest_framework_tutorial_redis
ports:
- '6379:6379'
volumes:
- 'redis_data:/bitnami/redis/data'
env_file:
- .envs/.production
Затем добавьте следующую переменную среды в файл .envs/.production, это значение будет использоваться в качестве пароля для подключения к Redis:
REDIS_PASSWORD=055EDy65AAhLgBxMp1u1
Затем сервис может быть выпущен в Интернете.
Подпишитесь на официальный аккаунт, чтобы присоединиться к группе обмена