Идентификатор распределенной системы

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

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

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

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

Создание распределенных идентификаторов.

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

  • Уникальный во всем мире. Это основное требование, которое нельзя повторять.
  • Тип числа, возрастающая тенденция: более поздний идентификатор должен быть больше предыдущего. Это считается из механизма хранения MySQL, и производительность записи данных должна быть гарантирована.
  • Короткая длина: это может повысить эффективность запросов, что также основано на спецификации базы данных MySQL, особенно когда идентификатор используется в качестве первичного ключа.
  • Информационная безопасность. Если идентификаторы генерируются постоянно, бизнес-информация неизбежно будет просачиваться и даже может быть угадана, поэтому она должна быть нерегулярной.
  • Высокая доступность и низкая задержка: создание идентификатора выполняется быстро, может выдерживать высокий уровень параллелизма, а задержка достаточно мала, чтобы не стать узким местом в бизнесе.

Несколько методов создания распределенных идентификаторов.

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

# 1. На основе UUID.

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

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

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

Преимущество:

  • Реализация кода достаточно проста в использовании.
  • С локальными сборками проблем с производительностью нет.
  • Благодаря глобально уникальной функции миграция базы данных не вызывает проблем.

Недостаток:

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

Подходящая сцена:

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

# 2. Автоинкремент на основе первичного ключа базы данных.

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

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

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

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

Преимущество:

  • Реализация проста, полагается только на базу данных, а стоимость низкая.
  • Идентификатор оцифровывается и монотонно увеличивается в соответствии с объемом хранилища базы данных и производительностью запросов.
  • Имеет определенную деловую читабельность.

Недостаток:

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

Подходящая сцена:

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

# 3. На основе автоматического увеличения первичного ключа базы данных с несколькими экземплярами.

Выше мы примерно объяснили, почему первичным ключом базы данных является auto-incremented, и обсудили ситуацию развертывания на одной машине.

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

Как реализуется эта схема?

То есть на основе auto_increment задайте размер шага роста шага, чтобы тренд ID, сгенерированный БД до этого, увеличивался и не повторялся.

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

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

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

Преимущество:

  • Решена единая точка генерации идентификатора при балансировке нагрузки.

Недостаток:

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

Подходящая сцена:

  • Объем данных невелик, и базу не нужно расширять.

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

№ 4. На основе алгоритма Snowflake.

Алгоритм Snowflake — это алгоритм генерации идентификаторов, используемый внутренними распределенными проектами Twitter. Теперь он с открытым исходным кодом и популярен. Ниже приведена диаграмма состава ID алгоритма Snowflake.

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

Среди них идентификатор 1-bit, неиспользуемый и отмеченный как 0.

Временная метка 41-bit используется для хранения разницы между временными метками.

Машинный код 10-digit может идентифицировать 1024 машинных узла. Если машина развернута в IDC, 10 цифр также можно разделить. Например, 5 цифр представляют собой идентификатор машинного зала, а 5 цифр представляют собой идентификатор машины, поэтому существует 32*32 комбинаций. , в целом достаточно.

Последняя 12-bit случайная последовательность используется для записи количества в миллисекундах, и узел может генерировать 4096 серийных номеров ID.

Таким образом, после всесторонних расчетов теоретическое количество запросов в секунду схемы алгоритма Snowflake составляет около 4,1 миллиона / с, а производительность достаточно высока, и этот метод может гарантировать, что идентификаторы, генерируемые каждым узлом в кластере, различны и увеличиваются. в интервале.

Преимущество:

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

Недостаток:

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

Подходящая сцена:

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

№ 5. На основе Redis.

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

Преимущество:

  • Инкрементальный упорядоченный, легко читаемый.
  • можно встретить определенные выступления.

Недостаток:

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

Подходящая сцена:

  • Требования к производительности не слишком высоки, малый бизнес легче, а работа Redis имеет определенные требования. Обратите внимание на проблемы с сетью и проблемы с одноточечным давлением. Если это распределенная ситуация, необходимо рассмотреть больше проблем. Поэтому этот метод редко используется в группе ситуаций.

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

# 6. На основе схемы LEAF.

Из приведенных выше нескольких распределенных схем ID видно, что она может решить определенные проблемы, но есть явные недочеты. По этой причине Meituan провел оптимизацию на основе схемы базы данных и предложил схему базы данных под названием Leaf-segment.

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

Решение сегмента Leaf состоит в том, чтобы каждый раз получать сегмент ID. После того, как сегмент израсходован, перейдите в базу данных, чтобы получить новый сегмент номера, что может значительно снизить нагрузку на базу данных. Как это сделать?

Очень просто, мы создаем таблицу следующим образом:

Среди них biz_tag используется для различения услуг, max_id представляет максимальное значение сегмента идентификационного номера, выделенного в настоящее время для biz_tag, step представляет длину сегмента номера, выделяемого каждый раз, а следующие desc и update_time представляют описание бизнеса и время последнего обновления сегмента номера соответственно.

Раньше считалось, что вам нужно обращаться к базе данных каждый раз, когда вы получаете идентификатор. Теперь вам нужно только установить достаточно разумное значение Step, например 1000. Теперь вы можете получить доступ к базе данных после того, как 1000 идентификаторов будут израсходованы. Это выглядит очень круто.

Теперь мы можем спроектировать весь процесс получения распределенных идентификаторов следующим образом:

  1. При регистрации пользователя пользовательскому сервису требуется идентификатор пользователя, он будет запрашивать интерфейс ID-сервиса (который является независимым приложением).
  2. Служба, которая генерирует идентификатор, запросит базу данных, чтобы найти идентификатор user_tag, текущий max_id0, а step=1000.
  3. Служба, создающая идентификатор, возвращает max_id и step пользовательской службе и обновляет max_id до max_id = max_id + step, который обновляется до 1000.
  4. Служба пользователя получает max_id=0, step=1000.
  5. Эта пользовательская служба может использовать идентификатор интервала [max_id + 1, max_id + step], который равен [1, 1000].
  6. Пользовательская служба сохраняет этот интервал в JVM.
  7. Когда пользовательской службе необходимо использовать идентификатор, идентификатор можно получить последовательно в интервале [1, 1000], и можно использовать метод getAndIncrement в AtomicLong.
  8. Если значение интервала израсходовано, заходим в интерфейс сервиса, который запрашивает ID производства, и получаем max_id как 1000, то есть можно использовать ID интервала [max_id + 1, max_id+step], который [1001, 2000] .

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

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

Преимущество:

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

Недостаток:

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

Вышеперечисленным недостаткам также необходимо уделять достаточно внимания. Официальная техническая команда тоже придумала переворот — double buffer.

Двойной буфер.

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

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

А методом двойного буфера, когда текущий сегмент номера израсходует 10%, проверить, готов ли следующий сегмент номера, если нет, обновить следующий сегмент номера.

Когда текущий сегмент номера израсходован, он переключается на следующий кэшированный сегмент номера для использования, а когда следующий сегмент номера израсходован до 10%, он проверяет, готов ли следующий сегмент номера, и так далее.

Ниже приводится краткое описание процесса:

  1. Текущий идентификатор сбора находится в буфере1, и каждый идентификатор сбора получен в буфере1.
  2. Когда идентификатор в буфере1 был использован до 100, что составляет 10% диапазона.
  3. Когда он достигнет 10%, сначала определите, был ли получен буфер 2. Если нет, немедленно инициируйте запрос на получение идентификатора потока. Этот поток присваивает полученному идентификатору значение buffer2.
  4. Если буфер1 израсходован, он автоматически переключится на буфер2.
  5. Когда буфер2 используется для 10%, он также запускает поток для его повторного получения и устанавливает его в буфер1
    туда и обратно.

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

Этот метод может повысить доступность бизнес-идентификатора в JVM, и рекомендуется, чтобы длина сегмента в 100 раз превышала количество запросов в секунду в пиковый период работы (значение опыта, которое можно установить в соответствии с вашим бизнесом), поэтому что даже если БД не работает, бизнес-идентификатор генерируется. Он также может поддерживаться в течение длительного времени и может быть эффективно совместим со случайным дрожанием сети и другими проблемами.

Преимущество:

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

Недостаток:

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

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

Этап динамической корректировки.

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

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

Предположим, что QPS услуги равен Q, длина сегмента номера равна L, а период обновления сегмента номера равен T, тогда Q * T = L.

Длина L фиксирована в начале, в результате чего по мере роста Q T будет становиться все меньше и меньше.

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

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

T < 15min,nextStep = step * 2
15min < T < 30min,nextStep = step
T > 30min,nextStep = step / 2

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

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

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

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

Спасибо, что прочитали эту статью.

Оставайтесь с нами, чтобы узнать больше.