Мы живем в век безотлагательности, когда время стоит больше, чем деньги. В сочетании с динамизмом интернета это приводит к тому, что аудитория с каждым днем становится все требовательнее.
У сайта может быть превосходное содержимое, но будут ли люди ждать, чтобы его увидеть, если оно загружается очень медленно?
Согласно нескольким исследованиям, до 50% пользователей ждет не более трех секунд. Для коммерческих сайтов это настоящая проблема, которая ведет к потере потенциальных продаж.
Кэширование данных веб-приложения может иметь решающее значение для решения этой проблемы и обеспечения высокой производительности при масштабировании.
В последние годы Redis стала самой популярной кэширующей базой данных, поскольку она позволяет значительно улучшить производительность и работу сайта, получая доступ к данным за считанные миллисекунды.
Что такое Redis?
Удаленный сервер словарей (Remote Dictionary Server, Redis) — это высокопроизводительная база данных NoSQL с открытым исходным кодом, которую применяют для кэширования в приложениях различного типа.
Redis основывается на структуре хэш-таблицы, где каждый ключ имеет соответствующее значение. По сравнению с другими аналогичными базами данных Redis позволяет использовать более сложные и гибкие структуры, которые открывают ряд дополнительных возможностей для бизнеса.
Redis рекомендуется, когда скорость доступа и время отклика критичны для бизнес-решения, а также при работе с приложениями реального времени, которым нужно быстро получать доступ к данным. Среди наиболее распространенных вариантов использования Redis:
- чаты и мессенджеры;
- списки последних элементов;
- счетчики в реальном времени и статистика;
- управление корзинами для онлайн-покупок;
- хранение пользовательских сеансов в приложении;
- поддержка кэширования веб-страниц.
Что такое кэширование?
Кэширование — это хранение копий данных в памяти, что позволяет приложениям быстрее получать доступ к ним.
Цель кэширования — ускорить операции доступа к данным по сравнению с тем, что могут предложить базы данных и удаленный сервер. Особенно это касается дорогостоящих (по времени) операций.
Кэширование пригодится, если запросы требуют нескольких операций, таких как извлечение данных из базы данных, проведение вычислений, обращение к данным других служб и так далее.
При этом мы обрабатываем данные только один раз, храним их в кэше, а затем извлекаем непосредственно из кэша, не выполняя всех этих дорогостоящих операций. Кэш периодически обновляется, чтобы пользователи могли видеть актуальную информацию.
Кэширование с Node.js и Redis
Начнем с разработки приложения. Наша цель — выполнить запросы ко внешнему API и измерить время отклика.
После внедрим в приложение Redis, чтобы результат запросов сохранялся в кэше. Благодаря этому мы сможем сравнить время отклика до и после внедрения Redis.
Установка Redis
Установим Redis в локальном окружении. Для этого можно воспользоваться следующими руководствами:
Установка Node.js
Создадим проект Node.js, начав со ввода команды для создания package.json
:
npm init
Установим необходимые для Node.js-приложения зависимости с помощью следующих команд:
npm install express redis axios
- Express для создания сервера.
- Redis для соединения приложения с Redis и выполнения запросов.
- axios для отправки REST-запросов.
npm install nodemon response-time -D
- nodemon автоматически запускает приложение после каждого изменения.
- response-time дает возможность измерить время отклика на каждый запрос.
В файле package.json
создадим внутри ключа scripts
новый скрипт, который будет использоваться для запуска приложения.
"dev": "nodemon src/app.js"
Создание приложения Node.js
Настроим начальный шаблон для Node.js-приложений, подобных этому. Создадим файл app.js
и добавим внутрь него следующие строчки:
const express = require('express')
const responseTime = require('response-time')
const redis = require('redis')
const axios = require('axios')const app = express()
app.listen(process.env.PORT || 3000, () => {
console.log("Node server started")
})
Эта команда создаст сервер Express:
npm run dev
В консоль будет выведено сообщение: “Node server started”
.
Получение данных от внешнего API
В этом руководстве мы воспользуемся REST-версией API Rick and Morty, где есть следующие конечные точки:
{
"characters": "https://rickandmortyapi.com/api/character",
"locations": "https://rickandmortyapi.com/api/location",
"episodes": "https://rickandmortyapi.com/api/episode"
}
С помощью axios сделаем запрос к эндпойнту /character
.
const express = require('express')
const responseTime = require('response-time')
const redis = require('redis')
const axios = require('axios')
const app = express()
app.use(responseTime())app.get("/character", async (req, res) => {
try {
const response = await axios.get('https://rickandmortyapi.com/api/character')
await client.set('characters', JSON.stringify(response.data))
return res.status(200).json(response.data)
} catch (err) {
return res.status(err.response.status).json({ mmessage: err.mmessage })
}
});
app.listen(process.env.PORT || 3000, () => { console.log("Node server started")})
В ответ на запрос API возвращает объект со всеми персонажами. Здесь нам интересно время отклика (response-time
), ушедшее на запрос.
Благодаря тому, что мы добавили пакет response-time в качестве промужеточного ПО, в заголовках запроса мы увидим один новый заголовок под названием X-Response-Time, который и укажет нужное время.
Этот запрос занял 238,831 мс.
Реализация кэша Redis для эндпойнта
Посмотрим, как можно повысить производительность приложения с помощью кэширования.
Во-первых, нужно подключиться к серверу Redis через приложение. Для этой задачи воспользуемся установленным ранее пакетом Redis.
По умолчанию redis.createClient()
будет использовать 127.0.0.1
и 6379
в качестве имени хоста и порта соответственно. Если у вас другой хост/порт, вы можете задать их так:
const client = redis.createClient(port, host)
const runApp = async () => {
const client = redis.createClient()
client.on('error', (err) => console.log('Redis Client Error', err))
await client.connect()
console.log('Redis connected!')
}
runApp()
Теперь сделаем тот же запрос к API и кэшируем ответ с помощью метода set()
клиента Redis, принимающего в качестве первого параметра имя ключа, который мы хотим сохранить. В качестве второго параметра используется значение этого ключа. Оно сохраняется как строка, поэтому следует перевести JSON в строку.
const runApp = async () => {
app.get('/character', async (req, res) => {
try {
const response = await axios.get('https://rickandmortyapi.com/api/character')
await client.set('characters', JSON.stringify(response.data))
return res.status(200).json(response.data)
} catch (err) {
return res.status(err.response.status).json({ mmessage: err.mmessage })
}
})
}
runApp()
Когда приложение перезагрузится, и мы получим результат запроса, у нас уже будет кэшированный ответ.
Для проверки этого задействуем Redis-commander, который позволяет просматривать базу данных Redis через веб-интерфейс. Для его установки введем следующую команду:
npm install -g redis-commander
Этот пакет предоставит redis-commander
, который при выполнении запустит веб-интерфейс базы данных на порту 127.0.0.1:8081
.
Войдя в redis-commander
, мы увидим, что ответ на запрос /character
уже сохранен с присвоенным ему именем.
Процесс кэширования с помощью Redis довольно прост. Получая запрос от пользователя, вначале проверяем, кэшированы ли уже запрошенные данные. Если да, то их можно быстро извлечь из Redis и отправить в ответе.
Если данные не кэшированы (это называется промахом кэша), нужно сначала извлечь данные из базы или внешнего API и отправить их клиенту. Полученные данные также обязательно сохраняются в кэше, чтобы в следующий раз при получении того же запроса мы могли быстрее отправить кэшированные данные пользователю.
const runApp = async () => {
app.get('/character', async (req, res) => {
try {
const cacheCharacters = await client.get('characters')
if (cacheCharacters) {
return res.json(JSON.parse(cacheCharacters))
}
const response = await axios.get('https://rickandmortyapi.com/api/character')
await client.set('characters', JSON.stringify(response.data))
return res.status(200).json(response.data)
} catch (err) {
return res.status(err.response.status)
.json({ mmessage: err.mmessage })
}
})
}
runApp()
Теперь информация извлекается из кэша, и время отклика значительно сократилось — до 4,230 мс.
Важно отметить, что это в этом руководстве мы затронули тему довольно поверхностно, а на деле Redis может предложить гораздо больше. Ознакомьтесь с его официальной документацией.
Вот полный код, который содержит еще один пример конечной точки, получающей параметры:
const express = require('express')
const responseTime = require('response-time')
const redis = require('redis')
const axios = require('axios')
const runApp = async () => {
// подключение к redis
const client = redis.createClient()
client.on('error', (err) => console.log('Redis Client Error', err));
await client.connect();
console.log('Redis connected!')
const app = express()
// добавление response-time к запросам
app.use(responseTime())
app.get('/character', async (req, res) => {
try {
// проверяем, сохранен ли запрос в кэше, и если да, то возвращаем ответ
const cacheCharacters = await client.get('characters')
if (cacheCharacters) {
return res.json(JSON.parse(cacheCharacters))
}
// запрос к API
const response = await axios.get('https://rickandmortyapi.com/api/character')
/* Другой способ — сохранить данные с именем url запроса, со свойством
req.originalUrl, которое будет таким же, как '/character'
await client.set(req.originalUrl, JSON.stringify(response.data))
*/
// сохранение ответа в кэше
await client.set('characters', JSON.stringify(response.data))
return res.status(200).json(response.data)
} catch (err) {
return res.status(err.response.status).json({ mmessage: err.mmessage })
}
})
app.get('/characters/:id', async (req, res) => {
try {
const cacheCharacter = await client.get('cacheCharacter' + req.params.id)
if (cacheCharacter) {
return res.json(JSON.parse(cacheCharacter))
}
const response = await axios.get('https://rickandmortyapi.com/api/character/' + req.params.id)
await client.set('cacheCharacter' + req.params.id, JSON.stringify(response.data))
return res.json(response.data)
} catch (err) {
return res.status(err.response.status)
.json({ message: err.message })
}
})
app.listen(process.env.PORT || 3000, () => {
console.log(`server on port 3000`)
})
}
runApp()
Заключение
В этом руководстве мы вкратце познакомились с Redis и создали простой механизм кэширования для приложения Node.js.
Теперь вы знаете, как пользоваться Redis для кэширования часто запрашиваемых данных в приложении и тем самым значительно повысить производительность.
Читайте также:
- 4 типичные ошибки разработчиков Node.js
- Кэширование с помощью Redis и Node.js
- Почему стоит использовать обратные вызовы и асинхронный код на NodeJS
Читайте нас в Telegram, VK и Дзен
Перевод статьи L Javier Tovar: Reduce Response Times and Improve Performance with Redis Caching