В данной статье будет приведена инструкция по развертыванию отказоустойчивого кластера Clickhouse на 3 ноды. В ходе развертывания будет использоваться docker compose. Это очень удобный способ для создания трех инстансов СУБД в целях тренировки.
Репозиторий с готовым docker compose проектом
После прочтения статьи у вас будет представление о том, как развернуть свой отказоустойчивый кластер хоть на 3, хоть на 5, хоть на 9 и сколько угодно узлов.
Вам станет понятно, за что отвечают те или иные параметры и вы без труда сможете углубиться в тему материала по ключевым словам и понятиям, приведенным в этой статье.
Предлагаю выкачать материалы из репозитория к себе и с ходом теории подглядывать на конфигурационные файлы для Clickhouse.
Что из себя представляет кластер Clickhouse
Кластер Clickhouse представляет собой группу запущенных процессов Clickhouse, объединенных сетью. В рамках кластера можно производить репликацию данных: поддерживать несколько таблиц в одинаковом состоянии. Это необходимо для обеспечения отказоустойчивости. Если один сервер стал недоступен, другой сервер продолжает предоставлять доступ пользователей с данным.
Остается ли кластер в рабочем состоянии или же переходит в режим “readonly”,
зависит от того, сколько серверов оказались недоступными. Кластер Clickhouse
будет оставаться в полностью рабочем состоянии до тех пор, пока будет доступно
как минимум int(N / 2) + 1
серверов, где N - суммарное число серверов в
кластере. То есть, если кластер состоит из 3 серверов, то он не будет переходить
в режим readonly до тех пор, пока не станут недоступными 2 сервера.
int(3 / 2) + 1 -> int(1.5) + 1 -> 1 + 1 = 2
- 2 это число серверов,
необходимое для штатной работы кластера на 3 сервера.
В случае кластера на 9 серверов, аналогично:
int(9 / 2) + 1 -> int(4.5) + 1 -> 4 + 1 = 5
- Кластер будет работать до тех
пор, пока в нём штатно работают 5 серверов.
В Clickhouse реплицируются не базы данных, а отдельные таблицы. Они основаны на движках таблиц, поддерживающих репликацию. Движок таблицы MergeTree не может быть использован для репликации таблицы. А вот движок ReplicatedMergeTree несет в себе возможности движка MergeTreem, но такая таблица еще и может быть реплицирована.
Clickhouse Keeper, Apache Zookeeper, RAFT
Clickhouse Keeper - сервис координации, написанный разработчиками Clickhouse.
Написан на C++ и представляет собой замену сервису Apache Zookeeper, написанному
на Java. Разработчики Clickhouse рекомендуют использовать именно Clickhouse
Keeper для развертывания кластеров СУБД. Далее в этой статье, а также в
официальной документации вы можете встретить секцию параметров
<zookeeper></zookeeper>
, имейте ввиду, что эти параметры относятся не именно к
Apache Zookeeper, а к любому из двух сервисов, смотря, какой настроите: Apache
Zookeeper или Clickhouse Keeper. Далее в статье для краткости я буду писать
просто Keeper, а иметь ввиду Clickhouse Keeper.
Keeper для достижения консенсуса использует протокол RAFT, в то же время Apache Zookeeper использует для этого протокол ZAB.. Ниже протокол будет рассмотрен в краткой форме. Для более подробного изучения протокола могу порекомендовать статью на хабре, интерактивную презентацию, сайт, посвещенный RAFT’у, а также научную публикацию.
Так как развернуть кластер?
Конфиг Clickhouse может храниться в нескольких xml-файлах внутри директории
config.d, при описании секций следует следить, чтобы корневой секцией во всех
файлах оставалась секция <clickhouse></clickhouse>
Первым делом нужно разрешить доступ к Clickhouse и Keeper из сети, для этого задается параметр listen_host.
<listen_host>0.0.0.0</listen_host>
0.0.0.0 означает, что доступ открыт для любого ip-адреса.
Запуск кластера Keeper
Для работы кластера Clickhouse требуется развернуть кластер Keeper. Keeper может работать и отдельно от СУБД. То есть имеется возможность развернуть кластер Keeper на одних машинах, а кластер СУБД на других. Развернем оба кластера на одних и тех же трех машинах (docker- контейнерах), ничего плохого в этом нет.
За запуск кластера Keeper отвечат вот этот конфиг:
<keeper_server>
<tcp_port>9181</tcp_port>
<server_id>1</server_id>
<log_storage_path>/var/lib/clickhouse/coordination/log</log_storage_path>
<snapshot_storage_path>/var/lib/clickhouse/coordination/snapshots</snapshot_storage_path>
<coordination_settings>
<operation_timeout_ms>10000</operation_timeout_ms>
<session_timeout_ms>30000</session_timeout_ms>
<raft_logs_level>warning</raft_logs_level>
</coordination_settings>
<raft_configuration>
<server>
<id>1</id>
<hostname>click1.clickcompose_default</hostname>
<port>9010</port>
</server>
<server>
<id>2</id>
<hostname>click2.clickcompose_default</hostname>
<port>9010</port>
</server>
<server>
<id>3</id>
<hostname>click3.clickcompose_default</hostname>
<port>9010</port>
</server>
<server>
<id>4</id>
<hostname>click4.clickcompose_default</hostname>
<port>9010</port>
</server>
<server>
<id>5</id>
<hostname>click5.clickcompose_default</hostname>
<port>9010</port>
</server>
</raft_configuration>
</keeper_server>
- tcp_port - порт, на котором сидит Clickhouse Keeper, на нём он принимает клиентские подключения и также отвечает на 4 буквенные команды: ruok, srvr и прочие;
- raft_configuration представляет собой карту кластера;
- hostname может быть как доменным именем, так и ip-адресом;
- port - порт для межсерверного взаимодействия узлов внутри кластера RAFT-Keeper.
Благодаря этому конфигу Clickhouse понимает, что требуется запустить сервер
Keeper’а, а сам Keeper из секции <raft_configuration></raft_configuration>
понимает, с кем ему предстоит работать.
Конфиг для Clickhouse, который даст информацию о том, как соединиться с
серверами Keeper
Этот конфиг говорит clickhouse о том, что необходимо сотрудничать с Keeper’ом. Секция настроек называется zookeeper, будто бы отсылая читателя-писателя конфига к Apache Zookeeper, но на самом деле секция zookeeper может описывать подключение как к Apache Zookeeper, так и к Clickhouse Keeper кластерам. Почему так сделано- тайна, покрытая мраком. Я так полагаю, сделано это потому что Clickhouse изначально мог работать только с Apache Zookeeper, Clickhouse Keeper появился позже, а наименование секции настроек решили не менять в целях обратной совместимости с уже существующими и работающими конфигурациями кластеров.
<zookeeper>
<node>
<host>click1</host>
<port>9181</port>
</node>
<node>
<host>click2</host>
<port>9181</port>
</node>
<node>
<host>click3</host>
<port>9181</port>
</node>
</zookeeper>
Кстати говоря, port - это тот самый порт, на котором Keeper ожидает клиентсктие подключения.
Создание кластера. Шардирование
В общем случае, шардирование есть разнесение одной базы данных по нескольким узлам/серверам/нодам, подставьте нужное или то, что больше нравится. В конкретных случаях в рамках используемой СУБД, смысл понятия “шардирование” будет меняться. Для одной СУБД это будет означать партицирование данных и хранение их на том же самом сервере, для другой шардирование заключается в хранении фрагментов одной таблицы на нескольких серверах. Могу порекомендовать такую статью на хабре по теме шардинга.
Как мы успели выяснить, шардирование- понятие общее. И вот, Clickhouse не видит ничего зазорного в том, чтобы требовать, чтобы реплики, хранящиее одни и те же данные, были в одном шарде. Так что не стоит ругаться на СУБД, имеет право. Итак, нас интересует отказоустойчивый кластер на 3 ноды с полным реплицированием. Чтобы такой сделать, следует определить кластер, в нём определить один шард, внутри шарда описать 3 реплики, внутри каждой реплики по яйцу, в каждом яйце по смерти Кащеевой.
<remote_servers>
<cluster_name>
<shard>
<replica>
<host>click1</host>
<port>9000</port>
</replica>
<replica>
<host>click2</host>
<port>9000</port>
</replica>
<replica>
<host>click3</host>
<port>9000</port>
</replica>
</shard>
</cluster_name>
</remote_servers>
- cluster_name - название кластера Clickhouse;
- port - TCP порт для коммуникации между серверами, в главном конфиге ноды (/etc/clickhouse-server/config.xml) имеет название tcp_port.
Проверка работоспособности Keeper кластера и Clickhouse кластера
В принципе, всё готово к запуску. Запускаем контейнеры через docker compose, и
каким угодно образом отправляем stat
(чуть ниже показано, как это сделать) на
TCP порт, на котором Keeper сервера ожидают клиентских подключений.
Ниже привожу ответ, который я получил от узлов в ответ на команду stat. Порты 9001, 9002, 9003 это те порты, на которые был произведен маппинг портов 9181 трех docker- контейнеров.
Нода 1:
user@vm:~/Desktop/click compose$ echo stat | nc localhost 9001
ClickHouse Keeper version: v22.2.3.1-stable-97317e8bb51d2723b539b7863dd6471a68b11d01
Clients:
172.19.0.1:47744(recved=0,sent=0)
172.19.0.3:59438(recved=7,sent=7)
Latency min/avg/max: 0/0/1
Received: 7
Sent : 7
Connections: 1
Outstanding: 0
Zxid: 19
Mode: leader
Node count: 4
Нода 2:
user@vm:~/Desktop/click compose$ echo stat | nc localhost 9002
ClickHouse Keeper version: v22.2.3.1-stable-97317e8bb51d2723b539b7863dd6471a68b11d01
Clients:
172.19.0.1:60756(recved=0,sent=0)
172.19.0.4:33656(recved=7,sent=7)
Latency min/avg/max: 0/0/1
Received: 7
Sent : 7
Connections: 1
Outstanding: 0
Zxid: 19
Mode: follower
Node count: 4
Нода 3:
user@vm:~/Desktop/click compose$ echo stat | nc localhost 9003
ClickHouse Keeper version: v22.2.3.1-stable-97317e8bb51d2723b539b7863dd6471a68b11d01
Clients:
172.19.0.1:38350(recved=0,sent=0)
172.19.0.2:52274(recved=7,sent=7)
Latency min/avg/max: 0/1/4
Received: 7
Sent : 7
Connections: 1
Outstanding: 0
Zxid: 19
Mode: follower
Node count: 4
Может возникнуть вопрос: почему параметр Node count равен 4? Ноды то у нас всего 3. На самом деле, это не количество работающих нод кластера и даже не общее количество нод в кластере. Ради эксперимента я запустил кластер на 5 серверов, Node count все равно равен 4.
На этом же кластере я создал базу данных и таблицу в ней (с помощью распределенного запроса). И вот, как теперь отвечает одна из нод кластера:
user@vm:~$ echo srvr | nc localhost 9001
ClickHouse Keeper version: v22.2.3.1-stable-97317e8bb51d2723b539b7863dd6471a68b11d01
Latency min/avg/max: 0/1/10
Received: 280
Sent : 286
Connections: 2
Outstanding: 0
Zxid: 963
Mode: follower
Node count: 140
Теперь Node count и вовсе 140.
В ответ на другую команду, mntr, нода возвращает чуть другую информацию. На этот раз некий параметр zk_znode_count равен 140.
user@vm:~$ echo mntr | nc localhost 9001
zk_version v22.2.3.1-stable-97317e8bb51d2723b539b7863dd6471a68b11d01
zk_avg_latency 0
zk_max_latency 10
zk_min_latency 0
zk_packets_received 808
zk_packets_sent 814
zk_num_alive_connections 2
zk_outstanding_requests 0
zk_server_state follower
zk_znode_count 140
zk_watch_count 6
zk_ephemerals_count 5
zk_approximate_data_size 28020
zk_key_arena_size 28672
zk_latest_snapshot_size 0
zk_open_file_descriptor_count 442
zk_max_file_descriptor_count 18446744073709551615
zk_znode_count и Node из примеров чуть выше это одна и та же метрика. Это не количество нод в кластере, это количество Z-нод внутри Clickhouse Keeper. Тут не обойтись без знаний о внутреннем устройстве Keeper’а. Мда уж… А документации то нет на Keeper. Ну прям вот нет, все, что говорится о Clickhouse Keeper на сайте Clickhouse, можно уместить на 4-5 листов А4. Как же быть…
Решение есть! Как же сразу такая идея не пришла в голову? Нужно посмотреть документацию на Apache Zookeeper, чтобы понять, как работает Clickhouse Keeper. Чудесно же!
Итак. Шутки в сторону. Где- то внутри себя Zookepeer содержит иерархическое пространство имен, похожее на распределенную файловую систему. Её можно представить как вывод директорий командой tree, но я вам такого не говорил. Каждый узел в этом дереве содержит какие- то данные, а также наследников. Такие узлы и называются Node’ами или znode’ами. Таким образом, чем больше данных в кластере, тем больше узлов становится. По этой причине в моем тестовом кластере произошло увеличение числа узлов от 4 до 140.
Заключение
В статье я постарался описать свой собственный опыт по конфигурации отказоустойчивого кластера Clickhouse. Надеюсь, что эта статья оказалась для вас полезной.
В будущей статье я намереваюсь освятить тему эксплуатации кластера, распределенных DDL запросов к кластеру и макросов. Создадим базу данных и таблицу, посмотрим, как обрабатывается выход одного и двух серверов из строя.
Создание реплицируемой таблицы на кластере описано в этом посте.