Redis — это сверхбыстрый и эффективный
Redis лучше всего подходит для ситуаций, когда требуется, чтобы данные извлекались и доставлялись клиенту как можно быстрее. Он довольно универсален и имеет множество вариантов использования, в том числе:
кэширование
как база данных NoSQL
как брокер сообщений
управление сессиями
аналитика в реальном времени
трансляция событий
Если вы используете Node, вы можете использовать модуль
Вы можете найти окончательные версии кода упражнений в следующем репозитории GitHub.
Предварительные требования и установка
Как следует из названия, перед использованием
Установка узла
Установить Node довольно просто, и вы можете следовать этому руководству по установке нескольких версий Node с помощью nvm.
Installing Redis
Для пользователей Mac и Linux установка Redis довольно проста. Откройте терминал и введите следующие команды:
wget https://download.redis.io/releases/redis-6.2.4.tar.gz
tar xzf
cd
make
Примечание. Актуальные команды см. на странице загрузки Redis.
После завершения установки запустите сервер с помощью этой команды:
src/
Вы также можете быстро попробовать Redis, запустив CLI:
src/
redis> set foo bar
OK
redis> get foo
«bar»
Если вы, как и я, пользователь Windows, все становится немного сложнее, потому что Redis не поддерживает Windows. К счастью, есть некоторые обходные пути, которые мы сейчас кратко рассмотрим.
Вы можете установить WSL (подсистема Windows для Linux). Вот официальное руководство от Microsoft.
Вы можете установить Memurai, альтернативу Redis для Windows. Он полностью совместим с Redis и в настоящее время поддерживает Redis API 5. Вот страница установки.
Вы можете использовать Redis в облаке. Вот краткое руководство, объясняющее, как настроить учетную запись.
Я использую первый вариант. Я установил дистрибутив Ubuntu, а затем установил Redis, как описано в инструкциях для пользователей Mac и Linux. Если makeкоманда завершается ошибкой, это, вероятно, связано с отсутствием пакетов, которые необходимо установить в первую очередь. Установите их и повторите попытку.
С Redis, установленным в Ubuntu, я запускаю сервер на стороне Linux, а затем создаю свой проект на стороне Windows. В основном я работаю в Windows, но использую Redis из Linux. Круто, да?
Примечание. Я не пробовал два других варианта и не могу сказать, как они работают.
Redis с Node.js: начало работы
Для начала создадим новый проект Node:
mkdir
cd
npm init -y
npm install redis
После того, как вы установили
const redis = require ('redis’) ;
const client = redis.createClient () ;
По умолчанию redis.createClient () будет использовать 127.0.0.1и 6379в качестве имени хоста и порта соответственно. Если у вас другой хост/порт, вы можете указать их так:
const client = redis.createClient (port, host) ;
Теперь вы можете выполнять некоторые действия после установления соединения. По сути, вам просто нужно прослушивать события подключения, как показано ниже:
client.on ('connect’, function () {
console.log ('Connected!') ;
}) ;
Итак, следующий фрагмент входит в app.js:
const redis = require ('redis’) ;
const client = redis.createClient () ;
client.on ('connect’, function () {
console.log ('Connected!') ;
}) ;
Теперь введите node appтерминал, чтобы запустить приложение. Перед запуском этого фрагмента убедитесь, что ваш сервер Redis запущен и работает.
Redis Data Types
Теперь, когда вы знаете, как подключиться к Redis из Node, давайте посмотрим, как хранить пары «
Струны
Все команды Redis отображаются как различные функции clientобъекта. Чтобы сохранить простую строку, используйте следующий синтаксис:
client.set ('framework’, 'ReactJS’) ; // OR
client.set (['framework’, 'ReactJS’]) ;
Вышеприведенные фрагменты хранят простую строку, ReactJSс ключом framework. Обратите внимание, что оба фрагмента делают одно и то же. Единственное отличие состоит в том, что первый передает переменное количество аргументов, а второй передает argsв функцию массив client.set (). Вы также можете передать необязательный обратный вызов, чтобы получить уведомление о завершении операции:
client.set ('framework’, 'ReactJS’, function (err, reply) {
console.log (reply) ; // OK
}) ;
Если операция по
client.get ('framework’, function (err, reply) {
console.log (reply) ; // ReactJS
}) ;
client.get () позволяет получить ключ, хранящийся в Redis. Доступ к значению ключа можно получить через аргумент обратного вызова reply. Если ключ не существует, значение replyбудет пустым.
Хэши
Много раз сохранение простых значений не решит вашу проблему. Вам нужно будет хранить хэши (объекты) в Redis. Для этого вы можете использовать такую hmset () функцию:
client.hmset ('frameworks_hash’, 'javascript’, 'ReactJS’, 'css’, 'TailwindCSS’, 'node’, 'Express’) ;
client.hgetall ('frameworks_hash’, function (err, object) {
console.log (object) ; // { javascript: 'ReactJS’, css: 'TailwindCSS’, node: 'Express’ }
}) ;
В приведенном выше фрагменте в Redis хранится хэш, который сопоставляет каждую технологию с ее фреймворком. Первым аргументом hmset () является имя ключа. Последующие аргументы представляют пары
Обратите внимание, что Redis не поддерживает вложенные объекты. Все значения свойств в объекте будут преобразованы в строки перед сохранением.
Вы также можете использовать следующий синтаксис для хранения объектов в Redis:
client.hmset ('frameworks_hash’, {
'javascript’: 'ReactJS’,
'css’: 'TailwindCSS’,
'node’: 'Express’
}) ;
Также можно передать необязательный обратный вызов, чтобы узнать, когда операция завершена.
Примечание: все функции (команды) можно вызывать с эквивалентами в верхнем/нижнем регистре. Например, client.hmset () и client.HMSET () такие же.
Списки
Если вы хотите сохранить список элементов, вы можете использовать списки Redis. Чтобы сохранить список, используйте следующий синтаксис:
client.rpush (['frameworks_list’, 'ReactJS’, 'Angular’], function (err, reply) {
console.log (reply) ; // 2
}) ;
Приведенный выше фрагмент создает список с именем frameworks_listи помещает в него два элемента. Итак, длина списка теперь равна двум. Как видите, я передал argsмассив в rpush (). Первый элемент массива представляет имя ключа, а остальные представляют собой элементы списка. Вы также можете использовать lpush () вместо того rpush (), чтобы сдвинуть элементы влево.
Чтобы получить элементы списка, вы можете использовать такую lrange () функцию:
client.lrange ('frameworks_list’, 0, -1, function (err, reply) {
console.log (reply) ; // [ 'ReactJS’, 'Angular’ ]
}) ;
Просто обратите внимание, что вы получаете все элементы списка, передавая -1в качестве третьего аргумента lrange (). Если вам нужно подмножество списка, вы должны передать здесь конечный индекс.
Наборы
Наборы похожи на списки, но разница в том, что они не допускают дублирования. Итак, если вам не нужны повторяющиеся элементы в вашем списке, вы можете использовать файл set. Вот как мы можем изменить наш предыдущий фрагмент, чтобы использовать набор вместо списка:
client.sadd (['frameworks_set’, 'ReactJS’, 'Angular’, 'Svelte’, 'VueJS’, 'VueJS’], function (err, reply) {
console.log (reply) ; // 4
}) ;
Как видите, sadd () функция создает новый объект setс указанными элементами. Здесь длина набора равна четырем, потому что Redis удаляет VueJSдубликаты, как и ожидалось. Чтобы получить элементы набора, используйте smembers () функцию следующим образом:
client.smembers ('frameworks_set’, function (err, reply) {
console.log (reply) ; // [ 'Angular’, 'ReactJS’, 'VueJS’, 'Svelte’ ]
}) ;
Этот фрагмент извлечет все элементы набора. Просто обратите внимание, что порядок не сохраняется при извлечении членов.
Это был список наиболее важных структур данных, которые можно найти в каждом приложении на основе Redis. Помимо строк, списков, наборов и хэшей, в Redis можно хранить отсортированные наборы, растровые изображения, гиперлоги и многое другое. Если вам нужен полный список команд и структур данных, посетите официальную документацию Redis. Помните, что почти каждая команда Redis доступна для clientобъекта, предлагаемого
Redis Operations
Теперь давайте взглянем на некоторые более важные операции Redis, которые также поддерживаются
Проверка наличия ключей
Иногда вам может потребоваться проверить, существует ли уже ключ, и действовать соответствующим образом. Для этого вы можете использовать exists () функцию, как показано ниже:
client.exists ('framework’, function (err, reply) {
if (reply === 1) {
console.log ('Exists!') ;
} else {
console.log ('Doesn\'t exist!') ;
}
}) ;
Удаление и истечение срока действия ключей
Иногда вам нужно очистить некоторые ключи и повторно инициализировать их. Чтобы очистить ключи, вы можете использовать delкоманду, как показано ниже:
client.del ('frameworks_list’, function (err, reply) {
console.log (reply) ; // 1
}) ;
Вы также можете указать срок действия существующего ключа следующим образом:
client.set ('status’, 'logged_in’) ;
client.expire ('status’, 300) ;
В приведенном выше фрагменте ключу назначается срок действия в пять минут key.
Увеличение и уменьшение
Redis также поддерживает увеличение и уменьшение ключей. Чтобы увеличить ключ, используйте incr () функцию, как показано ниже:
client.set ('working_days’, 5, function () {
client.incr ('working_days’, function (err, reply) {
console.log (reply) ; // 6
}) ;
}) ;
Функция incr () увеличивает значение ключа на 1. Если вам нужно увеличить значение на другую величину, вы можете использовать эту incrby () функцию. Точно так же для уменьшения ключа вы можете использовать такие функции, как decr () и decrby ().
И вот окончательная версия app.jsфайла:
const redis = require ('redis’) ;
const client = redis.createClient () ;
client.on ('connect’, function () {
console.log ('Connected!') ; // Connected!
}) ;
// Strings
client.set ('framework’, 'ReactJS’, function (err, reply) {
console.log (reply) ; // OK
}) ;
client.get ('framework’, function (err, reply) {
console.log (reply) ; // ReactJS
}) ;
// Hashes
client.hmset ('frameworks_hash’, 'javascript’, 'ReactJS’, 'css’, 'TailwindCSS’, 'node’, 'Express’) ;
client.hgetall ('frameworks_hash’, function (err, object) {
console.log (object) ; // { javascript: 'ReactJS’, css: 'TailwindCSS’, node: 'Express’ }
}) ;
// Lists
client.rpush (['frameworks_list’, 'ReactJS’, 'Angular’], function (err, reply) {
console.log (reply) ; // 2
}) ;
client.lrange ('frameworks_list’, 0, -1, function (err, reply) {
console.log (reply) ; // [ 'ReactJS’, 'Angular’ ]
}) ;
// Sets
client.sadd (['frameworks_set’, 'ReactJS’, 'Angular’, 'Svelte’, 'VueJS’, 'VueJS’], function (err, reply) {
console.log (reply) ; // 4
}) ;
client.smembers ('frameworks_set’, function (err, reply) {
console.log (reply) ; // [ 'Angular’, 'ReactJS’, 'VueJS’, 'Svelte’ ]
}) ;
// Check the existence of a key
client.exists ('framework’, function (err, reply) {
if (reply === 1) {
console.log ('Exists!') ;
} else {
console.log ('Doesn\'t exist!') ;
}
}) ;
// Delete a key
client.del ('frameworks_list’, function (err, reply) {
console.log (reply) ; // 1
}) ;
// Increment a key
client.set ('working_days’, 5, function () {
client.incr ('working_days’, function (err, reply) {
console.log (reply) ; // 6
}) ;
}) ;
Когда вы запустите файл, вы должны увидеть следующий вывод в своем терминале:
node app
Connected!
OK
ReactJS
{ javascript: 'ReactJS’, css: 'TailwindCSS’, node: 'Express’ }
2
[ 'ReactJS’, 'Angular’ ]
4
[ 'Angular’, 'ReactJS’, 'VueJS’, 'Svelte’ ]
Exists!
1
6
Примечание: если
Варианты использования Redis
Теперь, когда мы узнали об основах структур данных и операций Redis в
Использование Redis для кэширования
Кэширование — это процесс временного хранения извлеченных и обработанных данных в состоянии «готов к использованию». Это позволяет приложениям в будущих запросах быстрее получать доступ к этим данным. Это имеет решающее значение в случае высокоинтенсивных и ресурсоемких операций. Иногда запросы требуют нескольких операций (извлечение данных из базы данных и/или различных сервисов, выполнение над ними вычислений и т. д.), прежде чем окончательные данные будут составлены и доставлены клиенту.
Вместо этого, когда мы реализуем механизм кэширования, мы можем обработать данные один раз, сохранить их в кеше, а затем получить их позже напрямую из кеша, не выполняя многократных операций и вызовов сервера снова и снова. Затем, чтобы предоставлять свежие и актуальные данные, нам просто нужно периодически обновлять кеш.
Например, как мы увидим в случае использования ниже, если у нас есть некоторые данные, поступающие от стороннего API, и эти данные вряд ли будут изменены в ближайшее время, мы можем сохранить их в кеше после их получения. В следующий раз, когда сервер получит тот же запрос, он извлечет данные из кеша вместо того, чтобы делать новый вызов базы данных.
Поскольку Redis — это база данных в памяти, это идеальный выбор для кэширования. Итак, давайте посмотрим, как мы можем использовать его для создания механизма кэширования.
npm install express axios
Express — это минимальная и гибкая платформа
Axios — это простой
Затем создайте новый caching.jsфайл в корневом каталоге и поместите внутрь следующий код:
const redis = require ('redis’) ;
const client = redis.createClient () ;
const axios = require ('axios’) ;
const express = require ('express’) ;
const app = express () ;
const USERS_API = 'https: //jsonplaceholder.typicode.com/users/';
app.get ('/users’, (req, res) => {
try {
axios.get (`${USERS_API}`).then (function (response) {
const users = response.data;
console.log ('Users retrieved from the API’) ;
res.status (200).send (users) ;
}) ;
} catch (err) {
res.status (500).send ({ error: err.message }) ;
}
}) ;
app.get ('/
try {
client.get ('users’, (err, data) => {
if (err) {
console.error (err) ;
throw err;
}
if (data) {
console.log ('Users retrieved from Redis’) ;
res.status (200).send (JSON.parse (data));
} else {
axios.get (`${USERS_API}`).then (function (response) {
const users = response.data;
client.setex ('users’, 600, JSON.stringify (users));
console.log ('Users retrieved from the API’) ;
res.status (200).send (users) ;
}) ;
}
}) ;
} catch (err) {
res.status (500).send ({ error: err.message }) ;
}
}) ;
const PORT = 3000;
app.listen (PORT, () => {
console.log (`Server started at port: ${PORT}`) ;
}) ;
Здесь мы используем службу JSONPlaceholder, чтобы получить API для работы. В нашем случае API предоставляет нам данные пользователей.
Далее у нас есть два запроса: /usersи /
В первом пользователи извлекаются без кэширования результата. Всякий раз, когда мы снова отправим этот запрос, usersданные будут получены заново.
Во втором сначала выполняется проверка, не хранятся ли запрошенные данные уже в кеше. Если это так, то данные извлекаются из Redis. В противном случае, если usersданные не сохранены в кеше, они будут сначала извлечены из вызова API. В этом случае полученные данные также будут храниться в кеше, чтобы при следующем запросе они были получены быстрее.
Чтобы доказать, насколько важно кэширование для производительности, мы можем выполнить следующий тест.
Запустите node cachingв терминале и посетите /usersмаршрут в браузере.
Получение данных о пользователях без кэширования
Как видим, usersданные успешно извлекаются в 196ms.
Давайте теперь попробуем /
Получение данных о пользователях с кэшированием
В первый раз, когда мы отправим запрос, он даст нам примерно столько же времени, сколько мы получили в предыдущем маршруте, потому что у нас еще нет данных, сохраненных в кеше, но когда мы отправим его снова, результат по времени резко улучшенный — только 4ms. Это огромная разница даже в этом маленьком и простом примере. Представьте себе прирост производительности с тысячами пользователей. Итак, действительно, кэширование довольно впечатляющее!
Обратите внимание, что, в зависимости от вашей машины и скорости соединения, значения времени, которые вы получите здесь, могут отличаться от моих, но важно то, что соотношение между кэшированными и некэшированными данными останется примерно одинаковым.
Использование Redis в качестве брокера сообщений
Шаблон pub/sub (публикация/подписка) довольно прост и используется для публикации сообщений на «каналах». Затем эти сообщения отправляются всем получателям, подписанным на каналы. Давайте рассмотрим простой пример, чтобы немного прояснить ситуацию.
Для начала давайте сначала создадим новый publisher.jsфайл в корневом каталоге со следующим содержимым:
const redis = require ('redis’) ;
const publisher = redis.createClient () ;
const channel = 'status’;
async function publish () {
console.log (`Started ${channel} channel publisher... `)
publisher.publish (channel, 'free’) ;
}
publish () ;
Здесь мы определяем канал с именем status. Далее в publish () функции мы публикуем «бесплатное» сообщение в statusканал.
Давайте теперь создадим новый subscriber.jsфайл со следующим содержимым:
const redis = require ('redis’) ;
const subscriber = redis.createClient () ;
const channel = 'status’;
subscriber.subscribe (channel, (error, channel) => {
if (error) {
throw new Error (error) ;
}
console.log (`Subscribed to ${channel} channel. Listening for updates on the ${channel} channel... `) ;
}) ;
subscriber.on ('message’, (channel, message) => {
console.log (`Received message from ${channel} channel: ${message}`) ;
}) ;
Здесь мы определяем тот же канал. Затем мы подписываемся на этот канал и слушаем messageсобытие.
Теперь давайте посмотрим, как это работает. Откройте два экземпляра вашего терминала и запустите node subscriberпервый.
Файл подписчика запускается в терминале
Как мы видим, сообщение консоли успешно регистрируется, сообщая нам, что мы подписаны на statusканал и что мы ожидаем его обновлений.
Теперь запустите node publisherвторой терминал и обратите внимание, что происходит в первом.
Файлы подписчика и издателя, оба запускаются в терминале
Как мы видим, statusканал успешно запускается и в первом терминале от абонента приходит сообщение «свободен».
Итак, это шаблон pub/sub, представленный здесь в очень упрощенном виде. Но этот простой механизм можно использовать и в гораздо более сложных сценариях. Все зависит от наших потребностей.
Использование Redis для управления сессиями
Последний вариант использования, который мы рассмотрим, — это использование Redis для управления сеансами.
Для начала нам нужно установить следующие зависимости:
npm install
Обычно управление сеансом, реализованное в
Предупреждение Хранилище сеансов на стороне сервера по умолчанию MemoryStoreспециально не предназначено для производственной среды. Он будет вызывать утечку памяти в большинстве случаев, не масштабируется за пределы одного процесса и предназначен для отладки и разработки.
Итак, каково решение? Ну, вот где Redis появляется. Через
Например, в следующем варианте использования пользователь входит в приложение со своим именем пользователя и паролем. Затем сервер генерирует идентификатор сеанса и сохраняет его в хранилище Redis. Этот идентификатор сеанса отправляется клиенту и сохраняется в виде файла cookie. Каждый раз, когда пользователь посещает домашнюю страницу, файл cookie отправляется обратно на сервер, который проверяет, есть ли в магазине Redis сеанс с тем же идентификатором. Если да, домашняя страница загружается без перенаправления на страницу входа.
Давайте посмотрим на это в действии.
Создайте новый session.jsфайл в корневом каталоге со следующим содержимым:
const express = require ('express’) ;
const session = require ('
const redis = require ('redis’) ;
const client = redis.createClient () ;
const redisStore = require ('
const app = express () ;
app.use (express.json ());
app.use (express.urlencoded ({extended: true}));
client.on ('connect’, function (err) {
if (err) {
console.log ('Could not establish a connection with Redis. ' + err) ;
} else {
console.log ('Connected to Redis successfully!') ;
}
}) ;
app.use (session ({
store: new redisStore ({ client: client }),
secret: 'topsecret~! @#$%^&*',
resave: false,
saveUninitialized: false,
cookie: {
sameSite: true,
secure: false,
httpOnly: false,
maxAge: 1000 * 60 * 10 // 10 minutes
}
}))
app.get ('/', (req, res) => {
const session = req.session;
if (session.username && session.password) {
if (session.username) {
res.send (`Welcome ${session.username}!
`)
}
} else {
res.sendFile (__dirname + '/login.html’)
}
}) ;
app.post ('/login’, (req, res) => {
const session = req.session;
const { username, password } = req.body
session.username = username
session.password = password
res.type ('html’)
res.send ('Successfully logged in!')
}) ;
app.get ('/logout’, (req, res) => {
req.session.destroy (err => {
if (err) {
return console.log (err) ;
}
res.redirect ('/')
}) ;
}) ;
const PORT = 3000;
app.listen (PORT, () => {
console.log (`Server started at port: ${PORT}`) ;
}) ;
Здесь мы создаем новое хранилище сеансов. Сеанс будет действителен до maxAgeвремени, которое мы определили в конфигурации хранилища сеансов. По истечении этого времени сеанс будет автоматически удален из хранилища сеансов.
Затем мы добавляем три маршрута.
В первом, представляющем домашнюю страницу, мы проверяем, есть ли у пользователя активная сессия, и если да, то домашняя страница загружена. Если нет, пользователь перенаправляется на страницу входа (login.html).
Во втором маршруте мы берем полученные usernameи passwordотправленные через форму переменные и записываем их в хранилище сеансов.
В третьем маршруте мы уничтожаем сеанс и перенаправляем пользователя на домашнюю страницу.
Теперь нам нужно создать login.htmlфайл. Поместите внутрь следующее содержимое:
<! DOCTYPE html>
Username:
Password:
Здесь, когда нажимается кнопка «Входusername «, символы и passwordотправляются на сервер. Когда сервер успешно получает данные пользователя, пользователь перенаправляется на домашнюю страницу.
Пришло время проверить, как работает наше управление сессиями.
Беги node sessionи иди в http: //localhost:3000/. Введите любые данные пользователя, которые вы хотите, и нажмите кнопку «Войти «.
Страница с формой входа
Вы войдете в систему и встретитесь с приветственным сообщением, используя имя пользователя, которое вы только что предоставили. Теперь откройте инструменты разработчика браузера и перейдите на вкладку «Приложение «. На левой боковой панели найдите раздел «Хранилище «, разверните список «Файлы cookie «и нажмите http: //localhost:3000/. С правой стороны вы должны увидеть файл cookie с connect.sidназначенным именем по умолчанию.
Обратите внимание, что в Firefox раздел «Хранилище «представляет собой отдельную вкладку, поэтому список файлов cookie находится непосредственно под вкладкой «Хранилище «.
Домашняя страница с авторизованным пользователем
Вы можете доказать, что ключ cookie записан в Redis, выполнив KEYS *команду в интерфейсе командной строки Redis, которая покажет пронумерованный список всех существующих ключей данных:
Терминал, показывающий сеансовые ключи
Как видите, наш ключ cookie (начинающийся с sess:) стоит первым в списке. Другие ключи от запуска нашего app.jsфайла.
Теперь нажмите кнопку «Выйти»KEYS * и снова запустите команду.
Терминал, показывающий удаление сеансовых ключей
Как видите, ключ cookie теперь удален из Redis.
Вот как мы можем реализовать простое управление сеансом с помощью
Заключение
Мы рассмотрели основные и наиболее часто используемые операции в