Архитектура распределенного сигнализатора

Redis задняя часть алгоритм MongoDB
Архитектура распределенного сигнализатора

Уведомление об авторских правах: Эта статья является оригинальной статьей блоггера и не может быть воспроизведена без разрешения блоггера. Обратите внимание на публичный аккаунтОбмен технологиями (ID: jishuhui_2015)Связаться с автором можно.

1. Введение спроса

1. В распределенной среде убедитесь, что каждый порядковый номерУникальный во всей системеиз;

2. Серийный номерСортируемый, удовлетворяющий монотонно возрастающему закону;

3. В определенных сценариях может генерироватьБез правил(или не видно правил) серийный номер;

4. Сгенерированный серийный номеркак можно короче;

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

2. Схемный дизайн

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

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

1. сегментПервая стратегия называется «сегментной» и более подробно объясняется ниже:

Эмитент всего сегмента играет две важные роли: Redis и MongoDB.Теоретически MongoDB можно заменить MySQL или другими продуктами БД.

Число, сгенерированное генератором номеров сегментов, удовлетворяет закону монотонного возрастания, и число, сгенерированное за короткое время, не будет слишком длинным (начальное значение можно установить в соответствии с фактическими потребностями, например, 100).

Структура данных Redis (тип Hash)

key: <string>,表示业务主键/名称
value: {
  cur: <long>,表示当前序列号
  max: <long>,表示这个号段最大的可用序列号
}

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

--[[
  由于RedisTemplate设置的HashValueSerializer是GenericToStringSerializer,故此处的HASH结构中的
  VALUE都是string类型,需要使用tonumber函数转换成数字类型。
]]
local max = redis.pcall("HGET", KEYS[1], "max")  --获取一段序列号的max
local cur = redis.pcall("HGET", KEYS[1], "cur")  --获取当前发号位置
if tonumber(cur) >= tonumber(max) then  --没有超过这段序列号的上限
    local step = ARGV[1]
    if (step == nil) then  --没有传入step参数
        step = redis.pcall("HGET", KEYS[1], "step")  --获取这段序列号的step配置参数值
    end
    redis.pcall("HSET", KEYS[1], "max", tonumber(max) + tonumber(step))  --调整max参数值,扩展上限
end
return redis.pcall("HINCRBY", KEYS[1], "cur", 1)  --触发HINCRBY操作,对cur自增,并返回自增后的值

Примечание. Во время выполнения сценария lua redis, redis находится в состоянии BUSY, любая форма доступа к redis в это время вызоветJedisBusyExceptionисключение, поэтому логика обработки в lua-скрипте не слишком сложна.

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

Если вы, к сожалению, столкнулись с вышеуказанными проблемами, вам необходимо использовать клиент redis-cli для подключения к redis-серверу и отправить ему команду SCRIPT KILL для прекращения выполнения скрипта.

Если вы хотите избежать описанных выше проблем, вы также можете напрямую использовать RedisTemplate, предоставленный Springboot, который может поддерживать большинство команд Redis.

Структура данных MongoDB

{
 bizTag: <string>,  表示业务主键/名称
 max: <long>,  表示这个号段最大的可用序列号
 step: <int>, 每次分段的步长
 timestamp: <long>,  更新数据的时间戳(毫秒)
}

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

На данный момент сформирован прототип сегментного передатчика.

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

TCP尖刺
Для удаления «шипов» можно использоватьМодель с двойным буфером. Схематическая диаграмма выглядит следующим образом:
双Buffer模型

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

Когда Буфер-1 полностью израсходован, Буфер-2 можно без проблем подключить. Если расход Буфера-2 также достигает порога, то Буфер-1 можно снова открыть и так далее.

Далее давайте обсудимНенормальные/неисправные условия.

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

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

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

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

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

Если мы рассмотрим экстремальную ситуацию, когда Redis все еще не работает, мы можем использовать max, хранящийся в MongoDB, и назначить max+1 для cur (чтобы избежать выборки последнего сегмента числа, который оказывается недоступным).

③ MongoDB не работает. Эта проблема не очень серьезная, если шаг соответствующим образом удлинить (по крайней мере, число может быть принято в течение 20 минут), а время, когда Redis все еще нормально принимает число, можно использовать для спасения MongoDB. Однако, учитывая, что сервис монго может не так быстро восстанавливаться на практике, его можно взять в программе

Некоторые отказоустойчивые меры, такие как сегмент номера израсходован, служба mongo недоступна, и канал доступа к номеру напрямую закрыт до тех пор, пока MongoDB не сможет нормально использоваться; или программа дает шаг по умолчанию для расширения max в MongoDB на max+step*n (может потребоваться восстановление MongoDB только после N сегментов),

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

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

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

⑥ все вниз. У меня есть предложение mmp я не знаю, должен я сказать это или нет...

2. Снежинка

Вторая стратегия разработана Twitter, идея алгоритма более оригинальна, а реализация не слишком сложна.

snowflake
Приведенная выше схематическая диаграмма описывает двоичную структуру серийного номера.

Первая цифра не используется, это всегда 0, что означает положительное целое число;

Следующие 41 бит представляют отметку времени с точностью до миллисекунд. Для экономии места эту метку времени можно определить как количество миллисекунд, прошедших с определенного момента времени (по умолчанию в Java 1970-01-01 00:00:00);

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

Последние 12 цифр — это серийный номер, который увеличивается автоматически.

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

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

Конечно, алгоритм снежинки столкнется с двумя проблемами:

① Обозначение идентификатора машины. Эта проблема более заметна в распределенной среде.Обычным решением является использование Redis или Zookeeper для регистрации машины, чтобы гарантировать уникальность зарегистрированного идентификатора машины. чтобы решить

Проблема сильной зависимости от Redis или Zookeeper может записывать идентификатор машины в локальную файловую систему.

② Правила генерации идентификатора машины. В этой проблеме будет некоторая запутанность, потому что генерация идентификатора машины примерно соответствует трем условиям: а) чистое число типа int (10 бит), б) относительно стабильно, в) отличаться от других машин. Что касается элегантности и красоты, то она на втором месте. Для хранения идентификатора машины можно использовать структуру HASH.Правило KEY — «application-name.port.ip», где ip — это чистое число, преобразованное в длинное целое с помощью алгоритма, а VALUE — идентификатор машины. .

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

Предположим, что идентификатор службы (workerId) занимает 8 бит, идентификатор машинного отделения (rackId) занимает 2 бита, начиная с 1, workerId=00000001, RackId=01, MachineId=00000000101

Если он хранится в Redis, его представление выглядит следующим образом:

这里写图片描述
При сохранении в файле (рекомендуется файл свойств) имя файла будет sequence-client:8112:3232235742.properties, а содержимое файла будет следующим:
这里写图片描述
Если служба выдачи номеров подключена к сети, содержимое «application-name.port.ip» может быть получено напрямую.

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

3. Дизайн программы

Весь процесс нумерации можно разделить на три уровня:

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

策略层
Верхнее определение последовательности на самом деле является результатом нумерации. bizType — это определение бизнес-сценария выдачи номеров, таких как номер заказа, идентификатор пользователя и общий код для приглашения друзей.

Интерфейс инициализации стратегии выдачи номера — это работа по инициализации перед выдачей номера, а интерфейс генерации — основная запись для вызова генератора номеров.

Конечно, учитывая разные нештатные ситуации, добавляется обработчик, который отказывается выдавать номера (SequenceRejectedHandler). Реализация по умолчанию — только логирование. Пользователи могут реализовать этот обработчик в соответствии со своими потребностями, а затем с помощью метода set установить обработчик отклонения стратегия нумерации.

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

插件层
Видно, что плагин оформлен как "зарегистрирован», политика выдачи номеров может вступить в силу только после регистрации соответствующих подключаемых модулей.

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

Из определения SequencePlugin можно узнать, что плагин имеет приоритет (Order), который можно получить через getOrder().В этой системе нумерации, чем меньше значение Order, тем выше приоритет плагина. в. Кроме того, плагин имеет три важные операции:

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

after, что указывает на обработку после выдачи номера.

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

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

持久层
PersistRepository определяет основные методы CRUD, где persistId можно понимать как упомянутый выше BizType.

Все постоянные объекты начинаются с PersistModel.Segment и PersistDocument на приведенном выше рисунке определены для реализации отправителя сегмента.

4. Резюме

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

关注我们