Автор выбрал /dev/color для получения пожертвования в рамках программы Write for DOnations.
Введение
Большинство приложений зависят от данных, будь то из базы данных или API. Получение данных из API отправляет сетевой запрос на сервер API и возвращает данные в качестве ответа. Эти круговые поездки занимают время и могут увеличить время ответа вашего приложения для пользователей. Кроме того, большинство API ограничивают количество запросов, которые они могут обслуживать приложению в определенный промежуток времени, процесс, известный как ограничение скорости.
Чтобы избежать этих проблем, вы можете кэшировать свои данные, чтобы приложение делало один запрос к API, и все последующие запросы данных будут извлекать данные из кэша. Redis, база данных в памяти, которая хранит данные в памяти сервера, является популярным инструментом для кэширования данных. Вы можете подключиться к Redis в Node.js, используя модуль node-redis
, который предоставляет вам методы для извлечения и сохранения данных в Redis.
В этом руководстве вы создадите приложение Express, которое извлекает данные из RESTful API, используя модуль axios
. Затем вы измените приложение, чтобы сохранить данные, полученные из API, в Redis, используя модуль node-redis
. После этого вы реализуете период действия кэша, чтобы кэш мог истечь после прохождения определенного времени. Наконец, вы будете использовать промежуточное ПО Express для кэширования данных.
Предварительные требования
Для выполнения этого руководства вам понадобится:
-
Настроенная среда Node.js на вашем сервере. Если вы используете Ubuntu 22.04, установите последнюю версию Node.js и npm, следуя варианту 3 в Как установить Node.js на Ubuntu 22.04. Для других операционных систем см. серию Как установить Node.js и создать локальную среду разработки.
-
Redis установлен на вашем сервере. Если вы используете Ubuntu 22.04, выполните шаги 1 и 2 из Как установить и защитить Redis на Ubuntu 22.04. Если вы работаете на другой операционной системе, см. Как установить и защитить Redis.
-
Знание асинхронного программирования. Следуйте за Понимание цикла событий, обратных вызовов, обещаний и Async/Await в JavaScript.
-
Базовые знания использования веб-фреймворка Express. См. Как начать работу с Node.js и Express.
Шаг 1 — Настройка проекта
На этом этапе вы установите необходимые зависимости для этого проекта и запустите сервер Express. В этом учебнике вы создадите вики, содержащую информацию о различных видах рыб. Мы назовем проект fish_wiki
.
Сначала создайте каталог для проекта, используя команду mkdir
:
Перейдите в каталог:
Инициализируйте файл package.json
, используя команду npm
:
Опция -y
автоматически принимает все значения по умолчанию.
При выполнении команды npm init
будет создан файл package.json
в вашем каталоге со следующим содержимым:
OutputWrote to /home/your_username/<^>fish_wiki<^/package.json:
{
"name": "fish_wiki",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Затем установите следующие пакеты:
express
: веб-серверный фреймворк для Node.js.axios
: клиент HTTP для Node.js, который полезен для выполнения вызовов API.node-redis
: клиент Redis, который позволяет хранить и получать данные в Redis.
Чтобы установить все три пакета вместе, выполните следующую команду:
После установки пакетов вы создадите базовый сервер Express.
С помощью nano
или текстового редактора на ваш выбор, создайте и откройте файл server.js
:
В файле server.js
введите следующий код для создания сервера Express:
Сначала импортируйте express
в файл. Во второй строке установите переменную app
как экземпляр express
, что дает вам доступ к методам, таким как get
, post
, listen
и многим другим. В этом учебнике будет рассмотрено использование методов get
и listen
.
В следующей строке определите и назначьте переменной port
номер порта, на котором вы хотите, чтобы сервер слушал. Если номер порта не указан в файле переменных среды, по умолчанию будет использоваться порт 3000
.
Наконец, с использованием переменной app
вы вызываете метод listen()
модуля express
, чтобы запустить сервер на порту 3000
.
Сохраните и закройте файл.
Запустите файл server.js
с помощью команды node
, чтобы запустить сервер:
Консоль выведет сообщение, аналогичное следующему:
OutputApp listening on port 3000
Вывод подтверждает, что сервер работает и готов обслуживать любые запросы на порту 3000
. Поскольку Node.js не автоматически перезагружает сервер при изменении файлов, вы теперь остановите сервер, используя CTRL+C
, чтобы можно было обновить server.js
на следующем этапе.
После установки зависимостей и создания сервера Express вы будете извлекать данные из RESTful API.
Шаг 2 — Извлечение данных из RESTful API без кэширования
На этом этапе вы продолжите работу с сервером Express из предыдущего шага, чтобы извлечь данные из RESTful API без реализации кэширования, демонстрируя, что происходит, когда данные не хранятся в кэше.
Для начала откройте файл server.js
в вашем текстовом редакторе:
Затем вы извлечете данные из API FishWatch. API FishWatch возвращает информацию о видов рыб.
В вашем файле server.js
определите функцию, которая запрашивает данные API с использованием следующего выделенного кода:
На второй строке вы импортируете модуль axios
. Затем вы определяете асинхронную функцию fetchApiData()
, которая принимает species
в качестве параметра. Чтобы сделать функцию асинхронной, вы добавляете ключевое слово async
.
Внутри функции вы вызываете метод get()
модуля axios
с конечной точкой API, откуда вы хотите получить данные, которые в этом примере являются API FishWatch. Поскольку метод get()
реализует промис, вы добавляете перед ним ключевое слово await
для разрешения промиса. Как только промис разрешается и данные возвращаются из API, вы вызываете метод console.log()
. Метод console.log()
будет записывать сообщение о том, что запрос был отправлен на API. Наконец, вы возвращаете данные из API.
Затем вы определите маршрут Express, который принимает запросы GET
. В вашем файле server.js
определите маршрут следующим кодом:
В предыдущем блоке кода вы вызываете метод get()
модуля express
, который прослушивает только запросы GET
. Метод принимает два аргумента:
/fish/:вид
: конечная точка, на которой будет слушать Express. Конечная точка принимает параметр маршрута:вид
, который захватывает все, что введено на этой позиции в URL.getSpeciesData()
(пока не определена): функция обратного вызова, которая будет вызываться, когда URL совпадает с указанной конечной точкой в первом аргументе.
Теперь, когда маршрут определен, укажите функцию обратного вызова getSpeciesData
:
Функция getSpeciesData
– это асинхронная обработчиковая функция, передаваемая методу get()
модуля express
в качестве второго аргумента. Функция getSpeciesData()
принимает два аргумента: объект запроса и объект ответа. Объект запроса содержит информацию о клиенте, а объект ответа содержит информацию, которая будет отправлена клиенту из Express.
Затем добавьте выделенный код для вызова fetchApiData()
для получения данных из API в функции обратного вызова getSpeciesData()
:
В этой функции вы извлекаете значение, захваченное из конечной точки, хранящееся в объекте req.params
, затем присваиваете его переменной species
. В следующей строке вы определяете переменную results
и устанавливаете ее в undefined
.
После этого вызывается функция fetchApiData()
с переменной species
в качестве аргумента. Вызов функции fetchApiData()
предварен синтаксисом await
, потому что она возвращает обещание. Когда обещание разрешается, оно возвращает данные, которые затем присваиваются переменной results
.
Затем добавьте выделенный код для обработки ошибок времени выполнения:
Вы определяете блок try/catch
для обработки ошибок времени выполнения. В блоке try
вы вызываете fetchApiData()
для извлечения данных из API.
Если происходит ошибка, блок catch
регистрирует ошибку и возвращает код состояния 404
с ответом “Данные недоступны”.
Большинство API возвращают код состояния 404, когда у них нет данных для определенного запроса, что автоматически вызывает выполнение блока catch
. Однако API FishWatch возвращает код состояния 200 с пустым массивом, когда нет данных для этого конкретного запроса. Код состояния 200 означает успешный запрос, поэтому блок catch()
никогда не срабатывает.
Чтобы сработал блок catch()
, необходимо проверить, является ли массив пустым, и вызвать ошибку, когда условие if
вычисляется как true. Когда условия if
вычисляются как false, можно отправить ответ клиенту, содержащий данные.
Для этого добавьте выделенный код:
После того, как данные возвращены из API, оператор if
проверяет, пустая ли переменная results
. Если условие выполняется, используется оператор throw
для генерации пользовательской ошибки с сообщением API вернул пустой массив
. После выполнения этого оператора выполнение переходит к блоку catch
, который регистрирует сообщение об ошибке и возвращает ответ 404.
Напротив, если переменная results
содержит данные, условие оператора if
не будет выполнено. В результате программа пропустит блок if
и выполнит метод send
объекта ответа, который отправляет ответ клиенту.
Метод send
принимает объект с следующими свойствами:
-
fromCache
: свойство принимает значение, которое помогает определить, данные из Redis кеша или из API. Вы присвоили значениеfalse
, потому что данные поступают из API. -
data
: свойству присваивается переменнаяresults
, содержащая данные, возвращенные из API.
На данном этапе ваш полный код будет выглядеть следующим образом:
Теперь, когда всё на своем месте, сохраните и закройте ваш файл.
Запустите сервер Express:
API Fishwatch принимает множество видов рыб, но мы будем использовать только вид рыбы red-snapper
в качестве параметра маршрута на конечной точке, которую вы будете тестировать в течение этого руководства.
Теперь запустите свой любимый веб-браузер на локальном компьютере. Перейдите по URL http://localhost:3000/fish/red-snapper
.
Примечание: Если вы следуете за руководством на удаленном сервере, вы можете просмотреть приложение в браузере, используя перенаправление портов.
Сервер Node.js все еще запущен, откройте еще один терминал на локальном компьютере, а затем введите следующую команду:
Подключившись к серверу, перейдите по адресу http://localhost:3000/fish/red-snapper
в веб-браузере на вашем локальном компьютере.
Когда страница загрузится, вы увидите, что fromCache
установлен в false
.
Теперь обновите URL еще три раза и посмотрите на ваш терминал. Терминал будет регистрировать “Запрос отправлен на сервер API” столько раз, сколько раз вы обновили браузер.
Если вы обновили URL три раза после первого посещения, ваш вывод будет выглядеть следующим образом:
Output
App listening on port 3000
Request sent to the API
Request sent to the API
Request sent to the API
Request sent to the API
Этот вывод показывает, что сетевой запрос отправляется на сервер API каждый раз, когда вы обновляете браузер. Если бы у вас было приложение с 1000 пользователями, обращающимися к той же конечной точке, это означало бы 1000 сетевых запросов, отправленных на сервер API.
Когда вы внедряете кэширование, запрос к API будет выполнен только один раз. Все последующие запросы будут получать данные из кэша, повышая производительность вашего приложения.
На данный момент остановите свой сервер Express с помощью CTRL+C
.
Теперь, когда вы можете запрашивать данные из API и предоставлять их пользователям, вы будете кэшировать данные, возвращаемые из API, в Redis.
Шаг 3 — Кэширование запросов к RESTful API с использованием Redis
В этом разделе вы будете кэшировать данные из API таким образом, что только при первом посещении конечной точки вашего приложения будет запрошены данные с сервера API, а все последующие запросы будут извлекать данные из кэша Redis.
Откройте файл server.js
:
В вашем файле server.js
импортируйте модуль node-redis
:
В том же файле подключитесь к Redis с использованием модуля node-redis
, добавив выделенный код:
Сначала вы определяете переменную redisClient
со значением undefined. После этого вы определяете анонимную самовызываемую асинхронную функцию, которая является функцией, запускающейся непосредственно после ее определения. Вы определяете анонимную самовызываемую асинхронную функцию, заключив безымянное определение функции в скобки (async () => {...})
. Чтобы сделать ее самовызываемой, вы сразу после этого добавляете другой набор скобок ()
, что приводит к следующему виду (async () => {...})()
.
Внутри функции вы вызываете метод createClient()
модуля redis
, который создает объект redis
. Поскольку вы не указали порт для использования Redis при вызове метода createClient()
, Redis будет использовать порт 6379
, порт по умолчанию.
Также вызывается метод on()
Node.js, который регистрирует события на объекте Redis. Метод on()
принимает два аргумента: error
и обратный вызов. Первый аргумент error
– это событие, срабатывающее, когда Redis сталкивается с ошибкой. Второй аргумент – это обратный вызов, который выполняется, когда событие error
срабатывает. Этот обратный вызов записывает ошибку в консоль.
Наконец, вызывается метод connect()
, который начинает соединение с Redis на порте по умолчанию 6379
. Метод connect()
возвращает обещание, поэтому вы используете синтаксис await
, чтобы его разрешить.
Теперь, когда ваше приложение подключено к Redis, вы измените обратный вызов getSpeciesData()
, чтобы сохранить данные в Redis при первом посещении и извлекать данные из кеша для всех последующих запросов.
В вашем файле server.js
добавьте и обновите выделенный код:
В функции getSpeciesData
вы определяете переменную isCached
со значением false
. Внутри блока try
вызываете метод get()
модуля node-redis
с аргументом species
. Когда метод находит ключ в Redis, соответствующий значению переменной species
, он возвращает данные, которые затем присваиваются переменной cacheResults
.
Затем оператор if
проверяет, содержит ли переменная cacheResults
данные. Если условие выполняется, переменной isCache
присваивается значение true
. Затем вызывается метод parse()
объекта JSON
с аргументом cacheResults
. Метод parse()
преобразует строковые данные JSON в объект JavaScript. После разбора JSON вызывается метод send()
, который принимает объект с установленным свойством fromCache
, равным переменной isCached
. Метод отправляет ответ клиенту.
Если метод get()
модуля node-redis
не находит данных в кеше, переменная cacheResults
устанавливается в null
. В результате оператор if
возвращает ложное значение. В этом случае выполнение переходит к блоку else
, где вызывается функция fetchApiData()
для получения данных из API. Однако после получения данных из API они не сохраняются в Redis.
Для сохранения данных в кеше Redis вам необходимо использовать метод set()
модуля node-redis
. Для этого добавьте выделенную строку:
Внутри блока else
, после того как данные были получены, вы вызываете метод set()
модуля node-redis
для сохранения данных в Redis под ключом, равным значению переменной species
.
Метод set()
принимает два аргумента, которые являются парой ключ-значение: species
и JSON.stringify(results)
.
Первый аргумент, species
, – это ключ, под которым данные будут сохранены в Redis. Помните, что species
устанавливается в значение, переданное в точку доступа, которую вы определили. Например, когда вы посещаете /fish/red-snapper
, species
устанавливается в red-snapper
, который и будет ключом в Redis.
Второй аргумент, JSON.stringify(results)
, – это значение для ключа. В этом аргументе вы вызываете метод stringify()
объекта JSON
с переменной results
в качестве аргумента, которая содержит данные, возвращенные из API. Метод преобразует JSON в строку; поэтому, когда вы получали данные из кэша, используя метод get()
модуля node-redis
ранее, вы вызывали метод JSON.parse
с переменной cacheResults
в качестве аргумента.
Ваш файл теперь будет выглядеть следующим образом:
Сохраните и закройте файл, а затем запустите server.js
с помощью команды node
:
После запуска сервера обновите http://localhost:3000/fish/red-snapper
в вашем браузере.
Обратите внимание, что значение fromCache
по-прежнему равно false
:
Теперь обновите страницу еще раз, чтобы увидеть, что на этот раз fromCache
установлено в true
:
Обновите страницу пять раз и вернитесь в терминал. Ваш вывод будет похож на следующий:
OutputApp listening on port 3000
Request sent to the API
Теперь сообщение Request sent to the API
было зарегистрировано только один раз после нескольких обновлений URL, в отличие от последнего раздела, где сообщение было зарегистрировано при каждом обновлении. Этот вывод подтверждает, что на сервер был отправлен только один запрос и что далее данные были получены из Redis.
Чтобы дополнительно подтвердить, что данные хранятся в Redis, остановите свой сервер, используя CTRL+C
. Подключитесь к клиенту сервера Redis следующей командой:
Извлеките данные по ключу red-snapper
:
Ваш вывод будет напоминать следующее (отредактировано для краткости):
Output"[{\"Fishery Management\":\"<ul>\\n<li><a...3\"}]"
Вывод показывает строковую версию данных JSON, которые API возвращает при посещении конечной точки /fish/red-snapper
, что подтверждает, что данные API хранятся в кэше Redis.
Выйдите из клиента сервера Redis:
Теперь, когда вы можете кэшировать данные из API, вы также можете установить срок действия кэша.
Шаг 4 — Реализация срока действия кэша
При кешировании данных необходимо знать, как часто данные изменяются. Некоторые данные API изменяются в течение нескольких минут; другие – в течение часов, недель, месяцев или лет. Установка подходящего времени кеширования гарантирует, что ваше приложение предоставляет пользователям актуальные данные.
На этом этапе вы установите действительность кеша для данных API, которые должны храниться в Redis. Когда кеш истекает, ваше приложение отправляет запрос к API для получения свежих данных.
Вам необходимо ознакомиться с документацией вашего API, чтобы установить правильное время истечения кеша. В большинстве документаций будет упоминаться, с какой частотой данные обновляются. Однако есть случаи, когда документация не предоставляет этой информации, поэтому вам может прийтись догадаться. Проверка свойства last_updated
различных конечных точек API может показать, с какой частотой данные обновляются.
После выбора продолжительности кеша вам необходимо преобразовать ее в секунды. Для демонстрации в этом руководстве вы установите продолжительность кеша в 3 минуты или 180 секунд. Эта примерная продолжительность упростит тестирование функционала продолжительности кеша.
Чтобы реализовать продолжительность действия кеша, откройте файл server.js
:
Добавьте выделенный код:
В методе set()
модуля node-redis
вы передаете третий аргумент объекта с следующими свойствами:
EX
: принимает значение с продолжительностью кеша в секундах.NX
: если установлено вtrue
, это гарантирует, что методset()
должен устанавливать только ключ, который еще не существует в Redis.
Сохраните и закройте ваш файл.
Вернитесь к клиенту сервера Redis, чтобы проверить корректность кэша:
Удалите ключ red-snapper
в Redis:
Выйдите из клиента Redis:
Теперь запустите сервер разработки с помощью команды node
:
Переключитесь обратно в браузер и обновите URL http://localhost:3000/fish/red-snapper
. В течение следующих трех минут, если вы обновите URL, вывод в терминале должен быть согласован с следующим выводом:
OutputApp listening on port 3000
Request sent to the API
После того как пройдут три минуты, обновите URL в вашем браузере. В терминале вы должны увидеть, что “Запрос отправлен в API” был зарегистрирован дважды.
OutputApp listening on port 3000
Request sent to the API
Request sent to the API
Этот вывод показывает, что кэш устарел, и запрос к API был сделан снова.
Вы можете остановить сервер Express.
Теперь, когда вы можете установить корректность кэша, вы будете кэшировать данные с помощью промежуточного ПО далее.
Шаг 5 — Кэширование данных в промежуточном ПО
На этом шаге вы будете использовать промежуточное ПО Express для кэширования данных. Промежуточное ПО — это функция, которая может получить доступ к объекту запроса, объекту ответа и обратному вызову, который должен запускаться после его выполнения. Функция, которая запускается после промежуточного ПО, также имеет доступ к объекту запроса и ответа. При использовании промежуточного ПО вы можете изменять объекты запроса и ответа или возвращать ответ пользователю раньше.
Для использования промежуточного ПО в вашем приложении для кэширования вы измените функцию обработчика getSpeciesData()
для получения данных из API и их сохранения в Redis. Весь код, который ищет данные в Redis, будет перемещен в функцию промежуточного ПО cacheData
.
При посещении конечной точки /fish/:species
сначала будет запущена функция промежуточного ПО для поиска данных в кэше; если они найдены, будет возвращен ответ, и функция getSpeciesData
не будет выполняться. Однако, если промежуточное ПО не найдет данные в кэше, оно вызовет функцию getSpeciesData
для получения данных из API и их сохранения в Redis.
Сначала откройте ваш server.js
:
Затем удалите выделенный код:
В функции getSpeciesData()
удалите весь код, который ищет данные, сохраненные в Redis. Также удалите переменную isCached
, поскольку функция getSpeciesData()
будет только получать данные из API и сохранять их в Redis.
После удаления кода установите fromCache
в false
, как показано ниже, чтобы функция getSpeciesData()
выглядела следующим образом:
Функция getSpeciesData()
извлекает данные из API, сохраняет их в кэше и возвращает ответ пользователю.
Затем добавьте следующий код для определения промежуточной функции для кэширования данных в Redis:
Функция промежуточного обработчика cacheData()
принимает три аргумента: req
, res
и next
. В блоке try
функция проверяет, содержит ли переменная species
данные, хранящиеся в Redis под ее ключом. Если данные есть в Redis, они возвращаются и устанавливаются в переменную cacheResults
.
Затем оператор if
проверяет, есть ли данные в cacheResults
. Если условие истинно, данные сохраняются в переменную results
. После этого промежуточное ПО использует метод send()
для возврата объекта со свойствами fromCache
, установленным в true
, и data
, установленным в переменную results
.
Однако, если условие оператора if
ложно, управление переходит к блоку else
. Внутри блока else
вызывается функция next()
, которая передает управление следующей функции, которая должна выполниться после нее.
Чтобы промежуточное ПО cacheData()
передавало управление функции getSpeciesData()
, когда вызывается next()
, соответственно обновите метод get()
модуля express
:
Метод get()
теперь принимает cacheData
в качестве своего второго аргумента, который является промежуточным ПО, ищущим данные, кешированные в Redis, и возвращающим ответ при нахождении.
Теперь, когда вы посещаете конечную точку /fish/:species
, сначала выполняется cacheData()
. Если данные кэшированы, он вернет ответ, и цикл запроса-ответа закончится здесь. Однако, если данные не найдены в кэше, будет вызвана getSpeciesData()
для извлечения данных из API, их сохранения в кэше и возврата ответа.
Полный файл теперь будет выглядеть так:
Сохраните и закройте ваш файл.
Чтобы правильно протестировать кэширование, вы можете удалить ключ red-snapper
в Redis. Для этого перейдите в клиент Redis:
Удалите ключ red-snapper
:
Выйдите из клиента Redis:
Теперь запустите файл server.js
:
Как только сервер запустится, вернитесь в браузер и снова посетите http://localhost:3000/fish/red-snapper
. Обновите его несколько раз.
Терминал будет записывать сообщение о том, что запрос был отправлен на API. Промежуточное ПО cacheData()
будет обрабатывать все запросы в течение следующих трех минут. Ваш вывод будет выглядеть примерно так, если вы случайным образом обновите URL в течение четырех минут:
OutputApp listening on port 3000
Request sent to the API
Request sent to the API
Поведение согласуется с тем, как работало приложение в предыдущем разделе.
Теперь вы можете кэшировать данные в Redis, используя промежуточное ПО.
Заключение
В этой статье вы создали приложение, которое получает данные из API и возвращает их в качестве ответа клиенту. Затем вы изменили приложение, чтобы кэшировать ответ API в Redis при первом посещении и обслуживать данные из кэша для всех последующих запросов. Вы изменили длительность кэширования так, чтобы она истекала после определенного времени, а затем использовали промежуточное ПО для обработки извлечения данных из кэша.
Как следующий шаг, вы можете изучить документацию по Node Redis, чтобы узнать больше о возможностях модуля node-redis
. Вы также можете прочитать документацию по Axios и Express для более глубокого погружения в темы, рассмотренные в этом учебнике.
Чтобы продолжить развивать свои навыки в Node.js, ознакомьтесь с серией статей “Как писать код на Node.js”.
Source:
https://www.digitalocean.com/community/tutorials/how-to-implement-caching-in-node-js-using-redis