Redis для кэширования. Ускоряем взаимодействие с основной базой
Учимся ускорять нашу реляционную базу и измерять эффект от кэширования.
скриншот из игры marvel vs capcom: infinite / capcom, marvel
Сегодня мы создадим простое приложение, которое взаимодействует с базой данных MySQL, и применим механизм кэширования Redis. Приложение и обе базы развернём с помощью docker-контейнеров.
Если вы ещё не знакомы с Redis — начните с этой статьи, а о работе с Docker читайте здесь.
Какое приложение разработаем
Это будет spring-boot-приложение для хранения книг в базе данных (книжный онлайн-магазин).
Из-за частых запросов в базу подобные приложения работают медленно. Поэтому мы задействуем механизм кэширования — стратегию, позволяющую сохранять результаты запросов в оперативной памяти, что повысит скорость работы при повторном выполнении тех же запросов.
Иными словами, если данные есть в кэше — берём их оттуда; иначе выполняем более тяжёлый запрос — из постоянного хранилища.
Подготовка
- Устанавливаем Docker по инструкции с официального сайта.
- Генерируем наш проект с помощью инструмента Spring Initializr. Выбираем нужные зависимости (компоненты Spring, подключаемые к проекту):
- Spring Web,
- Spring Data JPA,
- MySQL Driver,
- Spring Data Redis
- и Lombok (по желанию).
3. Скачиваем и распаковываем полученный архив, открываем его в нашей среде разработки.
Делаем приложение
В открывшемся проекте создаём такую структуру каталогов (готовый код тут):
Начнём разработку с модели, а именно с класса Book (сущность, хранимая в базе данных):
@Data — lombok-аннотация, генерирующая шаблонный код (конструкторы, геттеры, сеттеры и так далее.
BookRepository — интерфейс, расширяющий интерфейс JpaRepository. Spring Data JPA также избавляет нас от необходимости реализовывать CRUD-операции.
Наибольший интерес представляет класс сервиса. Сервис — промежуточный слой между контроллером, обрабатывающим HTTP-запросы, и репозиторием. Каждый метод помечен аннотациями, которые обеспечивают кэширование.
@CacheConfig — аннотация конфигурирует все кэш-операции данного класса.
@Cacheable — говорит, что результат работы метода попадает в кэш и при последующем вызове берётся оттуда (по ключу, указанному в параметре).
@CachePut — позволяет обновить запись в кэше.
@CacheEvict — удаляет запись из кэша.
Класс BookController содержит конечные точки для всех вызовов разрабатываемого API.
Аннотации @GetMapping, @PostMapping и @PutMapping обозначают вызов соответствующего http-метода по пути, указанному в параметре.
Обратите внимание на операцию получения списка всех книг findAll () — мы рассчитываем время её выполнения и результат пишем в лог.
Ещё нам нужно добавить аннотацию @EnableCaching в главный класс приложения. Это позволит запускать постпроцессор для обработки других аннотаций и обработки кэширования.
Развёртываем приложение и базы
Воспользуемся Docker. Нам нужно подготовить развёртывание трёх наших сервисов: MySQL, Redis и самого приложения.
Создадим Dockerfile для описания образа нашего приложения:
Затем создадим файл docker-compose (описывает несколько связанных между собой контейнеров):
В нашем случае файл содержит описание трёх сервисов с именами контейнеров, образов и обозначением портов. Параметром environment задаются переменные среды.
Настраиваем подключение к базе
Это делается в файле application.properties:
Обратите внимание, что в spring.datasource.url и spring.redis.host задаются хосты контейнеров.
spring.cache.redis.time-to-live задаёт время существования параметра в кэше.
Собираем проект
Это делается командами:
./mvnw clean package -Dmaven.test.skip=true — собираем проект в JAR-файл.
docker-compose up — инициируем выполнение файла docker-compose.yml — а именно сборку трёх образов и создание контейнеров.
Результат отразится в терминале:
Последние четыре строки означают успешные сборки.
Проверяем эффект от кэширования
Чтобы протестировать работу сервисов, несколько раз выполним post-запрос, добавляющий новую книгу:
Это можно сделать через терминал или в Postman.
Далее в отдельном терминале выполняем команды:
Здесь мы подключаемся к контейнеру Redis и проверяем, что добавленные в базу книги попали и в кэш. Для чистоты эксперимента — очищаем кэш.
Далее подключаемся к контейнеру основного приложения в режиме чтения логов с помощью команды:
Флаг -f означает чтение логов в режиме реального времени (новые логи будут последовательно выводиться в консоль в порядке их появления), а 6f767bc19768 — идентификатор контейнера (его можно получить командой docker ps).
Затем несколько раз выполняем запрос списка книг:
и в логах получаем результат:
Видим, что первый запрос длился 676 мс (Duration) — его результаты выбирались из базы MySQL. А вот результаты последующих двух брались уже из кэша — и эти запросы выполнились в 60 раз быстрее.
Повторная проверка после очистки кэша подтверждает это:
Вот мы и доказали эффективность Redis для кэширования данных — ускорили взаимодействие с реляционной базой.