Эта статья была первоначально написана Ayooluwa Isaiah в блоге разработчиков Honeybadger.
Кэширование — это процесс хранения данных на высокоскоростном уровне хранения таким образом, что будущие запросы к таким данным могут быть выполнены гораздо быстрее, чем это возможно при обращении к первичному месту хранения. Примером кэширования, с которым вы можете быть знакомы, является кэш браузера, который хранит часто используемые ресурсы веб-сайта локально, чтобы не получать их по сети каждый раз, когда они нужны. Благодаря сохранению кэша объектов на аппаратном обеспечении пользователя, извлечение кэшированных данных происходит практически мгновенно, что приводит к увеличению скорости и удовлетворенности пользователей.
В контексте приложений на стороне сервера кэширование направлено на улучшение времени отклика приложения за счет повторного использования ранее полученных или вычисленных данных. Например, вместо того, чтобы повторять сетевые запросы к данным, которые не меняются часто или вообще не меняются (например, список банков в вашей стране), вы можете хранить данные в кэше после первого запроса и извлекать их оттуда при последующих запросах. Это сделает последующие запросы к этим данным на порядок быстрее, что приведет к повышению производительности приложения, снижению затрат и ускорению транзакций.
Цель этой статьи — предоставить обзор кэширования, стратегий кэширования и решений, доступных в настоящее время на рынке. После прочтения этой статьи вы должны иметь лучшее представление о том, когда кэшировать, что кэшировать и какие методы следует использовать в приложениях Node.js в зависимости от конкретного случая использования.
Преимущества кэширования
Основное преимущество кэширования заключается в том, что оно повышает скорость получения данных, уменьшая необходимость повторного вычисления результата или доступа к базовому уровню обработки или хранения. Более быстрый доступ к данным значительно повышает скорость отклика и производительность приложений без привлечения новых аппаратных ресурсов. Другие преимущества включают следующее:
-
Снижение нагрузки на сервер: Определенные запросы могут требовать значительного времени обработки на сервере. Если результат запроса уже присутствует в кэше, эта обработка может быть полностью пропущена, что ускоряет время ответа, освобождая ресурсы сервера для выполнения другой работы.
-
Повышенная надежность: Увеличение задержек при получении данных — обычный эффект скачков в использовании приложений, приводящий к снижению производительности по всем направлениям. Перенаправление значительной части нагрузки на уровень кэша помогает сделать производительность гораздо более предсказуемой.
-
Снижение сетевых затрат: Размещение часто используемых объектов в кэше снижает объем сетевой активности, которая должна выполняться за пределами кэша. В результате гораздо меньше данных передается к источнику контента и обратно, что приводит к снижению стоимости передачи, уменьшению перегрузок в очередях на сетевых коммутаторах, уменьшению количества потерянных пакетов и т.д.
-
Улучшенная производительность базы данных: При исследовании производительности приложений часто обнаруживается, что значительная часть общего времени отклика приходится на уровень базы данных. Даже если запросы эффективны, стоимость обработки каждого запроса (особенно для часто используемых объектов) может быстро привести к увеличению задержек. Отличный способ решить эту проблему — полностью обойти обработку запроса и использовать предварительно вычисленный результат из кэша.
-
Повышенная доступность контента: Кэширование может использоваться как способ сохранения доступности определенных данных, даже если исходное хранилище данных временно не работает.
Когда следует применять кэширование?
Кэширование — отличный инструмент для повышения производительности, о чем свидетельствуют преимущества, рассмотренные в предыдущем разделе. Так когда же следует рассматривать возможность добавления слоя кэширования в архитектуру вашего приложения? Есть несколько факторов, которые необходимо учитывать.
В большинстве приложений есть «горячие точки» данных, которые регулярно запрашиваются, но редко обновляются. Например, если вы работаете с онлайн-форумом, может быть постоянный поток новых сообщений, но старые сообщения остаются неизменными, и многие старые темы остаются неизменными в течение длительного времени. В этом сценарии приложение может получать сотни или тысячи запросов на одни и те же неизменные данные, что делает его идеальным кандидатом для кэширования. Вообще говоря, данные, к которым обращаются часто и которые не меняются часто или вообще не меняются, должны храниться в кэше.
Еще один момент при принятии решения о кэшировании — нужно ли приложению выполнять сложные запросы или вычисления перед возвращением или отображением некоторых данных. Для веб-сайтов с большой нагрузкой даже простой акт отображения HTML-вывода после получения и вычисления необходимых данных может потребовать значительного количества ресурсов и увеличить задержку. Если возвращаемый результат после вычисления может быть повторно использован в нескольких запросах и операциях, обычно целесообразно хранить его в кэше.
Скорость изменения части данных и то, как долго можно терпеть устаревшие данные, также влияют на то, насколько они пригодны для кэширования. Если данные часто меняются настолько, что их нельзя повторно использовать для последующих запросов, то, скорее всего, не стоит тратить время на их размещение в кэше. В этом случае следует рассмотреть другие виды оптимизации.
Кэширование может быть отличным способом повышения производительности приложения, но не обязательно оно будет правильным в каждом сценарии. Как и в случае со всеми методами оптимизации производительности, важно сначала провести измерения, прежде чем вносить существенные изменения, чтобы не тратить время на оптимизацию не того, что нужно.
Первым шагом является наблюдение за состоянием и производительностью рассматриваемой системы при заданной частоте запросов. Если система не справляется с ожидаемой нагрузкой, дросселирует или страдает от высокой задержки, возможно, стоит кэшировать данные, с которыми работает система, если такой кэш обеспечит высокий коэффициент попадания при нескольких запросах.
Стратегии кэширования, которые следует рассмотреть
Стратегия кэширования — это шаблон, используемый для управления кэшированной информацией, включая то, как кэш заполняется и поддерживается. Существует несколько стратегий, и выбор правильной стратегии имеет решающее значение для получения наибольших преимуществ в производительности. Стратегия, используемая для игрового сервиса, который собирает и возвращает таблицу лидеров в режиме реального времени, будет значительно отличаться от сервиса, который предоставляет другие типы данных, например, статистику COVID-19, которая обновляется несколько раз в день.
Прежде чем выбрать решение для кэширования, необходимо учесть три основных момента:
- Тип данных, которые кэшируются.
- Как данные считываются и записываются (стратегия доступа к данным).
- Как кэш удаляет старые или устаревшие данные (политика удаления).
В следующем разделе мы обсудим различные стратегии доступа к данным, которые могут быть использованы в зависимости от типа кэшируемых данных.
Шаблоны доступа к данным
Используемая схема доступа к данным определяет отношения между источником данных и уровнем кэширования. Поэтому очень важно правильно выбрать эту часть, поскольку она может существенно повлиять на эффективность кэширования. В оставшейся части этого раздела мы обсудим распространенные шаблоны доступа к данным, а также их преимущества и недостатки.
1. Модель «без кэша
В модели «без кэша» данные загружаются в кэш только при необходимости. Всякий раз, когда клиент запрашивает данные, приложение сначала проверяет уровень кэша на наличие данных. Если данные найдены в кэше, они извлекаются и возвращаются клиенту. Это называется попаданием в кэш. Если данные отсутствуют в кэше (промах кэша), приложение запрашивает базу данных, чтобы прочитать запрошенные данные и вернуть их клиенту. После этого данные сохраняются в кэше, чтобы последующие запросы на те же данные выполнялись быстрее.
Ниже приведен псевдокод примера логики использования кэша.
function makeAQuery(key) {
// Try to get the entity from the cache.
let data = cache.get(key);
// If there's a cache miss, get the data from the original store and cache it.
if (data == null) {
data = db.get(key)
// then store the data to cache with an appropriate expiry time
// to prevent staleness
cache.set(key, data, cache.defaultTTL)
}
// return the data to the application
return data;
}
// application code that gets the data
const data = makeAQuery(12345)
Преимущества
- Кэшируются только запрошенные данные. Это означает, что кэш не заполняется данными, которые никогда не будут использованы.
- Этот режим лучше всего подходит для рабочих процессов с интенсивным чтением, в которых данные записываются один раз и читаются несколько раз, прежде чем снова обновляются (если вообще обновляются).
- Он устойчив к сбоям в работе кэша. Если уровень кэша недоступен, система вернется к хранилищу данных. Имейте в виду, что длительный период отказа кэша может привести к увеличению задержки.
- Модель данных в кэше не обязательно должна совпадать с моделью данных в базе данных. Например, результаты нескольких запросов к базе данных могут храниться в кэше под одним и тем же идентификатором.
Недостатки
- Пропуск кэша может увеличить задержку, так как при этом выполняются три операции:
- Запрос данных из кэша.
- Считать данные из хранилища данных.
- Запись данных в кэш.
- Она не гарантирует согласованность между хранилищем данных и кэшем. Если данные обновляются в базе данных, они могут не сразу отразиться в кэше, что приведет к тому, что приложение будет обслуживать несвежие данные. Чтобы этого не произошло, шаблон cache-aside часто комбинируется со стратегией write-through (обсуждается ниже), при которой данные обновляются в базе данных и кэше одновременно, чтобы предотвратить устаревание кэшированных данных.
2. Схема сквозного чтения
При сквозном кэшировании данные всегда считываются из кэша. Когда приложение запрашивает у кэша запись, а ее еще нет в кэше, она загружается из базового хранилища данных и добавляется в кэш для дальнейшего использования. В отличие от паттерна cache-aside, приложение освобождается от ответственности за чтение и запись непосредственно в базу данных.
В большинстве случаев необходимо реализовать обработчик чтения, предоставляемый кэшем, который позволяет считывать данные непосредственно из базы данных в случае промаха кэша. Вот некоторый псевдокод, демонстрирующий, как это можно сделать:
// register the function that will be executed on cache misses.
cache.onmiss = (key) => {
return db.get(key) // return data from the database
};
// Actual data from the cache or onmiss handler
// A cache entry is created automatically on cache misses
// through the key and time-to-live values after the data
// is retrieved from the database
const data = cache.readThrough(key, data, ttl);
Преимущества
- Как и cache-aside, хорошо подходит для тяжелых рабочих нагрузок, когда одни и те же данные запрашиваются много раз.
- В кэш попадают только запрошенные данные, что способствует эффективному использованию ресурсов.
- Эта модель позволяет кэшу автоматически обновлять объект из базы данных при обновлении данных или по истечении срока действия записи в кэше.
Недостатки
- Модель данных в кэше не может отличаться от модели данных в базе данных.
- Она не устойчива к сбоям в кэше, в отличие от кэш-сайда.
- Задержка может увеличиться, если запрашиваемые данные отсутствуют в кэше.
- Существует вероятность того, что данные в кэше могут стать несвежими, но эту проблему можно решить, используя одну из стратегий записи, рассмотренных ниже.
3. Стратегия сквозной записи
При использовании стратегии сквозной записи уровень кэша рассматривается как основное хранилище данных для приложения. Это означает, что новые или обновленные данные добавляются или обновляются непосредственно в кэш, а задача сохранения данных в основном хранилище данных делегируется кэш-уровню. Обе операции записи должны быть выполнены в одной транзакции, чтобы предотвратить рассинхронизацию кэшированных данных с базой данных.
Ниже приведен псевдокодовый пример логики записи.
function updateCustomer(customerId, customerData) {
// the callback function will be executed after updating the
// record in the cache
cache.writeThrough(customerId, customerData, cache.defaultTTL, (key, value) => {
return db.save(key, value) // save updated data to db
});
}
// A variant is of this pattern is when updated in the db first
// and immediately updated in the cache
function updateCustomer(customerId, customerData) {
// update the record in the database first
const record = db.findAndUpdate(customerId, customerData)
// then set or update the record in the cache
cache.set(customerId, record, cache.defaultTTL)
}
Преимущества
- Данные в кэше никогда не устаревают благодаря тому, что после каждой операции записи они синхронизируются с базой данных.
- Это подходит для систем, не терпящих застойных данных в кэше.
Недостатки
- Увеличивается задержка при записи данных, поскольку сначала выполняется дополнительная работа по записи в хранилище данных, а затем в кэш.
- Операция записи завершится неудачно, если уровень кэша станет недоступным.
- В кэше могут накапливаться данные, которые никогда не будут прочитаны, что приводит к пустой трате ресурсов. Это можно уменьшить, объединив данный паттерн с паттерном освобождения кэша или добавив политику времени жизни (TTL).
4. Модель «запись-отслеживание
В модели write-behind (также известной как write-back) данные вставляются или изменяются непосредственно в кэш, а затем асинхронно записываются в источник данных после заданной задержки, которая может составлять как несколько секунд, так и несколько дней. Основным последствием использования этого способа кэширования является то, что обновления базы данных применяются через некоторое время после завершения транзакции кэша, что означает, что вы должны гарантировать успешное завершение записи в базу данных или обеспечить возможность отката обновлений.
Преимущества
- Улучшенная производительность записи по сравнению с записью через базу данных, поскольку приложению не нужно ждать, пока данные будут записаны в базовое хранилище данных.
- Снижается нагрузка на базу данных, так как несколько записей часто объединяются в одну транзакцию базы данных, что также может снизить затраты, если количество запросов является фактором в ценообразовании поставщика базы данных.
- Приложение в некоторой степени защищено от временных сбоев базы данных, поскольку неудачные записи могут быть повторно поставлены в очередь.
- Эта система лучше всего подходит для рабочих нагрузок, требующих больших объемов записи.
Недостатки
- Если произойдет сбой кэша, данные могут быть потеряны навсегда. Поэтому он не подходит для работы с конфиденциальными данными.
- Операции, выполняемые непосредственно над базой данных, могут использовать устаревшие данные, поскольку нельзя гарантировать, что кэш и хранилище данных будут согласованы в любой момент времени.
5. Схема с опережающим обновлением
В шаблоне refresh-ahead часто используемые кэшированные данные обновляются до истечения срока их хранения. Это происходит асинхронно, чтобы приложение не ощущало эффект медленного чтения при извлечении объекта из хранилища данных в случае его истечения.
Преимущества
- Идеально подходит, когда чтение данных из хранилища данных требует больших затрат.
- Помогает постоянно синхронизировать часто используемые записи в кэше.
- Идеально подходит для рабочих нагрузок, чувствительных к задержкам, таких как сайты спортивных трансляций и финансовые панели фондового рынка.
Недостатки
- Кэш должен точно предсказывать, какие элементы кэша могут понадобиться в будущем, поскольку неточные прогнозы могут привести к ненужным чтениям из базы данных.
Политика вытеснения кэша
Размер кэша обычно ограничен по сравнению с размером базы данных, поэтому необходимо хранить только те элементы, которые необходимы, и удалять лишние записи. Политика выселения кэша гарантирует, что кэш не превысит свой максимальный лимит, удаляя старые объекты из кэша по мере добавления новых. Существует несколько алгоритмов выселения, и выбор лучшего из них зависит от потребностей вашего приложения.
При выборе политики выселения следует помнить, что не всегда целесообразно применять глобальную политику к каждому элементу в кэше. Если извлечение кэшированного объекта из хранилища данных обходится очень дорого, может быть выгодно сохранить этот объект в кэше, независимо от того, соответствует ли он требованиям для выселения. Также может потребоваться комбинация политик выселения для достижения оптимального решения для вашего случая использования. В этом разделе мы рассмотрим некоторые из наиболее популярных алгоритмов, используемых в производственных средах.
1. Наименее недавно использованный (LRU)
Кэш, реализующий политику LRU, упорядочивает свои элементы в порядке их использования. Поэтому наиболее недавно использованные элементы будут находиться в верхней части кэша, а наименее использованные — в нижней. Это позволяет легко определить, какие элементы должны быть удалены, когда приходит время очистить кэш.
Каждый раз, когда вы обращаетесь к записи, алгоритм LRU обновляет временную метку объекта и перемещает его в верхнюю часть кэша. Когда придет время выкинуть некоторые объекты из кэша, он проанализирует состояние кэша и удалит объекты в нижней части списка.
2. Наименее часто используемый (LFU)
Алгоритм наименее часто используемых элементов удаляет элементы из кэша на основе того, как часто к ним обращаются. Анализ выполняется путем увеличения счетчика на объекте кэша при каждом обращении к нему, чтобы его можно было сравнить с другими объектами, когда придет время удалить элементы из кэша.
LFU эффективен в тех случаях, когда шаблоны доступа к объектам кэша меняются нечасто. Например, активы кэшируются на CDN на основе шаблонов использования, поэтому наиболее часто используемые объекты никогда не удаляются. Это также помогает выселять объекты, которые в определенный период получают всплеск запросов, но после этого частота доступа к ним резко падает.
3. Most Recently Used (MRU)
Политика выселения Most Recently Used по сути является обратной алгоритму LRU, поскольку она также анализирует элементы кэша на основе частоты последнего обращения к ним. Разница заключается в том, что она удаляет из кэша наиболее недавно использованные объекты, а не наименее недавно использованные.
Хорошим примером использования MRU является ситуация, когда маловероятно, что объект, к которому недавно был получен доступ, будет использован снова в ближайшее время. Примером может быть удаление забронированных мест в самолете из кэша сразу после бронирования, поскольку они больше не актуальны для последующего приложения бронирования.
4. Первым пришел, первым ушел (FIFO)
Кэш, реализующий FIFO, удаляет элементы в том порядке, в котором они были добавлены, без учета того, как часто или сколько раз к ним обращались.
Срок действия кэша
Политика истечения срока действия, используемая в кэше, является еще одним фактором, который помогает определить, как долго сохраняется объект в кэше. Политика истечения срока действия обычно назначается объекту при его добавлении в кэш и часто настраивается в зависимости от типа кэшируемого объекта. Распространенная стратегия заключается в назначении абсолютного времени истечения срока действия для каждого объекта при его добавлении в кэш. По истечении этого времени объект считается просроченным и соответственно удаляется из кэша. Это время истечения срока действия выбирается на основе требований клиента, например, как быстро меняются данные и насколько терпима система к несвежим данным.
Скользящая политика истечения срока действия — еще один распространенный способ аннулирования объектов кэша. Эта политика отдает предпочтение часто используемым приложением объектам хранения, увеличивая время их истечения на определенный интервал при каждом обращении к ним. Например, элемент, скользящее время истечения которого составляет 15 минут, не будет удален из кэша до тех пор, пока к нему обращаются хотя бы раз в 15 минут.
При выборе значения TTL для записей кэша нужно быть внимательным. После первоначальной реализации кэша важно следить за эффективностью выбранных значений, чтобы при необходимости их можно было переоценить. Обратите внимание, что большинство систем кэширования могут не удалять истекшие элементы сразу по причинам производительности. Они обычно используют алгоритм очистки, который обычно вызывается при обращении к кэшу, ищет просроченные записи и удаляет их. Это позволяет избежать необходимости постоянно отслеживать события истечения срока действия, чтобы определить, когда элементы должны быть удалены из кэша.
Решения для кэширования
Существует множество способов реализации кэширования в веб-приложении. Часто, как только выявляется необходимость в кэшировании, для решения этой задачи используется внутрипроцессный кэш, поскольку он концептуально прост, относительно прост в реализации и может дать значительный прирост производительности при минимальных усилиях. Основным недостатком внутрипроцессных кэшей является то, что кэшируемые объекты ограничены только текущим процессом. При использовании в распределенной системе с несколькими экземплярами, нагрузка на которые сбалансирована, в итоге у вас будет столько же кэшей, сколько и экземпляров приложений, что приведет к проблеме согласованности кэша, поскольку запросы от клиента могут использовать более новые или более старые данные в зависимости от того, какой сервер использовался для их обработки. Эта проблема не возникает, если вы кэшируете только неизменяемые объекты.
Еще одним недостатком внутрипроцессных кэшей является то, что они используют те же ресурсы и пространство памяти, что и само приложение. Это может привести к сбоям вне памяти, если верхние границы кэша не были тщательно продуманы при его настройке. Внутрипроцессные кэши также очищаются при перезапуске приложения, что приводит к увеличению нагрузки на нижележащие зависимости во время пополнения кэша. Это важный момент, если в вашем приложении используется стратегия непрерывного развертывания.
Многие проблемы с внутрипроцессным кэшем могут быть решены путем использования распределенного кэширования, которое обеспечивает единое представление кэша, даже если он развернут на кластере из нескольких узлов. Это означает, что объекты кэша записываются и считываются из одного и того же места, независимо от количества используемых серверов, что снижает вероятность возникновения проблем с когерентностью кэша. Распределенный кэш также остается заполненным во время развертывания, поскольку он не зависит от самого приложения и использует собственное пространство для хранения, поэтому вы не ограничены доступной памятью сервера.
При этом использование распределенного кэша сопряжено с определенными трудностями. Он увеличивает сложность системы, добавляя новую зависимость, которую необходимо отслеживать и масштабировать соответствующим образом, и он медленнее, чем кэш внутри процесса, из-за сетевых задержек и сериализации объектов. Распределенный кэш также может быть недоступен время от времени (например, из-за технического обслуживания и обновлений), что приводит к заметному снижению производительности, особенно в периоды длительных перерывов. Эта проблема может быть решена путем возврата к внутрипроцессному кэшу, если распределенный кэш недоступен.
Внутрипроцессное кэширование может быть реализовано в приложении Node.js с помощью библиотек, таких как node-cache, memory-cache, api-cache и других. Существует большое разнообразие решений распределенного кэширования, но наиболее популярными являются Redis и Memcached. Они являются хранилищами ключевых значений в памяти и оптимальны для рабочих нагрузок, связанных с чтением или интенсивными вычислениями, поскольку используют память, а не более медленные механизмы хранения на дисках, применяемые в традиционных системах баз данных.
Внутрипроцессное кэширование с помощью Node-cache
Ниже приведен пример, демонстрирующий, как можно эффективно кэшировать в процессе, не прибегая к сложному процессу настройки. Это простое приложение NodeJS использует node-cache
и шаблон cache-aside, рассмотренный ранее в этом посте, чтобы ускорить последующие запросы списка постов из внешнего API.
const express = require('express');
const fetch = require('node-fetch');
const NodeCache = require('node-cache');
// stdTTL is the default time-to-live for each cache entry
const myCache = new NodeCache({ stdTTL: 600 });
// retrieve some data from an API
async function getPosts() {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts`);
if (!response.ok) {
throw new Error(response.statusText);
}
return await response.json();
}
const app = express();
app.get('/posts', async (req, res) => {
try {
// try to get the posts from the cache
let posts = myCache.get('allPosts');
// if posts does not exist in the cache, retrieve it from the
// original source and store it in the cache
if (posts == null) {
posts = await getPosts();
// time-to-live is set to 300 seconds. After this period
// the entry for `allPosts` will be removed from the cache
// and the next request will hit the API again
myCache.set('allPosts', posts, 300);
}
res.status(200).send(posts);
} catch (err) {
console.log(err);
res.sendStatus(500);
}
});
const port = 3000;
app.listen(port, () => {
console.log(`Server listening on http://localhost:${port}`);
});
Когда выполняется первый запрос к маршруту /posts
, кэш пуст, поэтому мы должны обратиться к внешнему API, чтобы получить необходимые данные. Когда я проверял время ответа на первый запрос, на получение ответа ушло около 1,2 секунды.
После получения данных из API они сохраняются в кэше, поэтому последующие запросы занимают значительно меньше времени. В моих тестах я постоянно получал время ответа на последующие запросы около 20-25 мс, что примерно на 6000% больше, чем при запросе данных по сети.
Кэширование с помощью Redis
Redis — это практически готовое решение для распределенного кэширования не только для Node.js, но и для других языков. В этом примере показано, как можно добавить слой кэширования в приложение Node.js с помощью Redis. Как и в предыдущем примере с использованием node-cache
, данные для кэширования будут получены из API.
Убедитесь, что у вас установлен Redis, прежде чем пробовать приведенный ниже пример кода. Вы можете следовать официальному руководству по быстрому запуску, чтобы узнать, как запустить его в работу. Кроме того, не забудьте установить необходимые зависимости перед запуском программы. В данном примере используется библиотека node-redis.
const express = require('express');
const fetch = require('node-fetch');
const redis = require('redis');
const { promisify } = require('util');
const redisClient = redis.createClient();
const redisGetAsync = promisify(redisClient.get).bind(redisClient);
async function getCovid19Stats() {
const response = await fetch(`https://disease.sh/v3/covid-19/all`);
if (!response.ok) {
throw new Error(response.statusText);
}
return await response.json();
}
const app = express();
app.get('/covid', async (req, res) => {
let stats = null;
try {
// try to get the data from the cache
stats = await redisGetAsync('covidStats');
} catch (err) {
console.log(err);
}
// if data is in cache, send data to client
if (stats != null) {
res.status(200).send(JSON.parse(stats));
return;
}
try {
// otherwise, fetch data from API
stats = await getCovid19Stats();
// and store it in Redis. 3600 is the time to live in seconds
redisClient.setex('covidStats', 3600, JSON.stringify(stats));
res.status(200).send(stats);
} catch (err) {
console.log(err);
res.sendStatus(500);
}
});
const port = 3000;
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
В приведенном примере глобальная статистика COVID-19 извлекается из API и возвращается клиенту по маршруту /covid
. Эта статистика кэшируется в Redis на 1 час (3 600 секунд), чтобы свести к минимуму количество сетевых запросов. Redis хранит все в виде строки, поэтому вам придется преобразовывать объекты в строку с помощью JSON.stringify()
при хранении в кэше и затем обратно в объект с помощью JSON.parse()
после извлечения из кэша, как показано выше.
Обратите внимание, что для хранения данных в кэше используется метод setex
вместо обычного метода set
. Он предпочтительнее, потому что позволяет нам установить время истечения срока действия для кэшированного объекта. По истечении заданного времени Redis автоматически избавится от объекта из кэша, чтобы его можно было обновить, снова вызвав API.
Другие соображения
Вот некоторые общие рекомендации, которые следует учитывать перед внедрением кэша в ваше приложение:
- Убедитесь, что данные пригодны для кэширования и их посещаемость достаточно высока, чтобы оправдать дополнительные ресурсы, используемые для кэширования.
- Следите за показателями инфраструктуры кэширования (такими как частота попаданий и потребление ресурсов), чтобы убедиться, что она настроена должным образом. Используйте полученные данные для принятия последующих решений относительно размера кэша, срока действия и политики вытеснения.
- Убедитесь, что ваша система устойчива к сбоям кэша. Решайте такие сценарии, как недоступность кэша, сбои в его размещении/получении, а также ошибки в последующей работе непосредственно в коде.
- Снижайте риски безопасности, используя методы шифрования, если в кэше хранятся конфиденциальные данные.
- Убедитесь, что ваше приложение устойчиво к изменениям в формате хранения, используемом для кэшированных данных. Новые версии вашего приложения должны иметь возможность читать данные, которые предыдущая версия записывала в кэш.
Заключение
Кэширование — это сложная тема, к которой не стоит относиться легкомысленно. При правильной реализации вы получите огромные плоды, но оно может легко стать источником проблем, если вы примете неправильное решение. Я надеюсь, что эта статья помогла направить вас в правильное русло в отношении настройки, управления и администрирования кэша вашего приложения.
Спасибо за прочтение и удачного кодинга!