Трендовые github проекты в нашем телеграм канале. Подпишись → От одиночного кэша к отказоустойчивому кластеру Hazelcast
Кэш часто появляется в сервисе как локальное ускорение, а затем становится частью критического пути. Особенно это заметно, когда в нём живёт крупная модель процесса: состояние объёмом в несколько мегабайт, которое должно быть доступно нескольким компонентам и переживать перезапуск отдельных приложений. Одиночный Redis в такой роли превращает отказ одной ноды в остановку всей подсистемы.
Для Java-инфраструктуры с Kubernetes одним из вариантов распределённого хранилища становится Hazelcast. Он формирует кластер из JVM-процессов, хранит данные в IMap, умеет резервировать записи и обнаруживать соседние инстансы через API Kubernetes. Такой подход полезен, когда важны компактное развёртывание, интеграция с Java-стеком и автоматическое восстановление после сбоя ноды.
Сначала определить роль кэша
Перед выбором технологии стоит зафиксировать, что именно хранится в памяти и какие гарантии требуются. Если приложение держит в Redis большие переменные бизнес-процесса, это уже ближе к in-memory data grid, чем к кэшу результатов запросов. Для такого хранилища имеют значение:
- минимум три экземпляра и автоматическое масштабирование;
- сохранность данных при потере одной ноды;
- репликация без ручного переключения;
- предсказуемое потребление CPU и памяти;
- распределённые блокировки, если несколько сервисов меняют один ключ;
- возможность выгрузки данных в PostgreSQL;
- понятная поддержка в существующей Java-платформе.
Эти критерии полезнее абстрактного сравнения продуктов. Например, кластерный Redis и Sentinel закрывают часть задач, но добавляют отдельную область настройки и сопровождения. Тяжёлые распределённые СУБД могут превысить разумный бюджет ресурсов для вспомогательного слоя. Hazelcast хорошо ложится на сценарий, где кластер данных живёт рядом с приложениями и использует те же практики мониторинга, настройки GC и доставки образов.
Развёртывание в Kubernetes
Для размещения по одной копии Hazelcast на каждой рабочей ноде удобно использовать DaemonSet. При добавлении ноды Kubernetes создаст новый pod, а Hazelcast включит его в кластер. Ресурсы следует задать явно: для старта подойдут запрос 1 GiB памяти и 500m CPU, с лимитом порядка 1,5 GiB и двух ядер; реальные значения зависят от объёма данных, числа резервных копий и сериализации объектов.
Контейнеру нужны порт Hazelcast и проверки готовности. Readiness и liveness probe позволяют исключить нездоровый экземпляр из маршрутизации и дают Kubernetes сигнал на перезапуск. Конфигурацию разумно вынести в ConfigMap, чтобы менять параметры кластера отдельно от образа.
Внутри кластера multicast отключают: в Kubernetes он часто недоступен или создаёт лишние задержки. Вместо него включают Kubernetes discovery и ограничивают поиск нужным namespace и Service:
hazelcast:
cluster-name: app-cache
network:
join:
multicast:
enabled: false
kubernetes:
enabled: true
namespace: app
service-name: hazelcast-service
properties:
hazelcast.discovery.enabled: false
Имена cluster-name, namespace и Service должны быть одинаково согласованы в манифестах и клиентской конфигурации. Отдельно проверьте RBAC: если механизм обнаружения обращается к API Kubernetes, service account обязан иметь минимально необходимые права на чтение объектов в своём namespace.
Репликация IMap и восстановление после сбоя
Надёжность данных задаётся на уровне карты. Для кластера из трёх экземпляров конфигурация с backup-count: 2 создаёт основную копию и две синхронные резервные. Потеря одного участника не должна лишить кластер данных: оставшаяся копия становится доступной, а после появления новой ноды Hazelcast восстановит необходимое число реплик.
hazelcast:
map:
process-model:
backup-count: 2
async-backup-count: 0
read-backup-data: true
read-backup-data: true разрешает чтение с резервной копии и снижает лишние сетевые переходы. Это подходит только там, где приложение допускает особенности согласованности при чтении. Асинхронные копии экономят задержку записи, однако увеличивают вероятность потери последних изменений при аварии. Их стоит включать лишь для данных с понятной ценой такой потери.
Проверять схему нужно отказом ноды под нагрузкой. В корректно подготовленном кластере приложения получают алерт, pod-ы перераспределяются, а операции продолжаются без ошибок и ручного вмешательства. Тест также показывает, хватает ли памяти на реплики и не создаёт ли восстановление перегрузку сети.
Персистентность: ограничения MapStore
Память кластера не заменяет постоянное хранилище. Hazelcast позволяет реализовать загрузку и сохранение записей через собственный класс, работающий с базой. Для PostgreSQL это обычно означает код на JDBC, продуманную схему таблицы и индексы под операции чтения по ключу.
Важно учитывать границы этой модели. Реализация выполняется на стороне Hazelcast и в отдельном classpath. Сторонние библиотеки, привычные основному приложению, могут отсутствовать в серверном образе. Обновление кода загрузчика способно потребовать пересоздания карты или нового имени, поскольку серверная конфигурация карты не рассчитана на свободную замену. Эти особенности делают JDBC-реализацию прозрачнее, чем попытка перенести в кластер привычный Spring Data или JOOQ без подготовки образа.
Параметр write-delay-seconds: 0 даёт синхронную запись в базу. Ненулевое значение переводит выгрузку в асинхронный режим; тогда нужно оценить окно возможной потери данных. write-batch-size помогает группировать изменения. Для данных, которые должны присутствовать в памяти уже на старте, применяется режим EAGER, но массовая загрузка увеличивает время запуска и пиковое потребление ресурсов.
Блокировки и версия продукта
Распределённая блокировка — отдельная причина внимательно читать матрицу возможностей выбранной версии. В актуальных версиях Hazelcast часть CPSubsystem, основанная на Raft, относится к платным функциям. Обычный IMap.tryLock остаётся доступным, однако имеет собственные риски: таймауты, дедлоки и зависания при неверном использовании.
Если корректность операции зависит от строгой распределённой блокировки, проверьте лицензию, поведение при сетевом разделении и сценарии отказа до внедрения. Для некоторых систем логичнее перенести координацию в специализированный компонент или рассмотреть альтернативное in-memory хранилище с подходящими гарантиями.
Эксплуатационный итог
Hazelcast помогает убрать единичную точку отказа у кэша, когда три или больше участников держат синхронные копии данных, а Kubernetes восстанавливает pod-ы после сбоя ноды. Успех зависит от точной модели данных, рассчитанных реплик, тестов аварийного восстановления и заранее принятых ограничений на персистентность и блокировки. Такой список проверок стоит пройти до переноса критичного состояния в новый кластер.