Хабрахабр

[Перевод] Высокая доступность MySQL в GitHub

Сам сайт, интерфейс API на GitHub, система аутентификации и многие другие функции требуют доступа к базам данных. GitHub использует MySQL в качестве основного хранилища данных для всего, что не связано с git, поэтому доступность MySQL имеет ключевое значение для нормальной работы GitHub. Они настроены по классической схеме с одним главным узлом, доступным для записи, и его репликами. Мы используем несколько кластеров MySQL для обработки различных служб и задач. Реплики (остальные узлы кластера) асинхронно воспроизводят изменения главного узла и обеспечивают доступ для чтения.

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

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

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

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

Цели обеспечения высокой доступности

По мере нашего роста нам необходимо адаптировать HA-стратегию MySQL к изменениям. Решение, описанное в статье, – это новая, улучшенная версия предыдущих решений для обеспечения высокой доступности (HA), реализованных в GitHub. Мы стремимся придерживаться аналогичных подходов для MySQL и других служб в GitHub.

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

  • Какое максимальное время простоя для вас некритично?
  • Насколько надежны средства обнаружения сбоев? Критичны ли для вас ложноположительные срабатывания (преждевременная отработка отказа)?
  • Насколько надежна система отработки отказа? Где может возникнуть сбой?
  • Насколько эффективно решение работает в условиях нескольких центров обработки данных? Насколько эффективно решение работает в сетях с низкой и высокой задержкой?
  • Продолжит ли решение работать в случае полного отказа центра обработки данных (ЦОД) или в условиях сетевой изоляции?
  • Какой механизм (при его наличии) предотвращает или смягчает последствия возникновения в кластере двух главных серверов, которые независимо друг от друга осуществляют запись?
  • Критична ли для вас потеря данных? Если да, то в какой мере?

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

Отказ от использования VIP и DNS для обнаружения

В рамках предыдущего решения мы использовали:

  • orchestrator для обнаружения и отработки отказа;
  • VIP и DNS для обнаружения главного узла.

По имени определялся виртуальный IP-адрес (VIP) главного узла. В том случае клиенты обнаруживали узел записи по его имени, например, mysql-writer-1.github.net.

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

Рассмотрим следующую топологию репликации, охватывающую три различных центра обработки данных:

image

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

Клиентам на самом деле неизвестны идентификационные данные главного узла, они знают только имя, которое теперь должно указывать на новый узел. orchestrator обнаруживает сбой, выбирает новый главный узел, а затем назначает имя/VIP. Однако обратите внимание вот на что.

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

  • В штатном режиме система отработки отказа сначала свяжется с вышедшим из строя главным узлом и запросит его освободить VIP, а затем обратится к новому главному серверу с запросом на присвоение VIP. Но что делать, если первый главный узел недоступен или выдает отказ на запрос освободить VIP-адрес? Учитывая, что в данный момент сервер находится в состоянии отказа, маловероятно, что он сможет своевременно ответить на запрос или ответить на него вообще.
    1. В результате может возникнуть ситуация, когда два хоста заявляют свои права на один и тот же VIP. Разные клиенты могут подключаться к любому из этих серверов в зависимости от кратчайшего сетевого пути.
    2. Правильность работы в такой ситуации зависит от взаимодействия двух независимых серверов, а такая конфигурация ненадежна.
  • Даже если первый главный узел отвечает на запросы, мы впустую тратим драгоценное время: переключение на новый главный сервер не происходит, пока мы связываемся со старым.
  • При этом даже в случае переназначения VIP нет гарантии, что существующие клиентские соединения на старом сервере будут разорваны. Мы опять рискуем оказаться в ситуации с двумя независимыми главными узлами.

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

  • Для распространения изменений в DNS требуется больше времени. Клиенты хранят DNS-имена в течение заранее настроенного периода времени. Отработка отказа с участием нескольких ЦОД влечет за собой более длительные простои, поскольку уходит больше времени на предоставление всем клиентам сведений о новом главном узле.

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

  • Главные узлы самостоятельно передавали пакеты пульса через службу pt-heartbeat для измерения величины запаздывания и регулировки нагрузки. Службу необходимо было переносить на вновь назначенный главный узел. При возможности, на старом сервере ее нужно было отключить.
  • Аналогичным образом, главные узлы самостоятельно управляли работой с Pseudo-GTID. Нужно было запустить этот процесс на новом главном узле и желательно остановить на старом.
  • Новый главный узел становился доступным для записи. Старый узел (если возможно) должен был получить метку read_only (только для чтения).

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

Решение работало, и GitHub успешно отрабатывал отказы MySQL в фоновом режиме, но мы хотели улучшить свой подход к HA следующим образом:

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

HA-решение GitHub: orchestrator, Consul, GLB

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

  • orchestrator для обнаружения и отработки отказов. Мы используем схему orchestrator/raft с несколькими ЦОД, как изображено на рисунке ниже;
  • Consul от Hashicorp для обнаружения служб;
  • GLB/HAProxy как прокси-слой между клиентами и узлами записи. Исходный код инструмента GLB Director открыт;
  • технология anycast для сетевой маршрутизации.

image

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

Нормальный поток

В обычной ситуации приложения подключаются к узлам записи через GLB/HAProxy.

Как и раньше, они используют только имя. Приложения не получают идентификационной информации главного сервера. Однако в нашей текущей конфигурации это имя разрешается в IP-адрес anycast. Например, главным узлом для cluster1 будет mysql-writer-1.github.net.

В частности, в каждом из наших ЦОД развернуто несколько экземпляров GLB, нашего высокодоступного балансировщика нагрузки. Благодаря технологии anycast имя разрешается в один тот же IP-адрес в любом месте, но трафик направляется по-разному, учитывая местоположение клиента. За счет этого все клиенты обслуживаются локальными прокси. Трафик на mysql-writer-1.github.net всегда направляется к кластеру GLB локального ЦОД.

Наш сервер HAProxy предоставляет пулы записи: по одному на каждый кластер MySQL. Мы запускаем GLB поверх HAProxy. Все экземпляры GLB/HAProxy во всех ЦОД имеют одинаковые пулы, и все они указывают на одни и те же серверы в этих пулах. При этом у каждого пула лишь один сервер (главный узел кластера). В любом случае будет выполнено перенаправление на фактический главный узел кластера cluster1. Таким образом, если приложение хочет записать данные в базу на mysql-writer-1.github.net, то не имеет значения, к какому серверу GLB оно подключается.

Именно GLB перенаправляет трафик в нужное место. Для приложений обнаружение заканчивается на GLB, а необходимость в повторном обнаружении отсутствует.

Как мы вносим изменения в GLB? Откуда в GLB поступает информация о том, какие серверы включать в список?

Обнаружение через Consul

Впрочем, в нашем случае мы используем ее как высокодоступное хранилище значений ключей (KV). Служба Consul широко известна как решение для обнаружения служб, кроме того, она также берет на себя функции DNS.

Для каждого кластера существует набор записей KV, указывающих на данные соответствующего главного узла: его fqdn, порт, адреса ipv4 и ipv6. В хранилище KV в Consul мы записываем идентификационные данные главных узлов кластера.

Служба consul-template создает файл конфигурации и может перезагрузить HAProxy при изменении настроек. Каждый узел GLB/HAProxy запускает consul-template, службу, которая отслеживает изменения в данных Consul (в нашем случае это изменения в данных главных узлов).

На основе этой информации выполняется настройка экземпляров, новые главные узлы указываются в качестве единственной сущности в пуле серверов кластера. Благодаря этому информация об изменении идентификационных данных главного узла в Consul доступна каждому экземпляру GLB/HAProxy. После этого экземпляры перезагружаются, чтобы изменения вступили в силу.

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

Откуда Consul получает информацию об изменениях и как она распространяется между ЦОД?

orchestrator/raft

В каждом ЦОД у нас один или два узла orchestrator. Мы используем схему orchestrator/raft: узлы orchestrator взаимодействуют друг с другом посредством консенсуса raft.

Отработка отказа управляется одним ведущим узлом orchestrator/raft, но изменения, новости о том, что в кластере теперь новый главный узел, распространяются на все узлы orchestrator с помощью механизма raft. orchestrator отвечает за обнаружение сбоев, отработку отказов MySQL и передачу измененных данных о главном узле в Consul.

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

Обобщенное представление всего потока

При сбое главного узла:

  • узлы orchestrator обнаруживают сбои;
  • ведущий узел orchestrator/raft инициирует восстановление. Назначается новый главный узел;
  • схема orchestrator/raft передает данные об изменении главного узла всем узлам кластера raft;
  • каждый экземпляр orchestrator/raft получает уведомление об изменении узла и записывает в локальное хранилище KV в Consul идентификационные данные нового главного узла;
  • на каждом экземпляре GLB/HAProxy запущена служба consul-template, которая отслеживает изменения в хранилище KV в Consul, перенастраивает и перезагружает HAProxy;
  • клиентский трафик перенаправляется к новому главному узлу.

orchestrator не взаимодействует с балансировщиками нагрузки. Для каждого компонента обязанности четко распределены, и вся структура диверсифицирована и упрощена. Прокси-серверы работают только с Consul. Службе Consul не требуются данные о происхождении информации. Клиенты работают только с прокси-серверами.

Более того:

  • нет необходимости вносить изменения в DNS и распространять информацию о них;
  • TTL не используется;
  • поток не ждет ответов от главного узла в состоянии ошибки. В целом он игнорируется.

Дополнительная информация

Для стабилизации потока мы также применяем следующие методы:

  • Для параметра HAProxy hard-stop-after настроено очень малое значение. Когда HAProxy перезагружается с новым сервером в пуле записи, сервер автоматически завершает все существующие подключения к старому главному узлу.
    1. Настройка параметра hard-stop-after позволяет не ждать каких-либо действий от клиентов, кроме того, минимизируются негативные последствия возможного возникновения в кластере двух главных узлов. Важно понимать, что здесь нет никакой магии, и в любом случае проходит некоторое время, прежде чем старые связи будут разорваны. Но есть момент времени, после которого мы можем перестать ждать неприятных сюрпризов.
  • Мы не требуем постоянной доступности службы Consul. Фактически нам нужно, чтобы она была доступна только при отработке отказа. Если служба Consul не отвечает, то GLB продолжает работать с последними известными значениями и не принимает радикальных мер.
  • GLB настроен для проверки идентификационных данных недавно назначенного главного узла. Как и в случае с нашими контекстно-зависимыми пулами MySQL, выполняется проверка, чтобы подтвердить, что сервер действительно доступен для записи. Если мы случайно удалим идентификационные данные главного узла в Consul, то никаких проблем не возникнет, пустая запись будет проигнорирована. Если мы по ошибке запишем в Consul имя другого сервера (не главного), то и в этом случае ничего страшного: GLB не будет обновлять его и продолжит работать с последним допустимым состоянием.

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

Обнаружение сбоев с помощью orchestrator/raft

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

Сетевая изоляция ЦОД может вызвать путаницу: серверы внутри ЦОД могут взаимодействовать друг с другом. Схема orchestrator/raft также справляется с ситуациями полной сетевой изоляции ЦОД («ограждение» ЦОД). Как понять, кто на самом деле изолирован – серверы внутри данного ЦОД или все остальные ЦОД?

Ведущим становится узел, который получает поддержку большинства в группе (кворум). В схеме orchestrator/raft ведущий узел raft выполняет отработку отказов. Мы развернули узел orchestrator таким образом, что ни один отдельный ЦОД не может обеспечить большинство, в то время как его обеспечивают любые n-1 ЦОД.

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

Если в изолированном ЦОД находился главный узел, orchestrator инициирует отработку отказа, чтобы заменить его сервером одного из доступных ЦОД. Таким образом, ведущий узел orchestrator всегда будет находиться за пределами изолированного от сети центра обработки данных. Мы смягчаем последствия изоляции ЦОД, делегируя принятие решений кворуму доступных ЦОД.

Ускоренное оповещение

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

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

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

  • ничто не препятствует повышению статуса сервера (и, возможно, пользователь рекомендует этот сервер);
  • ожидается, что сервер сможет использовать все другие серверы в качестве реплик.

При этом orchestrator асинхронно начинает исправлять дерево репликации, что обычно занимает несколько секунд. В этом случае orchestrator сначала настраивает сервер как доступный для записи и немедленно объявляет о повышении его статуса (в нашем случае вносит запись в хранилище KV в Consul).

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

Полусинхронная репликация

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

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

Этого более чем достаточно для отправки изменений с главного узла в реплики в локальном ЦОД и даже в удаленные ЦОД. Мы выбрали достаточно низкое значение времени ожидания: 500 мс. С таким временем ожидания мы получили идеальный полусинхронный режим (без отката к асинхронной репликации), а также очень короткий период блокировки в случае отсутствия подтверждения.

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

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

Передача пакетов пульса

Это потребовало определенной доработки, чтобы служба pt-heartbeat могла спокойно работать с серверами, которые либо часто изменяют значение параметра read_only, либо становятся полностью недоступны. Вместо того, чтобы управлять запуском/остановкой службы pt-heartbeat на назначаемых/отключаемых главных узлах, мы решили запускать ее везде и всегда.

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

Делегирование задач orchestrator

Мы также делегировали orchestrator следующие задачи:

  • генерация Pseudo-GTID;
  • идентификация нового мастера как доступного для записи, очистка его состояния репликации;
  • идентификация старого мастера как доступного только для чтения (read_only), если это возможно.

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

Ограничения и недостатки

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

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

Это может привести к несогласованности состояний после восстановления сети. Отметим, что при изоляции центра обработки данных, в котором находится главный узел, приложения в этом ЦОД по-прежнему могут осуществлять запись на такой узел. Как уже говорилось ранее, пройдет некоторое время, прежде чем старый главный узел будет отключен, поэтому короткого периода «двоевластия» все-таки избежать не удастся. Мы стараемся смягчить последствия возникновения двух главных узлов в такой ситуации путем реализации метода STONITH изнутри самого изолированного ЦОД. Эксплуатационные издержки, направленные на полное предотвращение возникновения таких ситуаций, очень высоки.

д. Существуют и другие сценарии: отключение Consul во время отработки отказа, частичная изоляция ЦОД и т. Мы понимаем, что, работая с распределенными системами такого рода, невозможно закрыть все дыры, поэтому мы концентрируемся на самых важных.

Результаты

Наша система orchestrator/GLB/Consul обеспечила следующие преимущества:

  • надежное обнаружение отказов;
  • отработка отказов независимо от конкретных ЦОД;
  • отработка отказов без потерь в большинстве случаев;
  • поддержка сетевой изоляции ЦОД;
  • смягчение последствий, когда возникают два главных узла (работа в этом направлении продолжается);
  • отсутствие зависимости от взаимодействий;
  • общее время простоя 10-13 секунд в большинстве случаев.
    1. В редких ситуациях общее время простоя достигает 20 секунд, а в самых крайних случаях — 25 секунд.

Заключение

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

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»