Эдвард Хуанг

20 декабря 2019 года мы выпустили TiDB 3.1 Beta. В этой версии TiDB представил две важные функции: Чтение последователем и Резервное копирование и восстановление (BR), а также обогатил подсказки оптимизатора.

В бета-версии TiDB 3.1 функция Follower Read является важной функцией с открытым исходным кодом. Чтобы понять, насколько важна эта функция, вам понадобится немного предыстории. Механизм хранения TiDB, TiKV, хранит данные в основных единицах, называемых Регионами. Несколько реплик Региона образуют плотную группу. Когда в Регионе появляется точка доступа для чтения, лидер Региона может стать узким местом чтения для всей системы. В этой ситуации включение функции Follower Read может значительно снизить нагрузку на лидера и повысить пропускную способность чтения всей системы за счет балансировки нагрузки между несколькими ведомыми.

Мы написали всего 26 строк кода для реализации Follower Read. В нашем тестовом тесте, когда эта функция была включена, мы могли примерно удвоить пропускную способность чтения всей системы.

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

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

Что такое чтение подписчиков

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

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

Реализация TiKV Follower Read гарантирует линеаризуемость однострочного чтения данных. В сочетании с изоляцией моментальных снимков в TiDB эта реализация также обеспечивает пользователям строго согласованное чтение.

Почему мы представили Follower Read

В архитектуре TiKV мы используем алгоритм Raft для обеспечения согласованности данных. Но в предыдущем механизме только лидер в регионе справлялся с большими нагрузками, и вычислительные ресурсы последователей не использовались. Поэтому мы ввели Follower Read для обработки запросов на чтение от подписчиков, чтобы снизить нагрузку на лидера.

Архитектура ТиКВ

TiKV использует алгоритм Raft, чтобы гарантировать согласованность данных. Цель TiKV — поддерживать более 100 ТБ данных, но одна группа Raft не может этого сделать. Поэтому нам нужно использовать несколько групп Raft, то есть Multi-Raft. См. наш предыдущий пост Проектирование и реализация Multi-Raft.

Внедрение Raft в архитектуру TiKV

TiKV делит данные на регионы. По умолчанию каждый регион имеет три реплики, и эти реплики региона образуют группу Raft. По мере увеличения количества записываемых данных, если размер региона или количество ключей достигает порогового значения, происходит разделение региона. И наоборот, если данные удаляются, а размер региона или количество ключей уменьшается, мы можем использовать слияние регионов, чтобы объединить меньшие соседние регионы. Это снимает некоторую нагрузку на Raftstore.

Проблема с архитектурой ТиКВ

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

Хотя TiKV может распределять регионы равномерно по каждому узлу, только лидер может предоставлять внешние услуги. Два других подписчика получают только данные, реплицированные от лидера, или голосуют за выбор лидера Raft при отработке отказа. Проще говоря, на уровне региона только лидер имеет дело с большими рабочими нагрузками, а последователи поддерживаются в качестве холодных резервов.

Иногда, когда есть какие-то горячие данные, ресурсы машины лидера региона полностью заняты. Хотя мы можем принудительно разбить Регион и потом перенести данные на другую машину, эта операция всегда лагает, а вычислительные ресурсы фолловеров не используются.

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

Решение — чтение подписчиков.

Как мы реализуем чтение подписчиков

Реализация Follower Read основана на алгоритме ReadIndex. Прежде чем перейти к чтению подписчиков, позвольте мне сначала представить ReadIndex.

Алгоритм ReadIndex

В этом разделе обсуждается, как алгоритм ReadIndex решает проблему линеаризуемости.

Проблема линеаризуемости алгоритма Рафта

Как мы можем гарантировать, что сможем прочитать последние данные о подписчиках? Можем ли мы просто вернуть клиенту данные о последнем зафиксированном индексе подписчиков?

Ответ — нет, потому что Raft — это алгоритм, основанный на кворуме. Чтобы зафиксировать журнал, вам не нужно успешно записывать данные на все реплики региона (также известные как пиры). Вместо этого, когда журнал фиксируется для большинства одноранговых узлов, это означает, что он успешно записан в TiKV. В этом случае локальные данные о подписчике могут быть устаревшими. Это нарушает линеаризуемость.

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

ReadIndex как решение проблемы линеаризуемости

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

  1. Когда текущий лидер обрабатывает запрос на чтение, система записывает последний зафиксированный индекс текущего лидера.
  2. Текущий лидер гарантирует, что он по-прежнему является лидером, отправляя сигнал пульса кворуму.
  3. После того, как лидер подтвердит свое состояние лидера, он возвращает эту запись.

Таким образом, линеаризуемость не нарушается. Хотя алгоритму ReadIndex требуется сетевая связь для большей части кластера, эта связь просто передает метаданные. Это может значительно сократить сетевой ввод-вывод и, таким образом, увеличить пропускную способность. Кроме того, TiKV выходит за рамки стандартного алгоритма ReadIndex и реализует LeaseRead, который гарантирует, что аренда лидера короче, чем время ожидания переизбрания нового лидера.

Внедрение и проблемы Follower Read

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

Текущая реализация Follower Read

Как мы можем гарантировать, что сможем прочитать последние данные о подписчиках? Возможно, вы рассмотрите эту общую политику: запрос пересылается лидеру, а затем лидер возвращает последние зафиксированные данные. Фолловер используется как прокси. Идея проста и безопасна в реализации.

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

Основываясь на этой мысли, TiDB в настоящее время реализует функцию чтения последователя следующим образом:

1. Когда клиент отправляет запрос на чтение ведомому, ведомый запрашивает зафиксированный индекс ведущего.

2. После того, как ведомый получит последний зафиксированный индекс ведущего и применит этот индекс к себе, ведомый возвращает эту запись клиенту.

Вопросы для чтения подписчиком

В настоящее время функция чтения подписчиков имеет две проблемы:

Проблема 1: линеаризуемость

TiKV использует асинхронный Apply, который может нарушить линеаризуемость. Хотя лидер сообщает подписчикам последний зафиксированный индекс, лидер применяет этот журнал асинхронно. Последователь может применить этот журнал до того, как это сделает лидер. В результате мы можем прочитать эту запись на ведомом, но может потребоваться некоторое время, чтобы прочитать ее на лидере.

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

  • Когда мы выполняем запрос на получение точки, мы используем UINT64_MAX в качестве метки времени для чтения данных. Поскольку осуществляется доступ только к одной строке данных только в одном регионе, мы можем гарантировать изоляцию моментальных снимков для транзакции.
  • Если данные для зафиксированной временной метки (ts) могут быть прочитаны на последователе, но другие операторы SQL в той же транзакции временно не могут прочитать эту ts на лидере, неизбежно возникает блокировка. Последующая обработка блокировки может гарантировать изоляцию моментальных снимков.

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

Проблема 2: задержка чтения

Наша текущая реализация Follower Read по-прежнему использует вызов удаленной процедуры (RPC), чтобы запросить у лидера зафиксированный индекс. Поэтому, хотя мы и используем Follower Read, задержка чтения остается высокой.

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

Таким образом, функция Follower Read является прекрасной оптимизацией.

Контрольные показатели чтения подписчиков

Мы провели тесты в четырех сценариях и пришли к следующим выводам:

  • В многорегиональной архитектуре, когда включено чтение последователя, клиент может считывать данные из локального центра обработки данных. Это уменьшило использование пропускной способности сети. Когда объем данных достиг определенного масштаба, Follower Read значительно улучшил производительность системы. (См. Сценарий №1 и Сценарий №2 ниже.)
  • Follower Read может эффективно увеличить пропускную способность чтения всей системы и сбалансировать давление чтения в горячих точках. (См. Сценарий №3 и Сценарий №4 ниже.)

Вы можете прочитать этот раздел, чтобы получить более подробную информацию.

Тестовая среда

Мы использовали следующие машины для тестирования:

  • UCloud 16C 32 ГБ 100 ГБ SSD в Пекине, Китай
  • UCloud 16C 32 ГБ 100 ГБ SSD в Шанхае, Китай

Мы проверили пропускную способность и задержку и определили следующее:

  • Пропускная способность 297 Мбит/сек.
  • Задержка варьировалась от 27,7 до 28,3 мс.

Мы использовали Yahoo! Cloud Serving Benchmark (YCSB) для импорта 10 000 000 строк данных и проверки производительности сканирования для разных строк.

Мы выполнили необработанные тесты сканирования ключ-значение (KV). Настроив количество ключей сканирования, мы получили запросы разных объемов данных для тестирования.

Результаты теста

Сценарий №1

Описание сценария: Подписчик и клиент находились в одном центре обработки данных (ЦОД). Лидер находился в дата-центре в другом регионе. Лидер обслуживал запросы на чтение.

Результаты теста:

Анализ результатов:

Когда задержка между клиентом и TiKV увеличилась из-за механизма скользящего окна, соответствующая пропускная способность TCP-соединения уменьшилась. Когда объем данных был большим, мы даже наблюдали много DeadlineExceededlogs.

Причина высокой задержки P99 для TiKV заключается в том, что истек срок аренды лидера. Одна треть запросов на чтение должна быть прочитана ReadIndex перед выполнением.

Сценарий №2

Описание сценария: Подписчик и клиент находились в одном и том же центре обработки данных. Лидер находился в дата-центре в другом регионе. Фолловер обслуживал запросы на чтение.

Результаты теста:

Анализ результатов:

Follower Read уменьшил междисциплинарный трафик. Таким образом удалось избежать влияния механизма скользящего окна TCP на сеть с высокой задержкой. Когда объем данных в запросе был небольшим, влияние на задержку между контроллерами домена было большим. Когда объем данных в запросе был большим, влияние на задержку между контроллерами домена было небольшим. В этом случае, когда было включено Follower Read, пропускная способность чтения не сильно изменилась.

Сценарий №3

Описание сценария: Лидер и клиент находились на одном узле. Лидер обслуживал запросы на чтение.

Результаты теста:

Анализ результатов:

У клиента была более высокая задержка, чем у TiKV. Мы запустили netstat и обнаружили, что в TCP Send-Q было много пакетов. Это означает, что механизм скользящего окна TCP ограничивал пропускную способность.

Сценарий №4

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

Результаты теста:

Анализ результатов:

Когда объем данных в запросе на чтение был небольшим, а время обработки запроса было коротким, влияние Follower Read на задержку было большим. Это было связано с тем, что функция чтения последователя включала внутренний удаленный вызов процедуры (RPC). Операция RPC занимала большую часть времени обработки запроса на чтение. Следовательно, это оказало большое влияние на скорость чтения.

Что дальше для чтения подписчиков

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

Стратегии для данных о различной температуре

Вы можете задать мне вопрос: если я выполню большой запрос к таблице, повлияет ли это на текущую транзакцию онлайн-обработки транзакций (OLTP)? Хотя у нас есть очередь приоритетов ввода-вывода, встроенная в TiKV, которая отдает приоритет важным запросам OLTP. , он по-прежнему потребляет ресурсы машины с состоянием лидера.

Крайний случай — это небольшая горячая таблица с гораздо большим количеством операций чтения, чем операций записи. Хотя горячие данные кэшируются в памяти, когда данные очень горячие, возникает узкое место ЦП или сетевого ввода-вывода.

В нашем предыдущем посте TiDB Internal (III) — Планирование упоминается, что мы используем отдельный компонент под названием Драйвер размещения (PD) для планирования и балансировки нагрузки регионов в кластере TiKV. В настоящее время работа по планированию ограничена разделением, слиянием и перемещением регионов, а также перемещением лидера. Но в ближайшем будущем TiDB сможет динамически использовать разные стратегии репликации для данных разной степени нагрева.

Например, если мы обнаружим, что небольшая таблица очень горячая, PD может быстро позволить TiKV динамически создавать несколько (более трех) реплик этих данных только для чтения и использовать функцию чтения последователя, чтобы отвлечь нагрузку от лидера. Когда давление нагрузки снижается, реплики только для чтения уничтожаются. Поскольку каждый регион в TiKV небольшой (по умолчанию 96 МБ), TiDB может быть очень гибким и легким при этом.

Локальное чтение на основе чтения последователя

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

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

Как упоминалось выше, текущая реализация Follower Read мало что делает для уменьшения задержки чтения. Можем ли мы получить локальный зафиксированный журнал, не спрашивая лидера? Да, в некоторых случаях.

Как мы обсуждали в нашем предыдущем посте MVCC в TiKV, TiDB использует управление параллелизмом нескольких версий (MVCC) для управления параллелизмом транзакций. Каждая запись имеет уникальный, монотонно возрастающий номер версии.

Далее мы объединим чтение подписчиков с MVCC. Если номер версии данных в последнем зафиксированном журнале на локальном узле больше, чем номер версии транзакции, инициированной клиентом, система возвращает данные в последнем зафиксированном журнале на локальном узле. Это не нарушит свойства транзакций атомарность, согласованность, изоляция, долговечность (ACID).

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

Вывод

Функция чтения последователя использует ряд механизмов балансировки нагрузки для разгрузки запросов на чтение от лидера Raft на его последователей в регионе. Он обеспечивает линеаризуемость однострочных операций чтения данных и предлагает строго согласованные операции чтения в сочетании с изоляцией моментальных снимков TiDB.

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

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

Первоначально опубликовано на www.pingcap.com5 февраля 2020 г.