Это первая статья в моей серии о репликации баз данных. Обязательно ознакомьтесь с вводной статьей о сериале (здесь).

Репликация базы данных — это процесс копирования одной и той же базы данных на несколько узлов. Мы уже обсуждали причины этого в предыдущей статье.

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

  1. Один лидер
  2. Несколько лидеров
  3. Без лидера

Большинство современных систем используют один из этих алгоритмов. Мы также рассмотрим плюсы и минусы каждого из них, чтобы понять, что подходит для нашего варианта использования. В этой статье мы будем читать только об Single Leader, а остальное расскажем в следующих статьях.

Один лидер

  • В этом алгоритме из нескольких узлов один узел назначается лидером, а другие узлы — последователями.
  • Каждый запрос на чтение может проходить через ведомого или ведущего.
  • Каждый запрос на запись должен обрабатываться лидером. Всякий раз, когда клиент получает запрос на запись, он перенаправляет его лидеру.
  • Когда лидер получает запрос на запись. Он изменяет данные локально, а затем распространяет этот запрос на всех подписчиков. Теперь здесь возможны 2 операции: синхронная и асинхронная.

Синхронный против асинхронного

Ситуация, когда ведущий передает операцию записи подчиненному (после изменения ведущей копии БД) и не отвечает клиенту до тех пор, пока подчиненный не ответит успешно, называется синхронной записью.

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

Здесь у нас может быть 3 ситуации.

  1. Все подписчики синхронны. В этом случае, даже если один узел не работает, вся система операций записи будет приостановлена.
  2. Немногие последователи являются синхронными и несколько асинхронных: - В этом случае мы можем оставить несколько узлов синхронными, чтобы было несколько узлов, которые как бы синхронизируются с БД-лидером. Для других последователей асинхронной записи мы просто передадим запрос на запись и продолжим.
  3. Все подписчики являются асинхронными. В этом случае ведущий узел записывает данные локально, а затем передает информацию узлам-последователям и продолжает работу. Возможно, что ни один узел не записал эти данные, а ведущий узел выходит из строя, и в этом случае мы бы дали пользователю подтверждение того, что данные записаны, но теперь это будет недоступно.

Добавление новых подписчиков

  1. Обычно у БД есть возможность сохранять моментальный снимок с фиксированным интервалом времени.
  2. Всякий раз, когда новый узел должен быть настроен, он может получить последний снимок от мастера и запустить копию. Теперь, когда это будет завершено, он может проверить основную базу данных, чтобы получить изменения после моментального снимка, и сделать это. После этого оно будет захвачено.

Обработка сбоев узлов

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

Отказ подчиненного узла

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

Отказ ведущего узла

Обработка в этом сценарии немного сложна: -

  1. Обнаружение того, что ведущий узел не работает: -узел не может ответить по многим причинам, например проблема с сетью, сбой жесткого диска, выполняется сложный запрос, который требует времени. Таким образом, проверить, не работает ли узел, на самом деле довольно субъективно. Наиболее распространенным способом является проверка отсутствия ответа в течение порогового времени, например, скажем, 30 секунд. Этот интервал должен быть достаточно большим, чтобы обнаружить, что узел действительно не работает, иначе мы можем продолжать колебаться между процессом выбора n all.
  2. Выбор следующего ведущего узла.После того, как ведущий узел выходит из строя, один из ведомых узлов становится ведущим узлом (один из способов — сделать этот узел ведущим, в котором произошли самые последние изменения). )
  3. Настройка других узлов: -Теперь все узлы необходимо настроить так, чтобы они отправляли свои запросы на запись новому лидеру. В случае, если новый лидер появится после избрания лидера, также необходимо отметить о переизбрании.

Что может произойти из-за сбоя Лидерного узла

  1. Если лидер выходит из строя, у которого были записи, которые не были переданы новому лидеру (возможно, потому, что когда он сделал запись, а затем сделал еще одну, система вышла из строя до распространения). Если появится старый лидер, а новый лидер будет содержать конфликтующие записи, это станет проблемой. Как правило, мы игнорируем запросы старого лидера, но это снижает доверие клиента, поскольку мы дали успешный ответ на обе записи.
  2. По ошибке, если два лидера присутствуют одновременно, это может повредить согласованности системы и в конечном итоге испортит данные, и оба узла могут быть отключены.
  3. Правильный порог признания узла неработающим очень субъективен. Например, высокий трафик на узле может эффективно сократить время отклика. Меньшее значение может привести к выбору и настройке новых лидеров несколько раз.

Теперь мы рассмотрим, как операции записи распространяются на последователей от лидера.

Репликация на основе операторов

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

Это означает, что команды INSERT, UPDATE могут быть перенаправлены как есть.

Но здесь у нас могут быть такие ситуации, как: -

  • Команда UPDATE с использованием динамической функции, такой как RAND(), NOW(). В этом случае результат этой функции может быть разным на разных узлах.
  • Команда UPDATE использует какое-то условие в предложении where. В этом случае, если запросы не выполняются в одном и том же порядке на всех узлах, этот тип запроса может привести к разным типам данных на разных узлах.

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

Доставка журналов с опережающей записью

Мы читали о Write-Ahead Logging в моей предыдущей статье (здесь).

Просто помнить: -

  • В случае с LSM-деревьями этот журнал добавляется в память диска.
  • В случае B-дерева этот журнал добавляется в оперативную память.

В этом методе репликации этот журнал пересылается подписчикам, и они выполняют ту же операцию журнала со своими индексами и данными. Небольшая проблема в этом случае заключается в том, что мы тесно связаны с форматом механизма хранения базы данных. Потому что WAL содержит низкоуровневую информацию о памяти.

Следовательно, если БД меняет формат хранения с одной версии на другую, обновление БД не может быть выполнено в виде постепенного развертывания и, вероятно, потребует простоя всей системы.

Репликация на основе строк

При этом мы используем разные форматы журналов для WAL и для распространения записей среди подписчиков.

Например,

  1. Команда CREATE может нести информацию о новых значениях, которые были введены в ведущую БД.
  2. Команда DELETE может содержать поля, которые могут помочь определить, какая строка была удалена (в основном первичный ключ, но если у нас нет первичного ключа, у нас могут быть более старые значения данных, которые были удалены).
  3. Команда UPDATE может содержать поля, помогающие определить, какая строка была обновлена, а также новые данные, которыми данные были заменены.

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

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

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

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

  1. Читайте свои собственные записи
  2. Монотонное чтение
  3. Согласованное чтение префикса

Читайте свои собственные записи

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

Чтобы решить эту проблему, мы можем сделать следующее:

  • Если у нас есть ограничительная система записи, например, профиль пользователя может быть обновлен только самим пользователем, поэтому в этом случае мы можем перенаправить запрос ведущему, если пользователь хочет просмотреть свой собственный профиль, но мы передадим его ведомому узлу. если пользователь пытается просмотреть профиль других.
  • Это может не работать в системе, где что угодно может быть написано кем угодно. Что-то вроде добавления ответа на комментарий или комментирования публикации. Таким образом, в этом случае мы можем видеть последнюю отметку времени обновления, выполненного пользователем. Если это меньше (скажем, 1 минута), то мы можем перенаправить на лидера, в противном случае мы можем перенаправить на последователя. Нам может понадобиться сохранить этот lastUpdatedTimeStamp пользователя локально на клиенте.
  • Другой способ может заключаться в том, чтобы узнать последнюю отметку времени, когда пользователь сделал обновление (на стороне клиента), и переслать его подписчику, только если подписчик содержит изменения после этой отметки времени. Мы можем либо выбрать какого-то другого последователя, либо подождать, пока этот последователь «догонит».
  • Возможно, нам придется принять во внимание, что узлы находятся в разных центрах обработки данных и, возможно, в совершенно разных географических точках.

Монотонное чтение

Может возникнуть ситуация, когда мы делаем несколько запросов на чтение, которые могут оказаться на разных репликах. Но у этих реплик может быть разная задержка репликации. Таким образом, возможно, что мы получим больше данных от 1-го вызова и меньше данных от 2-го вызова.

Это приведет к плохому опыту. Это своего рода проблема согласованности.

Таким образом, мы можем решить это монотонным чтением. Они гарантируют более чем окончательную согласованность, но менее чем строго непротиворечивую.

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

Согласованное чтение префикса

Может возникнуть ситуация, когда команда записи, выполненная в момент времени t, будет получена позже, чем команда чтения, выполненная в момент времени t+1 на ведомом узле.

Чтобы предотвратить эту аномалию, мы должны гарантировать «согласованное чтение префиксов». Это означает, что порядок выполнения операторов будет иметь значение. Если последовательность записей происходит в определенном порядке, то любой, кто читает эти записи, увидит, что они появляются в том же порядке.