В настоящее время многие сервисы поддерживают отказоустойчивость из коробки, т.е. могут быть установлены на несколько узлов и (а) сохранять работоспособность при выходе из строя части из них, а также (б) распределять нагрузку между собой, т.е. предоставлять HA/LB — High Availibility и Load Balancing.
Некоторые известные примеры: LDAP, ElasticSearch, Ceph, MySQL (шутка) и т.д.
Проблема заключается в том, что клиентская часть не всегда знает и поддерживает кластерные возможности серверной части. Например, связь с ElastisSearch производится по протоколу HTTP. Если узел кластера, с которым связывается клиентское приложение, становится нерабочим, приложение должно самостоятельно принять решение, что надо обращаться к другому узлу кластера, т.е. использовать другой URL.
Решить данную проблему можно несколькими способами. Перечислим наиболее известные из них:
- балансировка по IP-адресу;
- DNS-балансировка;
- BGP anycast;
- Frontend — все запросы приходят на приложение-балансировщик (в зависимости от протокола — nginx, haproxy, mysql router и т.д.), которое передаёт их на исправные узлы.
Каждый из этих методов имеет свои недостатки:
- IP-балансировка требует, чтобы серверы находились в одной подсети.
- DNS-балансировка практически не используется, т.к. имеет недопустимо большое время задержки, потому что DNS-записи кэшируются на срок от нескольких минут до суток.
- BGP anycast в публичной сети требует анонсировать блок не меньше /24. Мы рассматриваем случай, когда и сервисы, и клиентская часть являются частью одного распределённого приложения, поэтому допустимо использовать для их связи приватные диапазоны ASnums и IP-адресов, но если сервисы находятся в разных точках сети, то такой BGP сможет работать только поверх VPN. Вряд ли надо пояснять, что в силу громоздкости подобная конструкция способна стать как решением существующих проблем, так и источником новых.
- Frontend является единой точкой отказа, а его пропускная способность — потенциальным узким местом.
Недостатки последнего варианта можно преодолеть, если переместить балансировщик к клиенту, т.е. вместо одного самостоятельного компонента, обслуживающего многих клиентов, будет много балансировщиков, каждый из которых обслуживает только того клиента, вместе с которым запущен.
На эту роль идеально подходит haproxy — производительный, относительно компактный, с простым файлом настроек, и с возможностью менять настройки на лету.
Доступность сервисов haproxy умеет проверять на двух уровнях:
- сетевом — отвечает ли TCP-порт на запрос соединения;
- прикладном — в настоящий момент поддерживаются HTTP, LDAP, MySQL, PostgreSQL, Redis и SMTP.
Например, если приложение должно работать с кластерами ElasticSearch и MySQL, то конфигурация haproxy может выглядеть примерно так:
listen mysql
bind 127.0.0.1:3306
mode tcp
balance roundrobin
option mysql-check user haproxy_check
server mysql-cluster-node-1 10.0.0.1:3306 check
server mysql-cluster-node-2 10.0.0.2:3306 check
listen elastic
bind 127.0.0.1:9200
mode http
balance roundrobin
option forwardfor
option httpclose
option httpchk
http-check expect status 200
server es-cluster-node-1 10.0.0.101:9200 check
server es-cluster-node-2 10.0.0.102:9200 check
listen 0.0.0.0:8080
mode http
stats enable
stats uri /haproxy_stats
stats auth haproxy_admin:VerySecretPassword
Дополнительным достоинством данной схемы является упрощение установки приложений — теперь от них требуется подключаться к сервисам по адресу 127.0.0.1, т.е. достаточно один раз указать этот адрес при разработке в настройках по умолчанию. За знание фактических адресов сервисов будет отвечать haproxy, настроенный под конкретную конфигурацию кластеров.
Если распределённая система не планирует разрастаться до таких размеров, при которых становится оправданным внедрение Consul или его аналогов — предложенный здесь вариант является приемлемым сочетанием простоты, надёжности, производительности и гибкости.