Skip to content

Caching with Redis

Перевод: Caching at Scale With Redis Cloud Caching Techniques for Enterprise Applications - Lee Atchison (2021)

Glossary

  • высокая доступность - high availability
  • кэширование - caching
  • масштабируемый - scalable
  • архитектурные шаблоны - architectural patterns
  • масштабирование кэша - cache scaling
  • согласованность кэша - cache consistency
  • службы (service)

Глава 1. Введение

Наш современный мир требует современных приложений. Современные приложения должны быть способны обрабатывать большие объемы данных, выполнять сложные операции, поддерживать множество взаимосвязей между элементами данных и оперировать с различными и разрозненными состояниями между транзакциями.

Это является вызовом на сегодняшний день, который требует значительных ресурсов, и эти требования постоянно растут в зависимости от постоянно меняющихся потребностей. Обработка этих потребностей при сохранении высокой доступности (high availability) является просто стоимостью входа для любого современного приложения.

Хотя существует множество методов, процессов и техник для обеспечения высокой доступности приложения в масштабе, кэширование (caching) является центральной техникой практически во всех из них. Эффективное кэширование - это отличительная черта эффективно масштабируемого (scalable) приложения. В этой книге описывается, что такое кэширование, почему оно является основой эффективных крупномасштабных современных приложений, и как Redis может помочь вам удовлетворить эти требования кэширования.

Затем рассматриваются различные типы стратегий кэширования и какие архитектурные шаблоны (architectural patterns) могут быть реализованы с использованием Redis. Затем будет рассказано о масштабировании кэша (cache scaling) и согласованности кэша (cache consistency), а также о том, как кэширование может быть использовано в облачных средах (cloud environments). Мы завершаем обсуждение измерения производительности кэша (cache performance) и заключаем с глоссарием важных терминов кэша.

Целевая аудитория этой книги - это старшие инженеры-программисты и архитекторы программного обеспечения, которые могут уже быть знакомы с Redis и заинтересованы в расширении использования Redis. Они создают и управляют высоко масштабируемыми, сложными приложениями с большими и часто неуклюжими наборами данных и интересуются тем, как Redis может быть использован в качестве их основного кэширующего движка (caching engine) для управления производительностью данных (data performance).

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

Вы получите знания, необходимые для обсуждения с вашим CTO/CIO/CFO о том, как Redis и Redis Enterprise могут помочь решить проблемы масштабирования данных вашей организации.

Глава 2. Что такое кэширование?

A cache is a place to hide things...

no, wait—that’s the wrong type of cache.


Кэш - это место, чтобы прятать вещи...

нет, подождите, это неправильный тип кэша.

В инженерии программного обеспечения кэш - это компонент хранения данных, к которому можно получить доступ быстрее или более эффективно, чем к исходному источнику данных. Это временное место хранения информации, чтобы повторный доступ к той же информации можно было осуществить быстрее и с меньшими ресурсами. На рисунке ниже показано, как потребитель запрашивает данные у службы (service), а затем данные возвращаются в ответе.

Figure 2-1. A simple cache

Но что происходит, если службе данных (data service) приходится выполнять некоторую сложную операцию для получения данных? Эта сложная операция может потреблять много ресурсов, занимать много времени или и то, и другое. Если службе приходится выполнять сложную операцию каждый раз, когда потребитель (consumer) запрашивает данные, значительное количество времени и/или ресурсов может быть потрачено на извлечение одних и тех же данных снова и снова.

Вместо этого, с помощью cache (кэша), при первом выполнении сложной операции результат возвращается потребителю, и копия результата сохраняется в кэше. Когда данные снова понадобятся, вместо того чтобы снова выполнять сложную операцию, результат может быть непосредственно извлечен из кэша и быстрее возвращен потребителю, используя меньше ресурсов. Эта конкретная стратегия кэширования (caching strategy) обычно называется кэшированием-выделяющимся (cache-aside)— о ней мы поговорим позже.

Кэширование: области применения

Существует множество различных типов кэшей, используемых в компьютерных науках, и они применяются в практически всех аспектах разработки программного и аппаратного обеспечения. Общие сферы применения кэшей включают:

HTTP application cache (Кэш приложений HTTP): При доступе к веб-странице, в зависимости от веб-сайта, страница может требовать значительной обработки, чтобы быть созданной, отправленной вам и отображенной в вашем браузере. Однако страница или значительные части страницы могут изменяться незначительно от одного запроса к другому. Кэш используется для хранения страниц и/или их частей, чтобы они могли быть возвращены пользователю быстрее и с использованием меньших ресурсов, чем если бы кэш не использовался. Это приводит к более отзывчивому (responsive) веб-сайту и возможности для веб-сайта обрабатывать значительно больше одновременных пользователей с заданным количеством вычислительных ресурсов. Кэш приложений HTTP показан на рисунке ниже.

Figure 2-2. Web caching at various locations in a network

HTTP network cache (Кэш сети HTTP): Когда пользователь получает доступ к веб-странице, сайт, генерирующий страницу, может находиться далеко, иногда в другой части страны или совсем в другой стране. Это может занять относительно долгое время, прежде чем страница будет отправлена с сервера веб-сайта на ваш браузер для отображения. Протоколы HTTP предоставляют возможность хранить копии определенных статических веб-страниц или элементов страницы на промежуточных узлах вдоль маршрута между браузером и сервером веб-сайта. Поскольку эти кэши позволяют хранить контент ближе к браузеру, веб-сайт может быть получен быстрее, и результаты будут отображаться быстрее. Это часто относится к статическому контенту, такому как изображения, фотографии и видео. На рисунке выше, представлен кэш сети HTTP.

Web browser cache (Кэш веб-браузера): Ваш веб-браузер также может кэшировать некоторый контент, чтобы он мог быть отображен практически мгновенно, а не ожидать загрузки через значительное расстояние и, возможно, по более медленному интернет-соединению. Этот кэш браузера особенно эффективен для изображений и другого статического контента. На рисунке выше, представлен кэш веб-браузера.

Service cache (Кэш сервиса): У отдельных служб могут быть внутренние кэши (internal caches), которые помогают выполнять сложные и затратные по времени операции. Эти кэши называются кэшами сервиса. Кэш сервиса очень похож на кэш приложения, но работает и кэширует в поддержку отдельных компонентов сервиса, составляющих приложение.

Application programming interface (API) cache (Кэш интерфейса прикладного программирования): Когда одна служба вызывает другую, используя API, ответ на вызов API может быть сохранен в кэше и использован для возврата результатов для эквивалентных будущих вызовов.

Database cache (Кэш базы данных): Когда база данных получает запрос на извлечение данных, требуемая обработка может быть довольно обширной. Кэши баз данных могут использоваться в нескольких местах внутри баз данных для ускорения запросов, а также для экономии вычислительных ресурсов, что помогает масштабированию баз данных (database scaling). Эти кэши обычно используются для хранения:

  • результатов запросов (query results)
  • промежуточных результатов (intermediate results)
  • результатов разбора запросов (query parsing results)
  • планов запросов (query plans)

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

Figure 2-3. Database cache

CPU cache (Кэш процессора (CPU)): В компьютерах есть много кэшей, включая в центральном процессоре (CPU). Кэши ядра CPU выполняют команды так, чтобы повторное выполнение одинаковых или похожих инструкций происходило значительно быстрее. Фактически, большая часть увеличения скорости компьютеров за последние годы обусловлена улучшениями в том, как выполняются команды и кэшируются, а не улучшениями реальной тактовой частоты.

Рисунок ниже иллюстрирует этот тип кэша.

Figure 2-4. CPU instruction cache

Memory cache (Кэш памяти): Оперативная память (RAM) быстрая, но не достаточно быстра для высокоскоростных процессоров. Чтобы данные могли быть извлечены достаточно быстро из RAM, чтобы не отставать от высокоскоростных процессоров, RAM кэшируется в крайне быстрый кэш, и команды выполняются из этого кэша.

Disk cache (Кэш диска): Дисковые накопители относительно медленны при извлечении информации. RAM, используемая в качестве среды хранения кэша, может быть размещена перед диском, так что повторные общие операции диска (например, извлечение содержимого списка каталога) могут быть прочитаны из кэша, а не ждать, пока результаты будут прочитаны с диска.

Что нужно для того, чтобы кэш был полезным?

Чтобы кэш работал и приносил пользу, должны быть выполнены следующие условия:

  1. Операция, необходимая для вычисления или получения запрошенных данных, должна либо быть медленной, либо требовать ресурсы для получения/вычисления.
  2. Кэш должен быть способен хранить и правильно извлекать результат быстрее и с использованием меньших ресурсов, чем исходный источник.
  3. Иногда данные, с которыми связан запрос, должны оставаться неизменными от одного запроса к другому. Хотя в определенных случаях можно кэшировать динамически изменяющиеся данные, но данные, которые не изменяются от запроса к запросу, легче использовать в ситуациях кэширования.
  4. Операция по вычислению или получению запрошенных данных не должна иметь побочных эффектов (side effects) (то есть, она не должна сохранять данные, не должна вносить изменения в другие системы и не должна управлять другим программным или аппаратным обеспечением), кроме потребления ресурсов. Подробнее об этом в следующем пункте.
  5. Эти данные должны быть нужны более одного раза. Чем чаще они нужны, тем более эффективен и полезен кэш.

Для эффективного кэширования важно хорошо понимать статистическое распределение доступа к данным вашего приложения или источника данных. Когда доступ к данным имеет нормальное распределение (normal distribution) или так называемое колоколообразное распределение (bell-curve distribution), кэширование вероятно будет эффективнее по сравнению с плоским распределением доступа к данным (flat data access distribution). Существуют продвинутые стратегии кэширования, которые более эффективны с другими видами распределения доступа к данным.

Статический vs. динамический кэш

Не все кэши доступны одинаковым образом. Хотя кэши используются и доступны разными способами, существуют два основных типа шаблонов использования кэшей. Они называются статическими (static), иногда называемыми только для чтения (read/only), кэшами, и динамическими (dynamic), иногда называемыми для чтения/записи (read/write), кэшами. Разница заключается в том, как заполняются кэши, когда данные в базовом хранилище данных изменяются, и это показано на рисунке ниже.

Figure 2-5. Static vs. dynamic cache

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

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

Глава 3. Зачем кэширование?

Caching allows you to skip doing important things, and yet still benefit from the results...

sometimes.


Кэширование позволяет вам пропустить выполнение важных задач, и тем не менее, получить пользу от результатов...

иногда.

Рассмотрим простой сервис, который умножает два числа. Вызов такого сервиса может выглядеть так:

MUL 6 7
    result: 42

Этот сервис может быть вызван неоднократно из разных источников с множеством различных запросов на умножение:

MUL 6 7
    result: 42
MUL 3 4
    result: 12
MUL 373 2389
    result: 891,097
MUL 1839 2383
    result: 4,382,337
MUL 16 12
    result: 192
MUL 3 4
    result: 12

Вы видите, что он может обрабатывать целую серию запросов на умножение, обрабатывать их все независимо и возвращать результаты. Но заметили ли вы, что последний запрос такой же, как второй? То же самое умножение запрошено второй раз. Однако этот бедный маленький сервис не знает, что его уже попросили выполнить запрос, и он выполняет всю работу, которую ему нужно сделать, чтобы рассчитать результат... снова.

Умножение 3 на 4 для получения 12 может показаться не очень трудной задачей. Но это сложнее, чем вы могли бы подумать, и операции, которые может попросить выполнить такой сервис умножения, могут быть значительно более сложными. Если сервис уже выполнил ту же операцию и вернул тот же результат, зачем ему снова выполнять ту же операцию? Хотя некоторые сервисы не могут пропустить выполнение повторной операции, в сервисе, подобном этому, нет причин повторно выполнять вычисление, которое уже было выполнено.

Вот где начинает играть кэширование. Вместо того чтобы напрямую отправлять запросы сервису, в архитектуру добавляется кэш. Затем для использования сервиса должен использоваться кэш. Предполагая стратегию кэширования на стороне (cache-aside strategy), запрос к сервису для умножения 3 на 4 может работать следующим образом:

  1. Обратиться к кэшу, чтобы увидеть, есть ли запись, представляющая 3x4.
  2. Если запись существует, вернуть результат из кэша. ОСТАНОВИТЬ.
  3. Если записи нет, вызвать сервис и получить результат умножения 3 на 4.
  4. Сохранить результат вызова сервиса в кэше под записью 3x4.
  5. Вернуть результат.

Что происходит дальше? Ну, первый раз, когда кто-то хочет получить результат умножения 3 на 4, запрос должен обратиться к сервису напрямую, потому что значение еще не было помещено в кэш. Однако после того, как значение рассчитано, результат помещается в кэш. Теперь кэш хранит операцию (3 умножить на 4, записанную как 3x4) в качестве ключа (key) к результату операции (12).

В следующий раз, когда кто-то захочет получить результат умножения 3 на 4, запрос увидит, что результат уже хранится в кэше, и просто вернет сохраненный результат. Сам сервис не нужно выполнять вычисление, что экономит время и ресурсы.

Этот процесс кэширования на стороне пользователя (cache-aside) иллюстрируется на рисунке ниже. В верхней части этого рисунка (раздел 1)вы можете видеть, как потребитель делает запрос к сервису умножения без кэша. Каждый раз, когда потребитель должен получить результат, ему приходится запрашивать у сервиса умножения обработку результата.

Figure 3-1. Multiplication service with cache

В середине рисунка (раздел 2) показано, что кэш был заполнен данными. Теперь, когда делается вызов сервиса, который вычисляет результат, запрос и соответствующий результат сохраняются в кэше. Таким образом, когда поступает повторный запрос, как показано в нижней части рисунка (раздел 3), запрос может быть удовлетворен кэшем, никогда не обращаясь к самому сервису.

Хорошо, какие проблемы решает кэширование?

Как мы обсудили в главе 2, "Что такое кэширование?", есть множество случаев для использования кэширования и множество типов задач, которые можно решить с его помощью. Вот некоторые типичные проблемы, которые кэширование либо решает, либо помогает решить в современном приложении:

  • Performance improvement (Улучшение производительности). Кэширование улучшает задержку (latency). Задержка (latency) - это новый сбой, и если вы можете избежать проведения затратных по времени вычислений (time-intensive calculation), просто используя закэшированный результат, вы можете сократить время ожидания для всех запросов, которые используют кэш. С течением времени это может иметь огромное влияние на производительность вашего приложения.
  • Scaling (Масштабирование). По мере расширения приложения ресурсы могут оказаться ограниченными. Кэширование позволяет приложению сократить необходимость использования избыточных ресурсов (как показано в нашем примере сервиса умножения), что улучшает общий масштаб, на котором приложение может работать, не перегружаясь.
  • Resource optimization (Оптимизация ресурсов). Некоторые ресурсы могут быть довольно дорогостоящими с точки зрения использования вычислительных мощностей, памяти и т. д. Умножение 3 на 4 вообще не стоит ничего, но выполнение большой симуляции данных может быть очень дорогостоящим. Кэширование может сократить необходимость в некоторых из этих операций, что уменьшает требуемые ресурсы и может улучшить пропускную способность (throughput).
  • Convenience and availability (Удобство и доступность). Иногда ресурсы, необходимые для выполнения вычислений, могут быть недоступны (not available). Они могут использоваться для других целей (особенно в высоконагруженном масштабируемом приложении), быть в автономном режиме или просто недоступны. Если результат уже доступен в кэше, вы можете вернуть результат без необходимости использования базовых ресурсов, и, следовательно, недоступность этих ресурсов не будет проблемой.

Проблемы кэширования

Конечно, как и любая другая технология, кэширование также включает компромиссы (trade-offs). Не все ситуации подходят для использования кэширования. Во многих случаях кэширование может не добавлять ценности, а в нескольких случаях оно даже может ухудшить производительность. В частности, есть три вещи, на которые следует обратить внимание при принятии решения о том, нужно ли и как использовать кэш:

  1. Кэширование может привести к тому, что приложение не выполнит желаемые побочные эффекты (side effects) целевых операций.
  2. Несогласованные данные (Inconsistent data) в кэше.
  3. Плохая производительность кэша.

Работа с побочными эффектами

В нашем примере с умножением, сервис ничего не делает, кроме как рассчитывает результат. Он не сохраняет данные, не вносит изменения в другие системы и не контролирует другое программное обеспечение (software) или оборудование (hardware). Он только рассчитывает результат. Сервис считается не имеющим побочных эффектов (side effects).

Сравните это с сервисом, который может выполнять какое-то физическое действие (например, поворачивать руль автомобиля) или может изменять данные в другой системе (например, обновлять данные в записи профиля пользователя). У таких типов сервисов есть побочные эффекты, потому что простой вызов сервиса вызывает изменения в приложении, системе или внешнем мире.

Приложение или сервис считается имеющим побочный эффект, если он изменяет какое-то состояние — виртуальное или физическое — вне локальной среды приложения или сервиса. Часто, если вы можете наблюдать, что приложение что-то сделало, тогда наблюдаемое вами является побочным эффектом.

  • Сервис, который изменяет положение руля автомобиля, имеет наблюдаемый эффект — у него есть побочный эффект.
  • Программное обеспечение, которое сохраняет данные в базе данных, оказывает внешнее воздействие — оно изменяет состояние базы данных.

Часто эти "побочные эффекты" желательны и могут быть критически важны для работы приложения. Хотя кэширование сервисов с побочными эффектами возможно, необходимо обеспечить правильную обработку предполагаемых действий с побочными эффектами.

Неправильное кэширование сервисов с побочными эффектами является причиной многих сбоев программного обеспечения и сбоев системы. Очень легко внести ошибки в систему при добавлении кэширования, если побочные эффекты не учитываются должным образом.

Несогласованные данные в кэше

Что произойдет, если кэш для сервиса умножения не содержит значения 12, хранящегося как результат 3 умножить на 4, а вместо этого содержит значение 13? Тогда кэш считается несогласованным (inconsistent), потому что данные в кэше не соответствуют значению в исходном сервисе.

Хотя в нашем примере сервиса умножения это может показаться маловероятным, в зависимости от выбранного шаблона кэширования поддержание согласованности кэша может быть трудным в некоторых ситуациях. Очень легко потерять синхронизацию закэшированных результатов с ресурсом, который они кэшируют.

Согласованность кэша (Cache consistency) является настолько важной темой, что мы посвятим ей большую часть целой главы — см. Глава 7, "Согласованность кэша".

Производительность кэша

Кэши полезны, потому что обычно они улучшают производительность вашего приложения в каком-то аспекте — будь то задержка (latency), использование ресурсов, пропускная способность (throughout) или какая-то другая мера.

Но это не всегда так. Кэши наиболее эффективны, когда выполняются следующие два критерия:

  1. В шаблоне кэширования на стороне пользователя (cache-aside pattern) ресурсы, необходимые для проверки и заполнения кэша, значительно меньше, чем ресурсы, необходимые для выполнения исходной операции.
  2. Количество раз, когда кэш содержит правильный ответ (и, следовательно, выполнение операции на бэкенде можно избежать), значительно превышает количество раз, когда кэш не содержит актуального ответа (и, следовательно, операция на бэкенде должна выполняться нормально).

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

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

Кроме того, если при проверке наличия правильного ответа в кэше он чаще недоступен, чем доступен, то кэш на самом деле не очень помогает, а накладные расходы на проверку кэша могут ухудшить производительность. Ситуации подобного рода не являются хорошими кандидатами для использования кэширования.

Для получения дополнительной информации о производительности кэша, см. Главу 9, "Производительность кэша".

Вывод

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

  • производительность (performance)
  • масштабируемость (scalability)
  • доступность (availability)
  • надежность (reliability)

Правильно реализованное кэширование имеет высокую ценность в приложениях практически любого размера и сложности. Однако помните, что неправильное кэширование может на самом деле ухудшить производительность. Более того, неправильное кэширование может привести к появлению ошибок и сбоев в вашей системе.

Глава 4. Основные стратегии кэширования

There are three hard things in building software: maintaining cache consistency and off-by-one errors.


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

Исключительное использование кэша

Типичное использование кэша — это временное хранилище данных перед системой записи. Это временное хранилище обычно предоставляет более быстрый доступ к данным, чем постоянное хранилище, либо из-за того, что среда кэша сама по себе физически быстрее (например, оперативная память по сравнению с жестким диском), либо из-за того, что кэш физически или логически расположен ближе к потребителю данных (например, на локальном клиентском компьютере вместо удаленного центра обработки данных).

На базовом уровне такой тип кэша просто хранит дубликаты данных, которые также хранятся в постоянном хранилище данных.

На рисунке ниже показана эта фундаментальная концепция кэша.

Figure 4-1. Cache in front of a persistent store

Когда приложению нужны данные, оно сначала проверяет, находятся ли данные в кэше. Если да, данные считываются напрямую из кэша. Это обычно самый быстрый и надежный способ получения данных. Однако если данных нет в кэше, они должны быть извлечены из основного хранилища данных. После этого данные обычно сохраняются в кэше для последующего использования.

Существует множество способов доступа к кэшу и множество способов хранения и потребления данных в кэше. Также существует множество стандартных стратегий кэширования, архитектур и паттернов использования, которые применяют кэш по-разному. Здесь мы разделим кэширование на стратегии inline и cache-aside, основываясь на том, как данные проходят через кэш.

Inline cache

Inline-кэш, это кэш, который стоит перед хранилищем данных, и хранилище данных доступно через кэш.

Посмотрите на рисунок ниже. Если приложение хочет прочитать значение из хранилища данных, оно пытается прочитать значение из кэша. Если кэш содержит значение, оно просто возвращается. Если кэш не содержит значение, тогда кэш считывает значение из основного хранилища данных. Кэш затем запоминает это значение и возвращает его вызывающему приложению. В следующий раз значение можно будет прочитать напрямую из кэша.

Figure 4-2. Inline cache, in which cache consistency is the responsibility of the cache

Cache-aside

В шаблоне кэширования cache-aside кэш доступен независимо от хранилища данных. В таком паттерне поддержание согласованности кэша — это ответственность приложения.

Когда приложению нужно прочитать значение, оно сначала проверяет, находится ли значение в кэше. Если значения нет в кэше, приложение напрямую обращается к хранилищу данных для чтения нужного значения. Затем приложение сохраняет значение в кэше для последующего использования. В следующий раз значение считывается напрямую из кэша.

В отличие от встроенного кэша, в паттерне кэширования с внешним кэшем нет прямого соединения между кэшем и основным хранилищем данных. Все операции с данными выполняются приложением. Это показано на рисунке ниже.

Figure 4-3. Cache-aside, in which cache consistency is the responsibility of the application

Согласованность кэша

Как уже упоминалось ранее, кэш просто хранит копию данных, содержащихся в основном хранилище данных. Поскольку в кэше хранится копия данных, в случае возникновения проблем или ошибок, возможно, что значение, хранящееся в кэше, будет отличаться от значения, хранящегося в основном хранилище данных. Это может произойти, например, когда исходные данные изменяются, а кэш не обновляется новым значением своевременно. Когда это происходит, кэш считается несогласованным (inconsistent).

Согласованность кэша (Cache consistency) — это мера того, совпадают ли данные, хранящиеся в кэше, со значением исходных данных, хранящихся в основном хранилище данных. Поддержание согласованности кэша необходимо для успешного использования кэша.

Эта проблема иллюстрируется на рисунке ниже. На этой диаграмме приложение изменяет значение данных в основном хранилище данных (изменяет ключ cost со значения 5 на значение 51). В это время кэш сохраняет старое значение (значение 5). Поскольку в кэше хранится значение, отличное от значения в основном хранилище данных, кэш считается несогласованным. Как поддерживать согласованность кэша между кэшем и основным хранилищем данных? Существует множество техник кэширования для успешного поддержания согласованности кэша.

Figure 4-4. Failure in cache consistency

Поддержание согласованности кэша с помощью cache invalidation

Самый базовый способ поддержания согласованности кэша (cache consistency) — это использование инвалидирования кэша (cache invalidation). Инвалидирование кэша — это, попросту говоря, удаление значения из кэша после того, как было определено, что значение устарело.

Взгляните на рисунок ниже. На этой диаграмме значение ключа cost обновляется до значения 51. Это обновление записывается приложением напрямую в хранилище данных. Для поддержания согласованности кэша, как только значение было обновлено в хранилище данных, значение в кэше просто удаляется из кэша либо приложением, либо самим хранилищем данных. Поскольку значение больше не доступно в кэше, приложение должно получить значение из основного хранилища данных. Удаляя недавно ставшее недействительным значение из кэша в паттерне cache-aside, следующее использование значения заставит его быть считанным из основного хранилища данных, гарантируя, что новое значение (51) будет возвращено.

Figure 4-5. Invalidating cache on write

Поддержание согласованности кэша с помощью кэшей write-through

В кэше записи через кэш (write-through), вместо того чтобы приложение напрямую обновляло хранилище данных и инвалидировало кэш, приложение обновляет кэш новым значением, и кэш синхронно обновляет хранилище данных. Это означает, что кэш поддерживает актуальное значение и может по-прежнему использоваться, при этом хранилище данных также содержит новое значение. Кэш отвечает за поддержание собственной согласованности.

Figure 4-6. An attempt to update a cache value

На рисунке выше видно, что ключ cost, хранящийся в хранилище данных, имеет значение 5, и это значение также хранится в кэше. Если приложение теперь хочет обновить это значение до 51, в кэше записи через кэш это значение записывается непосредственно в кэш. Затем кэш обновляет значение в хранилище данных, как показано на рисунке ниже.

Figure 4-7. Write-through cache

Как только запись завершена, и кэш, и хранилище данных имеют одинаковое значение (новое значение 51), и таким образом кэш остается согласованным. Любой другой, обращающийся к значению, будь то из кэша или хранилища данных, получит новое значение, последовательно и правильно.

Стратегии Write-behind/write-back кеширования

Одним из недостатков стратегии write-through является то, что операция записи относительно медленная, потому что вызов записи должен обновить как кеш, так и основное хранилище данных. Это означает, что требуются две записи, и одна из них осуществляется в более медленное основное хранилище данных. Для ускорения этой операции записи можно использовать кеш write-behind.

С кешем write-behind значение обновляется непосредственно в кеше, аналогично подходу write-through. Однако вызов записи затем сразу же возвращается, без обновления основного хранилища данных. С точки зрения приложения, операция записи быстрая, потому что обновляется только кеш.

В этот момент времени кеш содержит новое значение, а хранилище данных содержит старое значение. Чтобы поддерживать согласованность кеша, кеш затем обновляет основное хранилище данных новым значением на более позднем этапе. Обычно это фоновая, асинхронная операция, выполняемая кешем.

Хотя этот процесс обеспечивает более быструю операцию записи для приложения, существует компромисс. До тех пор, пока кеш не обновит хранилище данных новым значением, кеш и хранилище данных содержат разные значения. Кеш имеет правильное значение, а основное хранилище данных - неправильное или устаревшее значение. Это исправляется, когда операция write-behind в кеше обновляет хранилище данных, но до тех пор кеш и хранилище данных остаются несогласованными.

Это не будет проблемой, если весь доступ к ключу осуществляется через этот кеш. Однако если возникает ошибка и данные запрашиваются непосредственно из основного хранилища данных или другим способом, возможно, что старое значение будет возвращено в течение некоторого времени. Будет ли это проблемой, зависит от требований вашего приложения. См. Рисунок 4-8.

Figure 4-8. Write-behind cache

Очистка кеша

Фундаментально, кеш эффективен до тех пор, пока он содержит данные, необходимые приложению. Поскольку цель кеша — обеспечить более высокую производительность и/или доступность данных по сравнению с основным хранилищем данных, кеш полезен, пока он содержит нужные значения.

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

Первоначально это обычно не является проблемой. В стратегии cache-aside по мере чтения значений они просто вставляются в кеш. Но со временем кеш начинает заполняться данными. Когда кеш полон и требуется сохранить новые данные, как кеш справляется с этой задачей? В некоторых случаях ответственность за очистку кеша перекладывается на приложение. Это обычно называется политикой All-In или политикой без очистки.

Однако чаще всего кеш будет удалять старые или менее часто используемые данные, чтобы освободить место для новых данных. Тогда, если какое-то приложение в будущем потребуется эти старые данные, оно должно будет снова получить их из основного хранилища данных. Этот процесс удаления старых или менее часто используемых данных называется очисткой кеша, потому что данные изгоняются или удаляются из кеша.

Разные реализации кеша управляют процессом очистки различными способами в зависимости от конкретных потребностей использования. Но существует несколько общих методов, которые часто применяются.

Политика наименее недавно использованных данных (LRU)

В кеше с политикой очистки наименее недавно использованных данных (Least-recently used, LRU), когда кеш полон и требуется сохранить новые данные, кеш освобождает место для новых данных, анализируя данные, уже хранимые в кеше. Он находит данные, которые не были использованы в течение самого длительного времени, и удаляет их из кеша, освобождая место для новых данных.

Предположение, лежащее в основе кеша LRU, состоит в том, что данные, которые не использовались недавно, с меньшей вероятностью будут нужны в будущем. Поскольку цель кеша — сохранять данные, которые, вероятно, будут нужны в будущем, удаление данных, которые не использовались недавно, помогает сохранить часто используемые данные доступными в кеше.

Политика наименее часто используемых данных (LFU)

В кеше с политикой наименее часто используемых данных (Least-frequently used, LFU), когда кеш полон и требуется удалить данные, кеш ищет данные, к которым обращались наименьшее количество раз, и удаляет эти данные, чтобы освободить место для новых данных.

Разница между кешами LRU и LFU незначительна. LRU использует количество времени, прошедшее с последнего доступа к данным, в то время как LFU использует количество раз, когда данные были запрошены. Иными словами, LRU основывает свое решение на дате последнего доступа, а LFU — на количестве обращений.

Политика самых старых данных

В кеше с политикой самых старых данных, когда кеш полон, кеш ищет данные, которые хранятся в кеше дольше всего, и удаляет их первыми. Эта политика очистки не является распространенной в корпоративных кешах.

Иногда это называется кешем "первый вошел — первый вышел" (FIFO). Данные, которые были первыми вставлены в кеш, первыми и удаляются.

Случайное удаление (Random eviction)

В кеше со случайным удалением, когда кеш заполнен, случайным образом выбранные данные удаляются из кеша. Этот метод не является часто используемой техникой удаления данных, обычно выбирается один из алгоритмических подходов. Однако, эта техника проста для реализации кешем и, следовательно, быстра в исполнении. Но такие типы кешей с большей вероятностью удаляют неправильные данные, что означает, что они склонны создавать большее количество промахов кеша позже, когда вновь требуется доступ к удаленным данным. Вот почему случайное удаление обычно не используется в производственных средах.

Удаление по времени жизни (Time-to-live, TTL) eviction

При удалении данных по времени жизни (TTL eviction) значения данных имеют определенный период времени — возможно, секунды, минуты, часы, дни, годы — в течение которого они должны храниться в кеше. После истечения этого периода значения удаляются из кеша, независимо от того, заполнен ли кеш. В Redis это делается на уровне ключа, а не на уровне политики удаления кеша.

Управление сессиями является распространенным примером использования удаления по TTL. Объект сессии, хранящийся в кеше, может иметь установленное время жизни (TTL), которое представляет время, в течение которого система ждет перед тем, как завершить сеанс неактивного пользователя. Каждый раз, когда пользователь взаимодействует с сессией, значение TTL обновляется и откладывается. Если пользователь не взаимодействует до конца периода TTL, сессия удаляется из кеша, и пользователь фактически выходит из системы.

Постоянный кеш (Cache persistence)

В постоянном кеше данные никогда не удаляются. Если кеш заполняется данными, новые данные не будут храниться в кеше до тех пор, пока данные не будут удалены из кеша с помощью какого-то другого механизма, иногда вручную. Это означает, что новые данные, самые недавно запрашиваемые данные, являются данными, которые фактически "удаляются", потому что им не разрешено храниться в полном кеше с самого начала.

Это проще и эффективнее для реализации кеша, так как не требует никакого алгоритма удаления. Этот метод можно использовать в случаях, когда кеш по размеру не меньше основного хранилища данных. (Конечно, это нечасто, так как большинство кешей не хранят все данные из основного хранилища данных. Обычно кэшируются только определенные наборы данных, такие как сессии.) Если у кеша достаточно емкости для хранения всех данных, то нет шансов, что кеш когда-либо заполнится, и, следовательно, удаление данных никогда не требуется.

Перегрузка кеша (Cache thrashing)

Иногда значение удаляется из кеша, но затем вскоре снова запрашивается и, следовательно, должно быть снова получено. Это может вызвать удаление других значений из кеша, что, в свою очередь, требует их повторного получения позже при запросе. Это движение туда-сюда может привести к состоянию, известному как "перегрузка кеша" (cache thrashing), что снижает эффективность кеша. Перегрузка кеша обычно происходит, когда кеш заполнен и не использует наиболее подходящий тип удаления для конкретного случая использования. Часто простая настройка алгоритма удаления или изменение размера кеша могут уменьшить перегрузку.

Сравнение типов удаления (Comparing eviction types)

Таблица 4-1 сравнивает различные техники удаления данных.

Table 4-1. Cache eviction strategies

Нет правильной или неправильной стратегии удаления данных, правильный выбор зависит от потребностей и ожиданий вашего приложения. Чаще всего LRU (Least Recently Used) или LFU (Least Frequently Used) являются лучшим выбором, но какой из этих двух зависит от конкретных шаблонов использования. Обычно требуется анализ шаблонов доступа к данным и их распределения, чтобы определить правильный тип удаления для конкретного приложения, но иногда метод проб и ошибок является лучшей стратегией для выбора подходящего алгоритма. Стратегия удаления самых старых данных также является опцией, которую можно попробовать и сравнить с LRU и LFU. Опция случайного удаления используется не очень часто. Вы можете протестировать ее в своем приложении, но в большинстве случаев одна из других стратегий работает лучше. Некоторые приложения требуют максимальной производительности при равномерном распределении доступа к данным, поэтому удаление данных не допускается. Но при использовании этой опции управление пространством становится проблемой, которую необходимо решать соответствующим образом.

Теплые и холодные кеши (Warm vs. cold caches)

Во многих случаях, особенно для сессионных кешей, кеши в стороне (cache-aside) пусты при первом запуске кеша. Но статические кеши данных часто "заполняются" для избежания низкой производительности при запуске и регидратации. В этом случае все запросы на данные из кеша будут неуспешными (промах кеша), потому что данные еще не сохранены в кеше. Это часто называется холодным кешем (cold cache).

По мере того, как приложения запрашивают данные, данные считываются из постоянного хранилища данных и сохраняются в кеше. Со временем все больше и больше данных сохраняется в кеше и становится доступным для использования потребительскими приложениями. Со временем это приводит к уменьшению числа промахов кеша и увеличению числа попаданий в кеш. Производительность кеша улучшается с течением времени. Это называется теплым кешем (warm cache).

Процесс первоначального заполнения кеша данными называется прогревом кеша (warming the cache) или просто * разогревом кеша (cache warmup). Когда данные добавляются постепенно со временем, этот процесс называется * предзагрузкой (pre-fetching)**.

Redis как кеш (Redis as a cache)

Redis является идеальным приложением для кеширования данных. Он работает в памяти (RAM), что означает, что он быстрый. Redis часто используется как кеш-фронтенд для какого-то другого, более медленного, но более постоянного хранилища данных, такого как SQL база данных. Redis также может сохранять свои данные, что может быть использовано для различных целей, включая автоматическое прогревание кеша во время восстановления.

Как кеш, Redis с открытым исходным кодом обычно используется в стратегии кеш в стороне (cache-aside), и, как таковая, логика программирования для управления кешем обычно находится в самом приложении, которое его использует.

Удаление данных из кеша в Redis (Cache eviction with Redis)

Обычно кеш Redis хранит меньше данных, чем основное хранилище данных, поэтому удаление данных из кеша является важным аспектом, который нужно учитывать.

По умолчанию, когда база данных Redis заполняется, будущие записи в базу данных просто будут неуспешными, предотвращая вставку новых данных в базу данных. Этот режим может быть использован для реализации политики постоянного удаления данных из кеша, описанной ранее.

Однако Redis можно настроить на другой режим работы при заполнении, установив следующую опцию:

maxmemory-policy allkeys-lru

Когда база данных заполняется, старые данные будут автоматически удаляться из базы данных в соответствии с политикой удаления, при которой в первую очередь удаляются наименее недавно использованные данные. Этот режим может быть использован для реализации кеша LRU (наименее недавно использованные).

Redis также можно настроить как кеш LFU (наименее часто использованные), используя опцию удаления данных LFU:

maxmemory-policy allkeys-lfu

Redis может также реализовывать другие алгоритмы удаления данных. Например, он может реализовывать случайное удаление данных:

maxmemory-policy allkeys-random

Другие, более сложные конфигурации также могут быть использованы. Например, вы можете назначить время истечения для отдельных кешированных значений и установить разные времена для разных значений. Эти значения будут автоматически удаляться по истечении их времени. Кроме того, если база данных заполняется до того, как было установлено время удаления, вы можете использовать стратегию удаления LRU или случайного удаления, которая приоритетно удаляет значения с установленным временем истечения, оставляя в базе данных более долго значения без времени истечения:

maxmemory-policy volatile-lru
maxmemory-policy volatile-lfu
maxmemory-policy volatile-random

Это дает множество возможностей для реализации сложных и специфичных для приложения стратегий удаления данных.

Алгоритмы приближения (Approximation algorithms)

Следует отметить, что политики удаления LRU и LFU в Redis являются приближенными. При использовании опции истечения LRU Redis не всегда удаляет истинное наименее недавно использованное значение, когда необходимо удалить значение. Вместо этого он выбирает несколько ключей и удаляет наименее недавно использованный ключ среди выбранного набора.

Эти алгоритмы приближения, на практике, очень близки к статистическим ожиданиям алгоритма. Но для их реализации требуется значительно меньше памяти, что делает больше памяти доступной для хранения значений данных и ключей. Также можно тонко настроить алгоритмы приближения, чтобы они лучше соответствовали вашим потребностям.я

Глава 5. Продвинутые архитектуры и шаблоны кэширования

Кеши обладают множеством возможностей, особенностей и сценариев использования, которые выходят за рамки простого хранения пар "ключ-значение". Эта глава обсуждает некоторые из более расширенных аспектов кеширования (advanced aspects of caching).

Постоянство и регидратация кеша

Постоянство кеша (Cache persistence) - это способность сохранять содержимое кеша в постоянном хранилище, чтобы при сбое питания или другой аварии не терялось содержимое кеша.

Временный кеш (Volatile cache) - это кеш, который существует в памяти и может быть стерт при сбое питания или перезагрузке системы. В случае сбоя или другой проблемы содержимое временного кеша не может быть доступно — система должна всегда функционировать, даже если сам кеш удален. Производительность системы может быть затронута, но возможности и функциональность не должны пострадать.

С помощью постоянства кеша содержимое сохраняется даже при сбоях питания и перезагрузке системы. Приложение может рассчитывать на то, что объект будет храниться в кеше постоянно. Постоянный кеш (Persistent cache) может быть выбран по соображениям производительности, если допустимо, чтобы приложение не работало, если значение будет удалено неправильно.

Обычно временный кеш реализуется в оперативной памяти (RAM) или другом высокопроизводительном, непостоянном хранилище. Постоянный кеш обычно опирается на жесткий диск, SSD или другое долговременное постоянное хранилище.

Возможно реализовать постоянный кеш в временной памяти, если механизм кеша создает соответствующие резервные копии содержимого кеша в постоянной памяти. В этой ситуации, если кеш выйдет из строя (например, из-за сбоя питания или перезагрузки системы), так что временная память будет очищена, тогда резервная копия кеша в постоянной памяти будет использована для восстановления содержимого кеша во временной памяти до того, как кеш снова станет доступным. Этот процесс называется регидратацией кеша (cache rehydration).

Реджидратация кеша (Cache rehydration) может быть выполнена как из резервного постоянного хранилища, так и из резервного хранилища, которое предоставляет эталонную копию необходимых данных, как показано на рисунке 5-1.

Figure 5-1. Cache rehydration

Redis может работать как временный, так и как постоянный кеш. Он использует RAM для основного хранения памяти, делая его подобным временному кешу, однако постоянное хранилище может быть использовано для обеспечения резервного копирования и восстановления, так что Redis может быть использован как постоянный кеш.

Постоянство Redis (Redis persistence)

Redis предлагает ряд опций для обеспечения постоянства, в частности:

  1. Файлы только для добавления (Append-only files, AOF)
  2. Резервные копии по точкам во времени (Point-in-time backups, RDB)
  3. Комбинация обоих методов

Эти опции предоставляют различные варианты для создания постоянных данных по мере необходимости, сохраняя при этом преимущества производительности, связанные с использованием RAM.

Файлы только для добавления (Append-only files, AOF)

Redis использует файл, называемый файлом только для добавления (AOF), для создания постоянной резервной копии основного временного кеша в постоянном хранилище. Файл AOF сохраняет лог изменений кеша в реальном времени. Этот файл обновляется непрерывно, поэтому он представляет собой точное, постоянное отображение состояния кеша на момент его завершения, в зависимости от конфигурации и сценариев отказа. Когда кеш перезапускается и очищается, команды, записанные в лог-файле AOF, могут быть воспроизведены для восстановления состояния кеша Redis на момент завершения работы. Результат? Кеш, реализованный в основном во временной памяти, может использоваться как надежный постоянный кеш данных.

Опция APPENDONLY yes включает лог-файл AOF. Все изменения в кеше будут записываться в этот лог-файл, но сам лог-файл не обязательно будет немедленно сохранен в постоянное хранилище. Для повышения производительности можно задержать запись в постоянное хранилище на определенное время. Это контролируется с помощью APPENDFSYNC. Доступны следующие опции:

  • APPENDFSYNC no: позволяет операционной системе кешировать лог-файл и ожидать его сохранения в постоянное хранилище, когда это необходимо.
  • APPENDFSYNC everysec: заставляет записывать лог-файл AOF в постоянное хранилище раз в секунду.
  • APPENDFSYNC always: заставляет лог-файл AOF записываться в постоянное хранилище немедленно после создания каждой записи в логе.

Для полной безопасности и гарантии, что ваш кеш работает как постоянный правильно, следует использовать команду * APPENDFSYNC always, так как это единственный способ гарантировать, что сбой системы не приведет к потере данных. Однако, если ваш бизнес может мириться с некоторой потерей кеша при сбое системы, тогда опции everysec и no* могут быть использованы для улучшения производительности.

Результат команды APPENDONLY - это непрерывно растущий лог-файл. Redis может в фоновом режиме перестраивать лог-файл, удаляя ненужные записи в лог-файле. Например, если вы:

  1. Добавили запись в кеш
  2. Изменили значение записи
  3. Снова изменили значение записи
  4. Удалили запись

Тогда в лог-файле будет четыре записи. В конечном итоге все четыре записи больше не нужны для восстановления кеша, так как запись теперь удалена. Следующая команда очистит лог-файл:

BGREWRITEAOF

Результат - лог-файл с наименьшей последовательностью команд, необходимых для восстановления текущего состояния кеша в памяти. Вы можете заставить эту команду выполняться автоматически, что является рекомендуемой лучшей практикой, а не вручную.

Резервные копии по точкам во времени с Redis

Иногда полезно создать резервную копию текущего содержимого в кеше. Для этого предназначена команда RDB backup. Эта команда создает высокоэффективный, минимальный по размеру, файл резервной копии состояния кеша на текущий момент времени. Она может быть выполнена в любой момент времени следующим образом:

SAVE

Эта команда создает файл dump.rdb, содержащий полный текущий снимок кеша Redis. В качестве альтернативы вы можете выполнить следующую команду:

BGSAVE

Эта команда возвращается немедленно и создает фоновую задачу, которая создает снимок.

Сравнение RDB и AOF для обеспечения постоянства

Если ваша цель - создать надежный, постоянный кеш, который может пережить сбои процесса, сбои системы и другие системные ошибки, то единственный надежный способ сделать это - использовать AOF persistence с APPENDFSYNC, установленным на always. Никакой другой метод не гарантирует, что все состояние кеша будет правильно сохранено в постоянном хранилище в любое время. Если ваша цель - поддерживать серию резервных копий по точкам во времени для исторических целей и восстановления системы (например, сохранять одну резервную копию в день в течение целого месяца), тогда метод RDB backup - правильный способ создать эти резервные копии. Это связано с тем, что RDB является единым файлом, предоставляющим точный снимок базы данных в данный момент времени. Этот снимок гарантированно консистентен. Однако RDB ** не может быть использован для выживания в случае системных сбоев, так как любые изменения, сделанные в системе между снимками RDB**, будут потеряны при системном сбое.

Таким образом, в зависимости от ваших требований, и RDB, и AOF могут быть использованы для решения ваших потребностей в постоянстве. Используемые вместе, они могут обеспечить как устойчивый к сбоям постоянный кеш, так и исторические резервные копии по точкам во времени.

Смешанное кеширование RAM/SSD с Redis Enterprise

Открытый Redis требует, чтобы весь кеш, включая ключи и значения ключей, хранился только в RAM. Однако в Redis Enterprise вы можете настроить Redis на хранение значений ключей как в RAM, так и в флэш-памяти SSD. Это позволяет значительно увеличить объемы кеша. Хотя кеширование не является его основным применением, эта функция, называемая * Redis on Flash (RoF)*, является частью Redis Enterprise и может быть полезной в средах кеширования.

В RoF все ключи данных по-прежнему хранятся в RAM, но значения этих ключей умно хранятся в смеси RAM и флэш-памяти SSD. Значения хранятся на основе политики удаления наименее недавно использованных данных (LRU). Более активно используемые значения хранятся в RAM, а менее используемые значения хранятся в SSD

.

Так как хранилище SSD значительно больше и дешевле, чем RAM (хотя и не столь быстрое), использование RoF может позволить вам создавать значительно большие кеши более экономично.

Обратите внимание, что использование постоянной флэш-памяти SSD автоматически не преобразует ваш кеш в постоянный кеш. Это потому, что ключи все еще хранятся в RAM, независимо от того, где хранятся значения ваших данных, в RAM или SSD. Поэтому использование RoF с хранилищем SSD не устраняет необходимость создания файлов AOF и/или RDB для создания настоящего постоянного кеша.

Выполнение функций в кеше

Redis позволяет выполнять произвольные функции внутри самой базы данных кеша. Это полезно для ряда процессов выполнения приложений в базе данных. По сути, вы можете выполнять полноценные скрипты на Python (с добавлением других языков в будущем) в среде выполнения экземпляра Redis.

Как простой пример, представьте, что у вас есть в базе данных Redis несколько Hash maps, которые представляют информацию о пользователях, такую как имя, фамилия и возраст. Тогда вы можете использовать команду RG.PYEXECUTE, чтобы выполнить скрипт на Python для очистки данных. Вот пример скрипта, который удаляет всех пользователей, младше 35 лет:

RG.PYEXECUTE "GearsBuilder().filter(lambda x: int(x['value']['age']) > 35).foreach(lambda x: execute('del', x['key'])).run('user:*')"

Модуль RedisGears - это безсерверный механизм для обработки транзакций, пакетных и событийно-управляемых данных, который создает мощную среду выполнения, позволяющую создавать сложные механизмы кеширования. Например, вы можете реализовать встроенные (inline) или в стороне (aside) кеши, взаимодействующие с другими базами данных изнутри RedisGears. Он может быть использован для реализации паттернов кеширования write-through и write-behind.

Архитектуры микросервисов

Redis имеет много применений при построении архитектур на основе микросервисов. Наиболее распространенный случай использования - это асинхронный канал связи (asynchronous communications channel). Redis может реализовать высокоскоростную очередь для отправки команд, ответов и других данных асинхронно между соседними сервисами. Это показано на рисунке 5-2, где Сервис A (Service A) настроен на отправку сообщений в Сервис B (Service B), используя объект Redis Lists в экземпляре Redis. Этот случай использования описан в типе данных Redis Lists позже в этой главе.

Микросервисы также могут использовать Redis как классический кеш. Это может быть внутренний, серверный кеш, хранящий промежуточные данные, используемые внутри сервиса. Более конкретно, экземпляр Redis может быть использован как кеш в стороне (cache-aside), стоящий перед более медленным хранилищем данных, как показано в примере кеша данных Redis на рисунке 5-3. Паттерны кеша в стороне описаны более подробно в главе 4 "Основные стратегии кеширования".

Figure 5-2. Redis List as a FIFO command queue for microservices

Для получения дополнительной информации смотрите "Как Redis упрощает шаблоны проектирования микросервисов" на The New Stack (https://thenewstack.io/how-redis-simplifies-microservices-design-patterns).

На стороне клиента Redis может быть использован для кеширования промежуточных результатов, стоящих перед вызовами к серверным службам, уменьшая необходимость вызова серверных служб. Это показано на рисунке 5-4.

Наконец, кеш может быть вставлен между двумя службами и использоваться на уровне сети, предоставляя возможности кеширования на уровне HTTP. Кеширование на уровне HTTP управляется с помощью HTTP-заголовков, таких как Cache-Control, Expires, Last-Modified. Эти заголовки обычно обрабатываются посредниками, такими как обратные прокси-серверы (например, Nginx). Это показано на рисунке 5-5.

Figure 5-3. Redis as a distributed cache Figure 5-4. Caching interim result Figure 5-5. Network protocol layer cache

Поиск в кеше

RediSearch, модуль, доступный в Redis Enterprise, представляет собой вторичный индекс, механизм запросов и полнотекстовый поисковый движок с открытым исходным кодом над Redis. Он предоставляет возможности прямого поиска, аналогичные поисковым системам, в экземпляре Redis. Хотя можно реализовать поисковый движок, используя стандартный экземпляр Redis, модуль RediSearch упрощает многие сложности создания поискового движка. Более важно, RediSearch позволяет выполнять SQL-подобные запросы в базе данных Redis, извлекая выгоду из улучшенной производительности на вторичных индексах.

Модуль RediSearch позволяет создать индекс ключей, содержащих типы данных Hash. Индекс представляет собой атрибуты, которые вы планируете запросить в пределах всех ключей Redis, включенных в индекс. После создания индекса, термины поиска могут быть применены к индексу для определения, какие ключи Hash содержат данные, соответствующие терминам поиска. Это показано на рисунке 5-6.

Figure 5-6. Redis instance with RediSearch search index

Пример кода:

redis> HSET user:100 username lee firstname Lee lastname Atchison middleinitial A zipcode 99112
redis> HSET user:101 username john firstname John lastname Smith middleinitial D zipcode 54021
redis> HSET user:102 username dave firstname David lastname Johnson middleinitial J zipcode 12055

Далее, создаем индекс поиска:

redis> FT.CREATE user_idx PREFIX 1 "user:" SCHEMA username TEXT firstname TEXT lastname TEXT middleinitial TEXT zipcode

Это создает индекс поиска, используя все записи Hash, у которых ключ начинается с "user:".

Теперь можно выполнить поиск по этому индексу:

redis> FT.SEARCH user_idx "john" LIMIT 0 10

...возвращает данные из ключей "user:101" и "user:102".

Расширенная обработка данных

Типичный кеш использует систему "ключ-значение". Ключ представляет идентификатор для значения, хранящегося в кеше. Но каким является тип данных значения? В типичном кеше это обычно либо числовое значение, либо строковое представление другого типа данных. В Redis основным типом данных является String. Однако Redis поддерживает многие другие типы данных в поле значения.

Списки (Lists)

Один ключ Redis может иметь значение, представляющее список строк. Существуют специализированные команды для манипулирования этим списком, которые позволяют использовать его для различных целей. Например, вы можете реализовать простую очередь FIFO (первым пришел - первым ушел), используя команды LPUSH (левый push) и RPOP (правый pop). Эти команды будут добавлять (push) строку в левую часть списка и удалять (pop) строку с правой стороны списка. Это позволяет отправлять строки в простой модели FIFO. Например:

redis> LPUSH thelist "AAA"
1
redis> LPUSH thelist "BBB"
2
redis> LPUSH thelist "CCC"
3
redis> RPOP thelist
"AAA"
redis> RPOP thelist
"BBB"
redis> RPOP thelist
"CCC"
redis> RPOP thelist
nil

Тип данных Lists чаще всего используется для очередей и целей планирования, и он может быть использован для этих целей в некоторых сценариях кеширования. Некоторые кеши используют очереди, такие как Redis Lists, для упорядочивания или приоритизации данных, хранящихся в кеше.

Наборы (Sets)

Один ключ Redis может содержать набор строк. Набор Redis - это неупорядоченный список строк. В отличие от типа данных * Redis Lists, тип данных Redis Sets* не диктует порядок вставки или удаления. Кроме того, в наборах Redis данное значение данных (данная строка) должно быть уникальным. Таким образом, если вы попытаетесь вставить одно и то же строковое значение дважды в один и тот же набор, значение будет вставлено только один раз.

Наборы (Sets) очень полезны для операций, таких как определение существования, и как таковые весьма полезны в кешах. Основной командой вставки является SADD, основной командой удаления - SREM, а основной командой запроса - * SISMEMBER. Весь набор можно просмотреть с помощью команды SMEMBERS*. Пример их совместной работы:

redis> SADD theset "AAA"
1
redis> SADD theset "BBB"
1
redis> SMEMBERS theset
1) "AAA"
2) "BBB"
redis> SADD theset "CCC"
1
redis> SADD

 theset "BBB"
0
redis> SMEMBERS theset
1) "AAA"
2) "BBB"
3) "CCC"
redis> SISMEMBER theset "AAA"
1
redis> SISMEMBER theset "BBB"
1
redis> SISMEMBER theset "DDD"
0
redis> SREM theset "BBB"
1
redis> SMEMBERS theset
1) "AAA"
2) "CCC"

В приложении кеша наборы могут использоваться для тестирования повторяющихся операций. Например, вы можете использовать набор для определения, была ли данная команда запрошена с одного и того же IP-адреса недавно:

redis> SADD command1:ipaddr "10.3.1.12"
redis> SADD command1:ipaddr "10.21.23.43"
redis> SADD command1:ipaddr "22.101.15.31"
redis> SADD command1:ipaddr "10.3.1.12"
redis> SMEMBERS command1:ipaddr
1) "10.3.1.12"
2) "10.21.23.43"
3) "22.101.15.31"
redis> SMEMBERS command1:ipaddr "10.21.23.43"
1
redis> SMEMBERS command1:ipaddr "15.3.2.11"
0

Хеши (Hashes)

redis> HSET user:100 username lee firstname Lee lastname Atchison middleinitial A zipcode 99112
redis> HSET user:101 username john firstname John lastname Smith middleinitial D zipcode 54021
redis> HSET user:102 username dave firstname David lastname Johnson middleinitial J zipcode 12055

Глава 6. Масштабирование кэша

Кеши чрезвычайно важны для построения больших, высокомасштабируемых приложений. Они улучшают производительность приложений и снижают требования к ресурсам, что позволяет обеспечить большую общую масштабируемость приложений.

Но что делать, когда сам кеш нуждается в масштабировании?

Когда ваше приложение достигнет определенного размера и масштаба, даже ваш кеш столкнется с ограничениями производительности. Существует два типа ограничений, с которыми обычно сталкиваются кеши: ограничения по хранению и ограничения по ресурсам.

Ограничения по хранению (Storage limits) — это ограничения на количество доступного пространства для кеширования данных. Рассмотрим простой кеш службы, где результаты службы хранятся в кеше для предотвращения избыточных вызовов службы. Кеш имеет место только для определенного количества результатов запросов. Если это количество уникальных запросов превышено, то кеш заполнится, и некоторые результаты будут отброшены. Полный кеш достиг своего предела хранения, и кеш может стать узким местом для продолжающегося масштабирования приложения.

Ограничения по ресурсам (Resource limits) — это ограничения на способность кеша выполнять свои необходимые функции — хранение и извлечение кешированных данных. Обычно эти ресурсы — это либо пропускная способность сети для извлечения результатов, либо вычислительные мощности для обработки запроса. Рассмотрим тот же простой кеш службы. Если один запрос делается многократно и результат кешируется, вы не столкнетесь с ограничениями по хранению, так как нужно кешировать только один результат. Однако, чем больше делается один и тот же запрос, тем чаще один и тот же результат будет извлекаться из кеша. В какой-то момент количество запросов станет таким большим, что кеш исчерпает ресурсы, необходимые для извлечения значения.

Улучшение масштабируемости кеша

Для того чтобы улучшить масштабируемость кеша, вы должны увеличить лимиты хранения и/или лимиты ресурсов по мере необходимости, чтобы избежать воздействия этих ограничений на производительность вашего кеша. Аналогично тому, как масштабируются части вычислительной системы, существует два основных способа увеличения масштаба вашего кеша: * вертикальное масштабирование (vertical scaling) и горизонтальное масштабирование (horizontal scaling)*.

Вертикальное масштабирование (vertical scaling), или масштабирование вверх и вниз, включает в себя увеличение ресурсов, доступных для работы кеша. Обычно это связано с переходом на более мощный компьютер, на котором работает кеш. Для кеша, работающего в облаке, это часто означает переход на более крупный экземпляр.

Вертикальное масштабирование может увеличить количество доступной для кеша оперативной памяти (RAM), что снижает вероятность достижения кешем предела хранения. Но оно также может добавить более мощные процессоры и увеличить пропускную способность сети, что снижает вероятность достижения кешем предела ресурсов.

Горизонтальное масштабирование (horizontal scaling), или масштабирование вширь и вовнутрь, включает в себя добавление дополнительных узлов компьютеров в кластер экземпляров, которые работают с кешем, без изменения размера каждого отдельного экземпляра. В зависимости от того, как оно реализовано, горизонтальное масштабирование также может улучшить общую надежность кеша, а следовательно, и доступность приложения.

Другими словами, вертикальное масштабирование означает увеличение размера и вычислительной мощности одного экземпляра или узла, в то время как горизонтальное масштабирование включает увеличение количества узлов или экземпляров.

Техники горизонтального масштабирования

Существует множество различных способов реализации горизонтального масштабирования, каждый из которых имеет свой набор преимуществ и недостатков.

Реплики для чтения (Read replicas)

Реплики для чтения — это техника, используемая в открытом исходном коде Redis для улучшения производительности чтения кеша без значительного влияния на производительность записи. В типичном простом кеше кеш хранится на одном сервере, и доступ как для чтения, так и для записи к кешу осуществляется на этом сервере.

С репликами для чтения копия кеша также хранится на вспомогательных серверах, называемых репликами для чтения. Реплики получают обновления от основного сервера. Поскольку каждый из вспомогательных серверов имеет полную копию кеша, запрос на чтение кеша может обращаться к любому из вспомогательных серверов, так как у всех них есть один и тот же результат. Поскольку они распределены по нескольким серверам, значительно большее количество запросов на чтение может быть обработано, и они будут обработаны быстрее.

Когда происходит запись в кеш, запись выполняется в основной экземпляр кеша. Этот основной экземпляр затем отправляет сообщение, указывающее, что изменилось в кеше, всем репликам для чтения, чтобы все экземпляры имели согласованный набор кешированных данных.

Крупная реализация Redis, состоящая как минимум из трех серверов, показана на рисунке 6-1. Все записи в базу данных Redis выполняются в одном основном экземпляре. Этот основной экземпляр отправляет обновления измененных данных всем репликам. Каждая реплика содержит полную копию сохраненной базы данных Redis. Затем любой доступ на чтение к экземпляру Redis может происходить на любом из серверов в кластере.

Figure 6-1. Horizontal scalability with read replicas

Эта модель не улучшает производительность записи, но она может увеличить производительность чтения для крупномасштабной реализации, распределяя нагрузку чтения по нескольким серверам. Кроме того, может быть улучшена доступность — если какая-либо из реплик для чтения выйдет из строя, нагрузка может быть распределена на любые другие серверы в кластере, так что система останется работоспособной. Для повышения доступности, если основной экземпляр Redis выйдет из строя, одна из реплик для чтения может взять на себя роль мастера и принять на себя эти обязанности.

Шардинг (Sharding)

Шардинг — это техника для улучшения общей производительности кеша, а также увеличения его пределов хранения и ресурсов.

С шардингом данные распределяются по различным разделам, каждый из которых содержит только часть кешированной информации. Запрос на доступ к кешу (либо чтение, либо запись) отправляется селектору шарда (в Redis Enterprise это реализовано в прокси), который выбирает соответствующий шард для отправки запроса. В общем случае кеша селектор шарда выбирает соответствующий шард, просматривая ключ кеша для запроса. В Redis выбор шарда реализован прокси, который контролирует пересылку операций Redis в соответствующий шард базы данных. Он использует детерминированный алгоритм для указания, какому шару должен быть отправлен конкретный запрос. Алгоритм детерминированный, что означает, что каждый запрос для данного ключа кеша будет отправлен в один и тот же шард, и только этот шард будет иметь информацию для данного ключа кеша. Шардинг показан на рисунке 6-2.

Figure 6-2. Horizontal scaling via sharding

Шардинг — это относительно простой способ значительно увеличить емкость кеша. Добавив три шарда в реализацию Redis с открытым исходным кодом, например, вы можете почти утроить производительность кеша и утроить пределы хранения.

Но шардинг не всегда прост. В общем случае кешей выбор селектора шарда, который эффективно распределяет трафик по всем узлам, может потребовать настройки. Это также может снизить доступность, увеличивая зависимость от нескольких экземпляров. Сбой одного экземпляра может, если не управлять должным образом, привести к отказу всего кеша.

Redis Clustering решает эти проблемы и делает шардинг проще и легче в реализации. Redis Clustering использует простой CRC16 для ключа, чтобы выбрать один из до 1000 узлов, содержащих необходимые данные. Протокол пересоздания позволяет выполнять балансировку как по емкости, так и по производительности. Протоколы отказоустойчивости улучшают общую доступность кеша.

В Redis с открытым исходным кодом кластеризация реализована на стороне клиента в библиотеке клиента с поддержкой кластера. Это работает, но требует поддержки протокола кластеризации на стороне клиента. Redis Enterprise избегает этих проблем, реализуя прокси-протокол для предоставления кластеризации на стороне сервера, позволяя любому клиенту использовать кластеризированный кеш.

Шардинг — это эффективный способ быстрого масштабирования приложения, и он используется в ряде крупных, высокомасштабируемых приложений. Хотя концепция шардинга имеет свои преимущества и недостатки, Redis Clustering устраняет многие сложности шардинга и позволяет приложениям более эффективно сосредоточиться на аспектах управления данными при масштабировании большого набора данных.

Active-Active (мульти-мастер)

Active-Active, то есть мульти-мастер, репликация — это способ обработки больших нагрузок как на производительность записи, так и на производительность чтения кеша.

Как и в случае с репликами для чтения, Active-Active добавляет несколько узлов в кластер кеша, и копия кеша хранится равномерно на всех узлах. Поскольку каждый узел содержит полную копию кеша, это не

влияет на предел хранения кеша. Балансировщик нагрузки используется для распределения нагрузки между всеми узлами. Это означает, что значительно большее количество запросов может быть обработано и обработано быстрее, поскольку они распределяются по нескольким серверам.

Когда происходит запись на один из узлов кеша, экземпляр, который получает запись, отправляет сообщение, указывающее, что изменилось в кеше, всем другим узлам, так что все экземпляры имеют согласованный набор кешированных данных.

Реализация большого кеша, показанная на рисунке 6-3, состоит как минимум из трех серверов, каждый из которых запускает копию программного обеспечения кеша и каждый с полной копией базы данных кеша. Любой из них может обрабатывать любой тип запроса данных.

Figure 6-3. Multi-master replication

Эта модель также увеличивает общую доступность кеша, поскольку если один узел выйдет из строя, другие узлы могут взять на себя нагрузку.

Но что происходит, когда приходят два запроса на обновление одного и того же значения кешированных данных? В однопоточном кеше запросы сериализуются, и изменения происходят по порядку, при этом последнее изменение обычно переопределяет предыдущие изменения.

Однако в модели мульти-мастера два запроса могут прийти к разным мастерам, и мастера могут затем отправить противоречивые сообщения об обновлении другим мастер-серверам. Это называется конфликтом записи. Алгоритм должен быть написан для разрешения этих конфликтующих записей и определения того, как несколько запросов должны быть обработаны ( например, какой или какие запросы должны быть обработаны, а какие должны быть проигнорированы или в каком порядке запросы должны быть обработаны). Кроме того, может возникнуть задержка данных, что означает, что когда данные обновляются на одном узле, может потребоваться некоторое время, прежде чем они будут обновлены на всех узлах. Следовательно, в течение некоторого времени разные узлы могут содержать разные значения данных. Этот алгоритм может быть подвержен ошибкам и привести к несогласованности кеша. Необходимо принять меры, чтобы такие проблемы не возникали, или по крайней мере успешно устранялись, когда они возникают.

Active-Active Геораспределение в Redis Enterprise

Redis с открытым исходным кодом не поддерживает мульти-мастеровую избыточность. Однако Redis Enterprise поддерживает форму мульти-мастеровой избыточности, называемую Active-Active Геораспределение.

В этой модели несколько мастеров баз данных находятся в разных центрах обработки данных, которые могут быть расположены в разных регионах и по всему миру. Индивидуальные потребители подключаются к экземпляру базы данных Redis, который находится ближе всего к их географическому местоположению. Экземпляры базы данных Active-Active Redis затем синхронизируются в модели мульти-мастера так, чтобы каждый экземпляр Redis всегда имел полную и актуальную копию кешированных данных. Эта модель называется Active-Active, потому что каждый из экземпляров базы данных может принимать операции чтения и записи по любому ключу, и экземпляры являются равноправными узлами в сети.

Redis Enterprise Active-Active Геораспределение имеет сложные алгоритмы для эффективного решения конфликтов записи, включая реализацию конфликт-свободных реплицируемых типов данных (CRDT), которые гарантируют сильную конечную согласованность и делают процесс синхронизации репликации значительно более надежным. Обратите внимание, что приложение должно понимать последствия задержки данных и возникающих конфликтов записи, и должно быть написано так, чтобы эти проблемы не были проблемой.

Резюме техник масштабирования кеша

Таблица 6-1 показывает сводку техник масштабирования, обсуждаемых в этой главе, а также их преимущества и недостатки.

Table 6-1. Scaling technique summary

Какую технику, если вообще какую-либо, вы выберете для использования, зависит от архитектуры вашего приложения и требований, а также от целей вашей организации.

Техника масштабирования Преимущества Недостатки
Вертикальное масштабирование Увеличивает объем доступной RAM, уменьшает вероятность достижения предела хранения Может привести к единой точке отказа, ограничено возможностями одного узла
Реплики для чтения Увеличивает производительность чтения, повышает доступность Не улучшает производительность записи, потенциальная несогласованность данных
Шардинг Значительно увеличивает емкость кеша, улучшает производительность Требует настройки балансировки трафика, потенциальное снижение доступности
Active-Active мульти-мастер Обеспечивает высокую доступность, улучшает производительность чтения и записи Сложность разрешения конфликтов записи, задержка данных

Глава 7. Согласованность кэша

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

В главе 3 "Зачем кеширование?" мы рассмотрели сервис умножения и продемонстрировали, как кеширование может улучшить производительность этого сервиса. Возвращаясь к этому примеру, что происходит, если в сервисе умножения нет значения " 12" как результата "3 умножить на 4", а вместо этого хранится значение "13"?

В этом случае, когда поступит запрос на возвращение результата "3 умножить на 4", будет использовано кешированное значение, а не вычисленное, и сервис вернет "13", очевидно неправильный результат, который, скорее всего, никогда не возникнет в реальном мире.

Это пример кеша, который является несогласованным, потому что он сохранил устаревший ответ на запрос. Иногда бывает сложно понять, что это происходит, и еще труднее удалить эти несогласованные результаты. Это может быть или не быть серьезной проблемой для приложения, в зависимости от того, как приложение использует данные.

Как кеш становится несогласованным?

Существует множество способов, как кеш может стать несогласованным, включая:

  1. Когда изменяются основные результаты, а кеш не обновляется
  2. Когда возникает задержка при обновлении кешированных результатов
  3. Когда существует несогласованность между узлами кеша

Рассмотрим каждую из этих проблем отдельно.

Основные результаты изменяются, а кеш не обновляется

Рассмотрим рисунок 7-1, на котором показано, как сервис запрашивает результат из предположительно медленного источника данных, например, удаленного сервиса или базы данных. Чтобы ускорить чтение данных, используется кеш, который позволяет быстрее получить доступ к часто используемым результатам.

Пользователь запрашивает конкретное значение из медленного источника данных. Сначала проверяется кеш. Если значение не найдено в кеше, запрашивается медленный резервный источник данных, результат сохраняется в кеше, и запрос завершается. Однако, если результат уже сохранен в кеше, то возвращается кешированное значение.

Но что происходит, если значение в основном источнике данных изменилось? В шаблоне cache-aside (кеш в стороне), если старое значение все еще находится в кеше, то на будущие запросы будет возвращаться старое значение, а не новое. Кеш становится несогласованным. Это показано на рисунке 7-2. Чтобы кеш снова стал согласованным, либо старое значение в кеше должно быть обновлено до нового значения, либо старое значение должно быть удалено из кеша, чтобы будущие запросы получали правильное значение из основного источника данных.

Figure 7-1. A simple cache fronting a slow data source

Figure 7-2. Updating the data store without updating cache results in an inconsistent cache.

Задержка при обновлении кешированных результатов

Когда основной источник данных изменяет значение и необходимо обновить кеш с учетом измененного значения, часто отправляется сообщение в кеш с указанием удалить старое значение или обновить кешированное значение до нового, правильного значения. Этот процесс занимает некоторое время, в течение которого в кеше все еще хранится старое, несогласованное значение. Любой запрос, поступающий после изменения значения данных, но до обновления кеша, вернет несогласованное, неправильное значение.

Эта задержка может и должна быть очень короткой — надеюсь, достаточно короткой, чтобы задержка не вызвала серьезных проблем. Однако в некоторых случаях она может быть довольно значительной. Будет ли эта задержка проблемой, полностью зависит от конкретного случая использования, и это решает само приложение.

Кроме того, некоторые кеши могут использоваться для кеширования данных из динамически изменяющегося хранилища данных. Это часто бывает в случае кешей баз данных, где основные данные изменяются время от времени, но регулярно.

В этих случаях одной из стратегий является установка времени истечения срока действия кеша, требующего выброса кешированных значений и повторного чтения из основного хранилища данных через регулярные интервалы, ограничивая время, в течение которого кешированное значение может быть несогласованным. Хотя эта стратегия может сократить продолжительность времени, когда кешированное значение несогласованно, она не устраняет несогласованность полностью. Таким образом, эта стратегия используется только для кеширования динамически изменяющихся данных, когда некоторая погрешность в возврате точного результата приемлема. Примером такого кеша может быть кеширование количества лайков на посте в социальной сети. В этом случае количество лайков изменяется постоянно, но если установить истечение срока действия кеша каждые, скажем, 15 минут, можно гарантировать, что кешированное значение всегда будет точным в пределах последних 15 минут. Значение кеша все еще будет несогласованным, но несогласованность будет минимальной и в пределах допустимых границ для этого приложения.

Несогласованность между узлами кеша

В высокомасштабируемых или высоко распределенных кешах часто используется несколько компьютерных узлов для реализации кеша. Это подробно обсуждалось в главе 6 "Масштабирование кеша".

Во многих сценариях несколько узлов кеша будут иметь дублирующиеся копии всех или части кеша. Алгоритм используется для поддержания актуальности и согласованности значений кеша на всех узлах кеша. Это показано на рисунке 7-3.

Figure 7-3. Replicated cache with consistent data

Figure 7-4. Replicated cache with delayed (inconsistent) data

Глава 8. Кэширование и облако

"Облачно с возможностью кеширования."

Запуск одного экземпляра Redis с открытым исходным кодом на месте довольно прост. Существует несколько вариантов настройки, и ни один из них не является сложным.

Однако в облаке существует множество различных способов настройки и конфигурации Redis в качестве сервера кеша. Фактически, способов настройки кеша Redis больше, чем провайдеров облачных услуг. Эта глава обсуждает некоторые из различных доступных облачных опций.

Услуги Redis у крупных облачных провайдеров

Все крупные облачные провайдеры предлагают базы данных Redis, часто настроенные в первую очередь для целей кеширования, которые легко настроить и администрировать. Они могут использоваться так же, как и любые другие облачные услуги данного провайдера.

Кеши провайдеров облачных услуг

Все крупные облачные провайдеры, включая Amazon Web Services (AWS), Google Cloud, Microsoft Azure и IBM Cloud, предлагают услуги, включающие версию Redis с открытым исходным кодом. Эти услуги доступны в широком диапазоне размеров. Часто они доступны как в виде отдельных экземпляров, так и с балансировкой нагрузки для чтения реплик. Их можно настроить в различных регионах по всему миру.

Эти экземпляры легко настроить и использовать, их можно включать и выключать очень быстро, и обычно они оплачиваются по часам или по количеству использованных ресурсов. Это делает их особенно подходящими для разработки, тестирования и автоскейлинг производственных сред.

Другие поставщики облачных услуг

Несколько других поставщиков услуг предлагают предварительно настроенные, размещенные в облаке версии экземпляров Redis, включая:

  • Redis To Go
  • Heroku
  • ScaleGrid
  • Aiven
  • Redis Cluster (специализируется на размещении Redis на Kubernetes)
  • Digital Ocean
  • cloud.gov (специализируется на правительствах и государственных подрядчиках)

Redis Enterprise Cloud

Redis предлагает облачную версию своего корпоративного программного обеспечения Redis Enterprise, полностью поддерживаемого и расширенного. Redis Enterprise Cloud — это полностью управляемая служба DBaaS, обеспечивающая прозрачную высокую доступность и поддерживающая активное-активное геораспределение, а также гибридные и мультиоблачные развертывания. Redis Enterprise Cloud доступен у трех крупных облачных провайдеров: AWS, Azure и Google Cloud. Например, на Microsoft Azure Redis Enterprise предлагается как полностью поддерживаемая собственная услуга под названием Azure Cache for Redis Enterprise.

Помимо полезности Redis Enterprise Cloud для высокопроизводительных производственных сред, существует также бесплатный уровень, который можно использовать для пробных запусков и целей разработки. Если вы серьезно относитесь к использованию Redis для высоконагруженных производственных рабочих нагрузок, Redis Enterprise Cloud может быть подходящим выбором для вас.

Самостоятельный хостинг в облаке

Вы можете, конечно, установить и запустить Redis самостоятельно, как версию с открытым исходным кодом, так и версию Redis Enterprise, на отдельных облачных вычислительных экземплярах, так же, как вы можете установить и запустить Redis на компьютерах в ваших собственных локальных центрах обработки данных. Ничего магического не требуется. Однако важно убедиться, что настройки безопасности созданы и правильно управляются. Все крупные облачные провайдеры и Redis Enterprise Cloud предлагают стандартные возможности безопасности, основанные на их обычных облачных настройках безопасности — все настроено и предварительно настроено для вас. Если вы настраиваете и запускаете экземпляры самостоятельно, вам необходимо убедиться, что настройки безопасности правильные, сертификаты действительны и безопасность поддерживается.

Гибридные и мультиоблачные развертывания

Как обсуждалось в главе 6 "Масштабирование кеша", вы можете настроить экземпляр Redis с открытым исходным кодом, состоящий из одного основного экземпляра Redis и одного или нескольких реплик для чтения Redis. Эта настройка показана на рисунке 8-1.

Figure 8-1. Read replicas

В типичной облачной настройке эти реплики для чтения размещаются в отдельных зонах доступности для улучшения доступности системы. Однако возможно разместить реплики для чтения в совершенно других облачных регионах, чтобы обеспечить улучшенную производительность чтения в различных географических местах. Это показано на рисунке 8-2.

Figure 8-2. Multi-region read replicas

Теоретически, эта модель работает даже в случаях, когда разные регионы предоставляются разными облачными провайдерами. Единственным условием является то, что команды настройки репликации должны быть доступны для конфигурации облачным провайдером, и эти команды могут быть ограничены на некоторых уровнях обслуживания у некоторых провайдеров. Тем не менее, вы можете вручную настроить Redis на отдельных вычислительных экземплярах у нескольких облачных провайдеров, а затем настроить репликацию так, чтобы ваши реплики для чтения от одного облачного провайдера были подключены к мастеру у другого облачного провайдера. Это показано на рисунке 8-3.

Figure 8-3. Multicloud read replicas

Развертывания кластера Redis Enterprise

Используя базовый Redis с открытым исходным кодом, вы ограничены одним мастером и любым количеством реплик для чтения. Все записи должны отправляться на один основной экземпляр Redis. Это ограничивает полезность для много-региональных развертываний и мультиоблачных развертываний. Это связано с тем, что когда приложение распределено по нескольким регионам и/или нескольким провайдерам, только производительность чтения может быть улучшена за счет указания локальной реплики для чтения, как показано на рисунках 8-2 и 8-3. Запросы на запись все равно должны возвращаться к единственному основному экземпляру. Таким образом, в примере на рисунке 8-2, если приложение в регионе JP хочет записать данные в базу данных Redis, оно должно отправить эту запись в основной экземпляр Redis в регионе US. Это может существенно повлиять на производительность записи.

Чтобы улучшить как производительность записи, так и чтения в много-региональном или мультиоблачном развертывании, вы должны использовать другую архитектуру репликации, чем простая архитектура мастер-реплика, описанная здесь. Вместо этого вы должны создать топологию мультикластера, как показано на рисунке 8-4. Это требует возможности мульти-мастера, которая недоступна "из коробки" в Redis с открытым исходным кодом, но в Redis Enterprise можно развернуть несколько мастеров в нескольких регионах, используя активное-активное развертывание.

Figure 8-4. Multi-region Active-Active replication with Redis Enterprise

В этой модели как чтение, так и запись могут обрабатываться любым из основных экземпляров Redis в любом регионе или у любого провайдера. Это значительно улучшает производительность приложения. После того, как запись произошла в данном регионе, она автоматически реплицируется на все другие мастера в кластере.

Чтобы воспользоваться этим типом кластерной репликации, вы должны использовать Redis Enterprise. Для размещенных в облаке баз данных это означает, что вы должны либо использовать экземпляры Redis Enterprise Cloud, либо развернуть свои собственные экземпляры Redis на облачных вычислительных экземплярах или контейнерных образах. Ни один из крупных облачных провайдеров не предлагает нативные развертывания активное-активное в нескольких регионах, ни развертывания между облачными провайдерами. Для этого типа крупномасштабной, высокодоступной распределенной архитектуры вы должны использовать Redis Enterprise.

Глава 9. Производительность кэша

Производительность кеша — это важный аспект для создания эффективного и высокопроизводительного приложения. Кеширование может значительно улучшить производительность вашего приложения, если все сделано правильно. Однако, если кеширование настроено неправильно, оно может, напротив, ухудшить производительность.

Введение в производительность кеша

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

Основные принципы

Основной целью кеширования является уменьшение времени доступа к данным и снижение нагрузки на основные системы данных. Для этого важно убедиться, что операция кеширования выполняется быстрее и с меньшими ресурсами, чем сама основная операция.

Основные принципы производительности кеша включают в себя:

  1. Эффективность доступа (Access efficiency): Операция чтения из кеша должна быть значительно быстрее, чем чтение из основного хранилища данных.
  2. Эффективность хранения (Storage efficiency): Кеш должен уметь эффективно управлять своим пространством, чтобы максимально использовать доступную память.
  3. Консистентность (Consistency): Обеспечение того, чтобы данные в кеше всегда были актуальными и точными.
  4. Низкая задержка (Low latency): Кеширование должно минимизировать задержки в доступе к данным.

Измерение производительности кеша

Производительность кеша можно измерять с помощью различных метрик, включая:

  • Время ответа (Response time): Время, необходимое для извлечения данных из кеша.
  • Процент попаданий (Hit rate): Процент запросов, которые успешно находят данные в кеше.
  • Процент промахов (Miss rate): Процент запросов, которые не находят данных в кеше и вынуждены обращаться к основному хранилищу.
  • Нагрузка на CPU и память (CPU and memory load): Ресурсы, используемые для управления кешем.

Техники оптимизации производительности

Оптимизация производительности кеша включает несколько подходов:

  1. Использование эффективных стратегий эвикции (Eviction policies): Стратегии, такие как Least Recently Used (LRU) или Least Frequently Used (LFU), помогают определить, какие данные следует удалить из кеша, чтобы освободить место для новых данных.
  2. Настройка размеров кеша (Cache size tuning): Правильная настройка размера кеша позволяет максимизировать процент попаданий и минимизировать процент промахов.
  3. Анализ и оптимизация паттернов доступа (Access pattern analysis): Понимание и оптимизация того, как ваше приложение использует данные, помогает улучшить производительность кеша.
  4. Использование распределенного кеша (Distributed caching): В больших системах использование распределенного кеша, такого как Redis Cluster, может помочь улучшить производительность и надежность.

Производительность Redis

Redis — это мощный инструмент для кеширования, который предлагает множество функций для оптимизации производительности. Некоторые из этих функций включают:

  • Поддержка различных типов данных (Data types support): Redis поддерживает строки, хеши, списки, множества и другие типы данных, что позволяет эффективно кешировать разные виды данных.
  • Механизмы постоянства (Persistence mechanisms): Redis предлагает различные механизмы для сохранения данных, такие как RDB-снапшоты и AOF-логирование, которые помогают сохранять данные между перезагрузками.
  • Поддержка горизонтального масштабирования (Horizontal scaling support): Redis позволяет легко масштабировать кеш за счет использования кластеров и шардирования.

Заключение

Правильное использование и настройка кеша могут значительно улучшить производительность вашего приложения. Понимание основных принципов производительности кеша, использование эффективных стратегий и инструментов, таких как Redis, помогут вам создать высокопроизводительные и масштабируемые системы.

Глоссарий. Терминология кэша

Согласованность кеша (Cache consistency)
Мера того, насколько содержимое кеша соответствует основному хранилищу данных или сервису. Кеш считается согласованным, если возвращаемые им данные корректны. Кеш считается несогласованным, если возвращаемые им данные не совпадают с основным хранилищем данных или сервисом.

Очистка кеша (Cache eviction)
Действие по удалению (часто старых или менее часто используемых) данных из временного кеша, чтобы освободить место для новых данных, когда определено, что данные больше не нужны или менее вероятно, что они будут нужны по сравнению с другими данными в кеше.

Попадание в кеш (Cache hit)
Запрос данных, который может быть удовлетворен с помощью данных, хранящихся в кеше. Если происходит попадание в кеш, это означает, что кеш успешно обработал запрос без обращения к основному хранилищу данных или сервису.

Локальность кеша (Cache locality)
Набор данных, доступных в кеше. Часто, в зависимости от приложения, данные, которые будут нужны в будущем, могут быть предсказаны по данным, использованным в прошлом, и эти предсказанные данные могут быть заранее загружены в кеш. Эти предсказанные данные и являются локальностью кеша. Эта практика распространена для кешей, таких как кеши CPU или памяти, где чтение одного места в памяти часто приводит к доступу к соседним местам памяти.

Промах в кеше (Cache miss)
Запрос данных, который не может быть удовлетворен существующими данными в кеше и должен быть отправлен в основное хранилище данных или сервис для обработки. В случае промаха в кеше, кеш не успешен в удовлетворении запроса.

Издержки кеша (Cache overhead)
Количество времени, превышающее время, которое обычно требуется без кеша, чтобы вернуть желаемый результат данных из хранилища данных или сервиса. Издержки кеша используются при вычислении эффективности кеша для улучшения производительности.

Прогрев кеша (Cache warmup)
Процесс преобразования холодного кеша в теплый кеш. Этот процесс может включать в себя искусственное хранение данных при запуске в кеше, чтобы сделать его теплым кешем. Но чаще всего кеш начинает работать холодным и постепенно прогревается с течением времени по мере поступления большего количества запросов. Результатом является кеш, который переходит от большей части промахов в кеше к большей части попаданий в кеш.

Холодный кеш (Cold cache)
Кеш, который пуст или еще не имеет достаточного количества данных, чтобы запросы могли быть удовлетворены из кеша. Холодный кеш приводит к большому количеству промахов в кеше.

Задержка данных (Data lag)
Время, необходимое для того, чтобы измененное значение в базе данных или кеше, сделанное на одном сервере, было распространено на все остальные серверы в системе.

Сброс кеша (Flush, cache flush)
Действие по очистке кеша от всех данных. Когда кеш был сброшен, он больше не содержит никаких данных, и последующие запросы приведут к промахам в кеше.

Горизонтальное масштабирование (Horizontal scaling)
Действие по масштабированию системы для поддержки больших и большего количества запросов путем добавления дополнительных серверов и других компонентов в систему, а не увеличения размеров этих серверов и компонентов.

Аннулирование кеша (Invalidation, cache invalidation)
Действие по маркировке данных в кеше как неправильных и недействительных. Когда запрос поступает в кеш, соответствующий данным, которые были отмечены как недействительные, он обрабатывается так, как будто данных нет в кеше, и генерируется промах в кеше, вынуждая запрос быть переданным в основное хранилище данных или сервис.

Наименее часто используемый (Least Frequently Used, LFU)
Стратегия очистки кеша, используемая полными кешами для удаления данных, которые менее вероятно будут использованы, чтобы освободить место для данных, которые более вероятно будут использованы. Стратегия LFU удаляет данные, которые были использованы наименьшее количество раз.

Наименее недавно использованный (Least Recently Used, LRU)
Стратегия очистки кеша, используемая полными кешами для удаления данных, которые менее вероятно будут использованы, чтобы освободить место для данных, которые более вероятно будут использованы. Стратегия LRU удаляет данные, которые не использовались на протяжении наибольшего периода времени.

Мульти-мастер (Multi-master)
База данных или кеш, в котором запросы, включая запросы на обновление, могут быть отправлены на более чем один сервер, и сама база данных координирует изменения между серверами, чтобы убедиться, что изменения правильно обновлены.

Постоянный кеш (Permanent cache)
Кеш, в котором данные никогда не удаляются. Если постоянный кеш заполняется данными, то новые данные не могут быть сохранены в кеше до тех пор, пока данные не будут удалены из кеша с помощью какого-то другого метода, часто вручную.

Устойчивый кеш (Persistent cache)
Содержимое устойчивого кеша сохраняется даже после отключения питания и перезагрузки системы. Приложение может полагаться на то, что объект хранится в кеше навсегда. Это может быть важно для производительности, и допустимо, чтобы приложение не выполнялось, если значение было удалено неправильно.

Реплика для чтения (Read replica)
Сервер базы данных или кеша, который предоставляет только для чтения версию базы данных или кеша. Он используется для улучшения масштабируемости операций чтения простым и обычно бесконфликтным способом.

Шардирование (Sharding)
Техника для улучшения общей производительности кеша, а также увеличения его пределов хранения и ресурсов. Она включает в себя разделение базы данных или кеша на отдельные и изолированные компоненты, каждый из которых обрабатывает части общих данных приложения. Ключ шардирования используется для определения, какие части данных идут в один компонент по сравнению с другим. Например, идентификатор клиента может использоваться для размещения клиентов, имена которых начинаются с букв "a" до "e", на одном сервере, а тех, чьи имена начинаются с "f" до "m", на другом сервере, и так далее.

Вертикальное масштабирование (Vertical scaling)
Действие по масштабированию системы для поддержки больших и большего количества запросов, путем увеличения размеров и мощностей отдельных серверов и других компонентов системы, а не добавления большего количества серверов или компонентов параллельно.

Временный кеш (Volatile cache)
Кеш, в котором данные удаляются или извлекаются из кеша, если в кеше больше нет места для хранения новых данных. Обычно удаляются данные, которые устарели, неактивны или менее вероятно будут нужны пользователю. Различные алгоритмы очистки ( LRU, LFU и т.д.) используются для решения, какие данные удалять из кеша.

Теплый кеш (Warm cache)
Кеш, который имеет достаточное количество данных, чтобы была высокая вероятность того, что запрос будет удовлетворен данными из кеша, что приводит к большому количеству попаданий в кеш.

Конфликт записи (Write conflict)
Когда два запроса на запись разных значений происходят из разных источников в одно и то же место. Для разрешения конфликта система должна определить, какое значение использовать.