Как стать автором
Обновить

Что, если выкинуть все лишнее из базы в распределенный кэш – наш опыт использования Hazelcast

Блог компании ЮMoney Высокая производительность *Open source *Анализ и проектирование систем *Разработка под e-commerce *
Всего голосов 30: ↑30 и ↓0 +30
Просмотры 41K
Комментарии 17

Комментарии 17

Спасибо за отличную статью.
Т.к. работаю в Hazelcast, возьмусь прокомментировать пару моментов.
Буду комментировать по ходу чтения статьи.


Развал кластера и Split Brain

Обычно, из-за высокой latency между датацентрами, мы не рекомендуем размазывать кластер Hazelcast на множестно датацентром. есть WAN Replication, но как и у многих конкурентов она входит в платный пакет.
Так же, хочу отметить, что в более новых версиях появился механизм quorum, который позволяет настроить CP vs AP поведение для конкретных структур данных.
По поводу NoClassDefFoundError. У Hazelcast много чего разного лежит в META-INF/services.
Не все uberjar упаковщики правильно приносят это все.
В общем случае, хотелось бы поглядеть на полный stacktrace, но я тут вижу вы как-то это полечили.


Ложные срабатывания политик эвикта данных

Начиная с 3.7, eviction был очень сильно переработан. Об алгоритме можно почитать тут и тут.
Пусть вас не смущает JCache в последнем линке. с 3.7 JCache и IMap используют унифицированный механизм.
Так же в 3.7, появились Custom Eviction Policies — секция Custom Eviction Policy, так что можно реализовать что-то свое если LRU или LFU не подходят (там есть пример).


можно сначала запустить ноду кластера, а затем применить настройки хранения

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


Вывод: сначала конфигурируем инстанс, затем запускаем.

ваш вывод очень правильный.


Config conf = new Config();
// кастомизации происходят тут
HazelcastInstance hz = Hazelcast.newHazelcastInstance(conf);

В любом случае, все ноды кластера должны иметь одинаковый конфиг.


Долго выполняются команды в момент изменения структуры кластера

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


Мониторинг кластера

Тут все правильно сказал.
Management Center, кстати, умеет отдавать агрегированную статистику через JMX.
Можно заставить, MC собирать статистику по Hazelcast кластеру и отдавать ее в Zabbix или Prometheus.


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

Я бы не рекомендовал. Лучше поглядите на Diagnostics — фича, highly inspired by Metrics framework.


Перезапуск нод кластера
Каждое изменение настроек Hazelcast

вот только вчера выкатили 3.9-EA (early access) с новой фичей про добавление конфигураций динамически.
Можно пробовать!


А раз документации к продукту немного, то подробнее остановлюсь на решениях.

Вот тут сейчас обидно было ©


  1. официальные доки
  2. бесплатная книга (постоянно обновляется)
  3. платная книга (уже малец старая)
  4. и много всяких позных ништяков

мне еще предстоит познать «радость» обновления с Hazelcast 3.5.5 до свежей версии 3.8.

Начиная с 3.6, клиенты и ноды начали общаться по стандартному протоколу Hazelcat Open Client Protocol, что позволяет обновлять минорные версии нод и клиентов в разное время.
В 3.8 EE (Enterprise Edition) появилась возможность обновлять минорные версии нод «на горячую», т.е. обновлять 3.8 -> 3.9, 3.9->3.10 и тд.
Исходя из всего выше описанного, обновление на 3.8 очень рекомендовано.


В любом случае, буду рад ответить на любые вопросы, если такие появятся.

Обычно, из-за высокой latency между датацентрами, мы не рекомендуем размазывать кластер Hazelcast на множестно датацентром. есть WAN Replication, но как и у многих конкурентов она входит в платный пакет.


Наш опыт показывает что latency просаживается не существенно. Не нашел в документации информацию по WAN Replication — поддерживается ли репликация между двумя разными версиями Hazelcast?
Начиная с 3.7, eviction был очень сильно переработан. Об алгоритме можно почитать тут и тут.


Проблема с ложным срабатыванием FREE_HEAP_PERCENTAGE воспроизводится на 3.8. Проблема описана в статье — политика срабатывает при достижении размера занимаемой памяти всем приложением. На графике «Garbage Collector за работой» показано как происходит выделение и освобождение памяти — видно, что график много раз за день пересекает границу в 75% от доступной памяти. Это приводит к срабатыванию FREE_HEAP_PERCENTAGE=25%, хотя данных в коллекциях не так много. Я бы не назвал это ошибкой в Hazelcast, скорее — особенностью реализации. А вот за реализацией нужно идти в исходный код, в документации про реализацию ничего не сказано
(MapListener) Я бы не рекомендовал. Лучше поглядите на Diagnostics — фича, highly inspired by Metrics framework.


Спасибо за рекомендацию. Однако, мы логируем с помощью MapListener не статистику, а события по изменениям данных в коллекциях. Например, добавления с конкретным значением key, эвикты, удаления.
Статистику по каждой коллекции мы мониторим на основе данных IMap::LocalMapStats, отправляя их напрямую в Graphite. Таким образом получаем графики с latency, которые присутствуют в статье
вот только вчера выкатили 3.9-EA (early access) с новой фичей про добавление конфигураций динамически.
Можно пробовать!


Позволяет добавлять только новые конфигурации на горячую. Основной кейс у нас — изменение текущих конфигураций. Программное добавление конфигураций подразумевает реализацию динамического изменения конфигов в компоненте, запускающем Hazelcast ноду. Это усложняет разработку, тестирование и поддержку компонента. Гораздо прозрачней для эксплуатации безопасно выключить ноду и запустить с новыми конфигами
Вот тут сейчас обидно было ©

Соглашусь что документация хорошая, актуальная и позволяет получить информацию и рекомендации по продукту. Но лучшая документация — исходный код. Он у вас хорош, действительно. Прочитал код и сразу все понятно как работает
Начиная с 3.6, клиенты и ноды начали общаться по стандартному протоколу Hazelcat Open Client Protocol, что позволяет обновлять минорные версии нод и клиентов в разное время.
В 3.8 EE (Enterprise Edition) появилась возможность обновлять минорные версии нод «на горячую», т.е. обновлять 3.8 -> 3.9, 3.9->3.10 и тд.
Исходя из всего выше описанного, обновление на 3.8 очень рекомендовано..

В первую очередь собираемся обновляться до 3.8 как раз для этого. Можете порекомендовать варианты миграции с предыдущих версий? Мы сейчас в процессе изобретения велосипеда для миграции без простоя (:
В свое время отказался от Hazelcast так как:

1. При старте хацелькаст отжирал около 50 МБ хипа; А мы ранимся на low-end VM, где всего есть 250мб;
2. Внутри, на то время, был сложный и не очень хороший код со слипами в местах записи (если мне не изменяет память)…
3. Довольно большая джарка, сейчас уже 5мб, смотреть пункт 1.
Антон, спасибо за статью. Читали всей командой.

1) А вы подкладываете в Hazelcast свои jar-ы (domain-классы) для сериализации/десериализации/выборке по индексу? Удается ли в случае изменений в таких классах обновляться без остановки кластера?

У нас сейчас не получается подложить jar на каждую ноду по очереди (hazelcast начинает сыпать исключениями). Мы пробовали вместо jar-ов в classpath работать через Portable, но очень много однообразного кода readPortable/writePortable/portableFactory, вернулись на Java-сериализацию. Когда нужно переподложить jar-ы — останавливаем кластер. В 3.8 заявлено «User Code Deployment: Load your new classes to Hazelcast IMDG members dynamically without restarting all of them». Но интересно, как при таких высоких SLA как у Яндекс-денег, вы на 3.5 справляетесь с обновлениями этих jar-ов без остановки кластера? Или обходитесь стандартными Java-классами?

2) Как вы настроили параметры

hazelcast.client.max.no.heartbeat.seconds,
hazelcast.heartbeat.interval.seconds,
hazelcast.client.invocation.timeout.seconds
и другие таймауты
?

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

3) Можете рассказать подробнее про потерю нод, датацентра? Как быстро кластер выкидывает ноду и продолжает работать? Как вы тестировали? (про потерю датацентра видел, но интересует с точки зрения простоев и SLA).

4) Сколько памяти вы даете каждой ноде? Где-то видел рекомендацию про не более 4Гб.
А вы подкладываете в Hazelcast свои jar-ы (domain-классы) для сериализации/десериализации/выборке по индексу? Удается ли в случае изменений в таких классах обновляться без остановки кластера?


Так как мы запускаем каждую ноду программно как часть java-приложения, то храним доменные объекты прямо в коде этого компонента. Однако, это не широко распространенный кейс из-за описанных вами сложностей. Фактически мы не меняем доменные объекты.
Основной объем информации хранится в виде IMap<String, String>, в качестве ключа — уникальный идентификатор, значение — POJO сериализованный в JSON. Сериализацией/десериализацией управляет клиент, обратная совместимость реализуется очень просто. В результате никаких простоев
Можете рассказать подробнее про потерю нод, датацентра? Как быстро кластер выкидывает ноду и продолжает работать? Как вы тестировали? (про потерю датацентра видел, но интересует с точки зрения простоев и SLA).

Весь процесс выключения ноды занимает ~500мс, из них ~30 мс штатный вывод из кластера. Бывают исключения, когда весь процесс затягивается до 5 секунд, что связано с Gracefull Shutdown.
Выкидывание по причине потери связи с одной из нод бывает крайне редко, обычно укладывается в таймауты.
Время за которое кластер восстанавливает нормальную работу хорошо видно в Kibana — это период времени когда был всплекс ошибок чтения/записи в Hazelcast. При нештатном выкидывании ноды из кластера в логах наблюдаются единичные ошибки в течении максимум 1 секунды
Сколько памяти вы даете каждой ноде? Где-то видел рекомендацию про не более 4Гб.

512*25 серверов=12.5Gb. Нам хватает с кратным запасом. Кластер расположен на серверах рядом с другими сервисами. Если вам нужна другая конфигурация, то нужно просто провести нагрузочное тестирование, подтюнить GC.
Вот тест с 2Тб — https://www.youtube.com/watch?v=DozGQMHRoZI
Как вы настроили параметры

hazelcast.client.max.no.heartbeat.seconds,
hazelcast.heartbeat.interval.seconds,
hazelcast.client.invocation.timeout.seconds
и другие таймауты
?

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

Значения таймаутов клиента установлены по умолчанию. Таймаутом управляет конкретный бизнес процесс. Например, задача запускается из асинхронной очереди и пытается получить данные из Hazelcast с помощью IMap::getAsync(key).get(operationTimeout.getMillis(), TimeUnit.MILLISECONDS)), где operationTimeout — параметр конфигурации клиентского приложения, согласно требованиям к нему. Если Hazelcast ушел в себя, компонент ушел в себя, сеть мигнула или пожар, то ничего страшного — через operationTimeout задача прервется и через некоторое время запустится вновь.
В случае синхронных операций, которым требуется сделать вызов Hazelcast, прерывание операции так же обрабатывает клиент запустивший эту операцию. Например, пришел http запрос и мы пытаемся получить из Hazelcast кэш данных, не дождавшись данных за operationTimeout отдаем клиенту ошибку, который может сделать повторный вызов для исключения единичных сбоев.
>>Служба эксплуатации проводила учения по отключению одного ДЦ, и в какой-то момент кластер Hazelcast остался без половины своих нод. Все данные были успешно восстановлены из бэкапов группы партиций, а уменьшение количества нод в кластере позитивно сказалось на скорости работы.

Поскольку SmartRouting у Вас отключен, запросы с клиента идут не прямо на ноду, содержащую данные, а на случайную ноду кластера. Могут попасть и в другой ДЦ, затем запрос будет перенаправлен в «правильный» ДЦ. Запросы между ДЦ, обычно, гораздо медленнее запросов внутри одного ДЦ. Поэтому, когда один ДЦ отключился, все стало ~ в два раза быстрее.
И еще интересно: у Вас после восстановления данных из бэкапов новые бэкапы создались внутри одного ДЦ? Мне кажется, их не должно б быть. Тогда и скорость put'ов еще возрастет…
Поскольку SmartRouting у Вас отключен, запросы с клиента идут не прямо на ноду, содержащую данные, а на случайную ноду кластера. Могут попасть и в другой ДЦ, затем запрос будет перенаправлен в «правильный» ДЦ. Запросы между ДЦ, обычно, гораздо медленнее запросов внутри одного ДЦ. Поэтому, когда один ДЦ отключился, все стало ~ в два раза быстрее

При отключенном smartRouting клиент подключается не к рандомной ноде кластера, а к той, что прописана в настройках подключения. Далее получение данных отдано на откуп Hazelcast. Клиента одного ДЦ подключаются к нодам того же ДЦ.
В обоих схемах не избежать походов за данными в другой ДЦ. Да, на графике виден эффект от исключения похода в другой ДЦ
И еще интересно: у Вас после восстановления данных из бэкапов новые бэкапы создались внутри одного ДЦ? Мне кажется, их не должно б быть. Тогда и скорость put'ов еще возрастет…

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

так какая скорость в секунду была и какие ключи — размер в байтах и на каком размере базы данных?


Например — скорость 100 тыс транзакций в секунду при добавлении ключа 8 байт в таблицу с 1 миллиардом записей. Так можете сказать?

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