Генерація розподілених порядкових чисел?


103

У минулому я зазвичай впроваджував генерацію порядкових номерів, використовуючи послідовності бази даних.

наприклад Використання типу Postgres SERIAL http://www.neilconway.org/docs/sequences/

Мені цікаво, як, як генерувати порядкові номери для великих розподілених систем, де немає бази даних. Хтось має досвід чи пропозиції щодо найкращої практики щодо створення генерації послідовних номерів безпечним способом для декількох клієнтів?


Це питання давнє, але, будь ласка, дивіться мою нову відповідь stackoverflow.com/questions/2671858/…
Jesper M

Як ви користуєтеся nextval.org? Веб-сайт трохи дивний, і я не знаю, про що. Це якась команда Unix? Або якийсь хмарний сервіс?
diegosasw

Відповіді:


116

Гаразд, це дуже старе питання, яке я вперше бачу зараз.

Вам потрібно буде розмежувати між порядковими номерами та унікальними ідентифікаторами , які (необов'язково) легко сортуються за певними критеріями (як правило, час генерації). Істинні порядкові номери означають знання того, що всі інші працівники робили, і як такі вимагають спільного стану. Немає простого способу зробити це розподіленим, масштабним способом. Ви можете переглянути такі речі, як мережеві трансляції, діапазони вікон для кожного працівника та розподілити хеш-таблиці для унікальних ідентифікаторів працівників , але це велика робота.

Унікальні ідентифікатори - інша справа, є кілька хороших способів генерації унікальних ідентифікаторів децентралізованим чином:

а) Ви можете скористатися послугою мережі ID Сніжинки Сніжинки . Сніжинка - це:

  • Мережева послуга, тобто ви здійснюєте мережевий дзвінок, щоб отримати унікальний ідентифікатор;
  • який створює 64 бітні унікальні ідентифікатори, які впорядковуються за часом генерації;
  • і послуга є високомасштабною та (потенційно) високодоступною; кожен екземпляр може генерувати багато тисяч ідентифікаторів в секунду, і ви можете запускати кілька примірників у вашій LAN / WAN;
  • написана Scala, працює на JVM.

b) Ви можете генерувати унікальні ідентифікатори на самих клієнтах, використовуючи підхід, отриманий із способів виготовлення ідентифікаторів UUID та ідентифікаторів Сніжинки. Існує кілька варіантів, але щось є таким:

  • Найбільш значущі 40 або більше бітів: мітка часу; час генерації ідентифікатора. (Ми використовуємо найважливіші біти для позначки часу, щоб зробити ідентифікаційні файли впорядкованими за часом генерації.)

  • Наступні 14 біт або більше: лічильник на генератор, який збільшує кожен генератор по одному для кожного нового генерованого ідентифікатора. Це гарантує, що ідентифікатори, згенеровані в той же момент (однакові часові позначки), не перетинаються.

  • Останні 10 або більше біт: унікальне значення для кожного генератора. Використовуючи це, нам не потрібно робити синхронізацію між генераторами (що надзвичайно важко), оскільки всі генератори виробляють неперекриваються ідентифікатори через це значення.

c) Ви можете створювати ідентифікатори для клієнтів, використовуючи лише часову позначку та випадкове значення. Це дозволяє уникнути необхідності знати всі генератори та присвоїти кожному генератору унікальне значення. З іншого боку, такі посвідчення особи не гарантуються , що вони є унікальними у всьому світі, вони мають велику ймовірність бути унікальними. (Щоб зіткнутися, одному або більше генераторам потрібно було б створити одне і те ж випадкове значення в той самий час.) Щось уздовж рядків:

  • Найбільш значущі 32 біти: Timestamp, час генерації ідентифікатора.
  • Найменш значущі 32 біти: 32-бітні випадковість, сформовані заново для кожного ІД.

г) Простий вихід, використання UUID / GUID .


Cassandra підтримує лічильники ( cassandra.apache.org/doc/cql3/CQL.html#counters ), хоча є деякі обмеження.
Піюш Кансал

порядкові номери легко встановити позицію для індексу растрових зображень, але унікальний ідентифікатор іноді занадто довгий (64 біт або 128 біт), як можна унікальне відображення ідентифікатора на позицію індексу растрової карти? Дякую.
Брюссена

2
дуже сподобався варіант #b ..... це може призвести до великих масштабів і не спричинити особливих проблем з одночасністю
puneet

2
twitter/snowflakeбільше не підтримується
Navin

Якщо ви хочете реалізувати ліцензію на Apache2 опції B, перегляньте bitbucket.org/pythagorasio/common-libraries/src/master/… Ви також можете отримати її від maven io.pythagoras.common: розподілений-послідовність-id-генератор: 1.0 .0
Wpigott

16

Зараз є більше варіантів.

Хоча це питання "стареньке", я потрапив сюди, тому я думаю, що може бути корисним залишити відомі мені варіанти (поки що):

  • Ви можете спробувати Hazelcast . У своєму випуску 1.9 він включає розподілену реалізацію java.util.concurrent.AtomicLong
  • Ви також можете використовувати Zookeeper . Він надає методи для створення вузлів послідовності (додається до імен znode, хоча я вважаю за краще використовувати номери версій вузлів). Але будьте обережні з цим: якщо ви не хочете пропустити цифри у своїй послідовності, можливо, це не те, що ви хочете.

Ура


3
Zookeeper були варіантами я пішов з, є опис гарне і рецензія цього в списку розсилки , що я почав - mail-archive.com/zookeeper-user@hadoop.apache.org/msg01967.html
Jon

Джон, дякую за вказівку на цю нитку, саме такий тип рішення я думав. До речі, ви зробили код для подолання обмеження MAX_INT?
Паоло

15

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

Наприклад, вузол 1 генерує послідовність 001-00001 001-00002 001-00003 і т.д., а вузол 5 породжує 005-00001 005-00002

Унікальний :-)

Якщо ви хочете отримати якусь централізовану систему, ви можете розглянути можливість видачі вашого сервера послідовностей блоками. Це значно зменшує накладні витрати. Наприклад, замість того, щоб просити новий ідентифікатор від центрального сервера для кожного ідентифікатора, який повинен бути призначений, ви вимагаєте ідентифікатори в блоках по 10 000 від центрального сервера, а потім потрібно лише зробити ще один запит мережі, коли ви закінчитеся.


1
Мені подобається ваша думка про генерацію пакетного ідентифікатора, але це просто обмежує будь-яку можливість розрахунку в реальному часі.
ішан

Я реалізував подібний механізм. У тому, що, крім клієнтів, які кешують блок послідовностей, я додав кілька серверів-хостів, які кешують блоки послідовностей. (Один) головний генератор підтримується в деяких високодоступних сховищах або однопровідних хостах, доступних лише флоту серверів-хостів. Кешування сервера також допоможе нам у більшій бездротовій роботі, незважаючи на те, що єдиний майстер на мить знижується.
Janakiram

11

Це можна зробити за допомогою Redisson . Він реалізує розподілену та масштабовану версію AtomicLong. Ось приклад:

Config config = new Config();
config.addAddress("some.server.com:8291");

Redisson redisson = Redisson.create(config);
RAtomicLong atomicLong = redisson.getAtomicLong("anyAtomicLong");
atomicLong.incrementAndGet();

8

Якщо це дійсно має бути глобально послідовним, а не просто унікальним, то я б розглядав можливість створення єдиного, простого сервісу для видачі цих номерів.

Розподілені системи покладаються на безліч взаємодіючих невеликих сервісів, і для цього простого завдання вам дійсно потрібні чи ви справді отримаєте користь від іншого складного, розподіленого рішення?


3
... а що станеться, коли сервер, який працює з цією службою, виходить з ладу?
Навін

У вас є попередження, яке говорить комусь почати ще одну? Іноді це буде просто чудово. Я думаю, що у відповіді намагаються сказати "тримати речі в перспективі". Ідеальне розподілене рішення має свої недоліки, а іноді і простіше, тим краще.
nic ferrier

6

Існує кілька стратегій; але жодне, що я знаю, не може бути по-справжньому розподіленим і дати реальну послідовність.

  1. мати центральний генератор чисел. це не повинно бути великою базою даних. memcachedмає швидкий атомний лічильник, у переважній більшості випадків він досить швидкий для всього вашого кластера.
  2. розділити цілий діапазон для кожного вузла (як відповідь Стівена Шлансктера )
  3. використовувати випадкові числа або UUID
  4. використовувати деякий фрагмент даних разом з ідентифікатором вузла, і хеш все це (або hmac це)

особисто я схилявся до UUID або захоплювався, якщо мені хочеться мати просто-таки суміжний простір.


5

Чому б не використати (захищений від потоку) генератор UUID?

Я, мабуть, повинен розширити це.

Гарантовано, що UUID є унікальними у всьому світі (якщо уникати таких, заснованих на випадкових числах, де унікальність є дуже ймовірною).

Ваша вимога "розподіленого" дотримання, незалежно від кількості генераторів UUID, використовується глобальною унікальністю кожного UUID.

Ваші вимоги "безпечно для потоків" можна задовольнити, вибравши UUID-генератори "безпечні для потоків".

Ваша вимога "номер послідовності" передбачається, що відповідає гарантована глобальна унікальність кожного UUID.

Зауважте, що багато реалізацій послідовних номерів баз даних (наприклад, Oracle) не гарантують ні монотонно збільшуються, ні (навіть) збільшуються порядкові номери (за принципом "на з'єднання"). Це відбувається тому, що послідовна партія номерів послідовностей виділяється в "кешовані" блоки на основі кожного з'єднання. Це гарантує глобальну унікальність та підтримує адекватну швидкість. Але порядкові номери, фактично виділені (з часом), можна змішати, коли вони виділяються кількома з'єднаннями!


1
Незважаючи на те, що UUID працюють, проблема з ними полягає в тому, що ви повинні бути обережними, як зберігати їх, якщо вам в кінцевому підсумку потрібно проіндексувати створені ключі. Вони також зазвичай займають набагато більше місця, ніж монотонно збільшена послідовність. Дивіться percona.com/blog/2014/12/19/store-uuid-optimized-way для обговорення збереження їх з MySQL.
Павло

2

Розподілене генерування ідентифікаторів можна архівувати за допомогою Redis та Lua. Реалізація, доступна в Github . Він створює унікальні ідентифікатори, що розподіляються та k-сортують.


2

Я знаю, що це давнє запитання, але ми також стикалися з тією ж потребою, і не змогли знайти рішення, яке б відповідало нашій потребі. Наша вимога полягала в тому, щоб отримати унікальну послідовність (0,1,2,3 ... n) ІД, а значить, сніжинка не допомогла. Ми створили власну систему для генерування ідентифікаторів за допомогою Redis. Redis - це одна різьба, отже, механізм її списку / черги завжди давав би нам 1 попсету.

Що ми робимо, ми створюємо буфер ідентифікаторів. Спочатку в черзі буде від 0 до 20 ідентифікаторів, які готові відправити за запитом. Кілька клієнтів можуть запитувати ідентифікатор, і redis з'являтиметься 1 ідентифікатор одночасно. Після кожного спливаючого вікна зліва, ми вставляємо BUFFER + currentId праворуч, що продовжує список буфера. Впровадження тут


0

Я написав просту послугу, яка може генерувати напів унікальні непослідовні 64-бітні довгі числа. Його можна розгорнути на декількох машинах для надмірності та масштабування. Він використовує ZeroMQ для обміну повідомленнями. Для отримання додаткової інформації про те, як це працює, дивіться на сторінці github: zUID


0

Використовуючи базу даних, ви можете досягати 1,000+ кроків за секунду з одним ядром. Це досить просто. Ви можете використовувати власну базу даних як бекенд, щоб генерувати це число (як це має бути власний агрегат, з точки зору DDD).

У мене було те, що здається подібною проблемою. У мене було кілька розділів, і я хотів отримати лічильник зміщення для кожної. Я реалізував щось подібне:

CREATE DATABASE example;
USE example;
CREATE TABLE offsets (partition INTEGER, offset LONG, PRIMARY KEY (partition));
INSERT offsets VALUES (1,0);

Потім виконано наступне твердження:

SELECT @offset := offset from offsets WHERE partition=1 FOR UPDATE;
UPDATE offsets set offset=@offset+1 WHERE partition=1;

Якщо ваша програма дозволяє, ви можете виділити блок відразу (це був мій випадок).

SELECT @offset := offset from offsets WHERE partition=1 FOR UPDATE;
UPDATE offsets set offset=@offset+100 WHERE partition=1;

Якщо вам потрібна додаткова пропускна здатність, ви не можете виділити компенсації заздалегідь, ви можете реалізувати власну послугу, використовуючи Flink для обробки в режимі реального часу. Мені вдалося отримати приблизно 100 КК на одну секцію.

Сподіваюся, це допомагає!


0

Проблема схожа на: У світі iscsi, де кожен місяць / обсяг повинен бути однозначно ідентифікований ініціаторами, що працюють на стороні клієнта. Стандарт iscsi говорить, що перші кілька бітів повинні представляти інформацію про постачальника зберігання / виробника, а решта монотонно збільшуються.

Аналогічно, можна використовувати початкові біти в розподіленій системі вузлів для представлення nodeID, а решта можна монотонно збільшувати.


1
будь ласка, додайте ще детальну інформацію
Вед Пракаш

0

Одним із пристойних рішень є використання давно покоління. Це можна зробити за допомогою резервної копії розподіленої бази даних.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.