Как ускорить отклик и повысить производительность при помощи кэширования Redis



Книга <strong>Как ускорить отклик и повысить производительность при помощи кэширования Redis</strong>

Мы живем в век безотлагательности, когда время стоит больше, чем деньги. В сочетании с динамизмом интернета это приводит к тому, что аудитория с каждым днем становится все требовательнее.





У сайта может быть превосходное содержимое, но будут ли люди ждать, чтобы его увидеть, если оно загружается очень медленно?


Согласно нескольким исследованиям, до 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 для кэширования часто запрашиваемых данных в приложении и тем самым значительно повысить производительность.



451   0  

Comments

    Ничего не найдено.