Прийнятно покладатися на випадкові вставки, які є унікальними?


42

Я впроваджував мережевий протокол, і мені потрібні пакети, щоб вони мали унікальні ідентифікатори. Поки щойно я генерував випадкові 32-бітні цілі числа, і припускаючи, що це астрономічно малоймовірно, що відбудеться зіткнення протягом життя програми / з'єднання. Це взагалі вважається прийнятною практикою у виробничому коді, чи слід розробити більш складну систему для запобігання зіткнень?


47
Чому використання послідовного цілого числа не збирається його скорочувати?
whatsisname

20
Чому ви просто не використовуєте збільшення приросту? GUID , розроблені таким чином, щоб мати унікальні властивості, які ви описуєте, мають розмір 128 біт, а не 32.
Роберт Харві

21
Крім того, призначте номер каналу кожному підключеному комп'ютеру та використовуйте ідентифікаційний номер послідовності, що збільшується. У поєднанні два числа (з номером каналу, що приймає біти високого порядку) стає вашим новим унікальним ідентифікатором.
Роберт Харві

27
Якщо ваш "генератор випадкових чисел" гарантує, що певне число не повториться, поки не буде сформовано будь-яке інше число, це дуже поганий генератор випадкових чисел! За цією ж логікою єдиною можливою "випадковою" послідовністю викидання монети буде HTHTHTHTHT ....
alephzero

17
"Мені потрібні пакети, щоб вони мали унікальні ідентифікатори". Як наслідок порушення цієї вимоги? Якщо вам потрібні унікальні ідентифікатори, у найсуворішому читанні цього слова ви повинні мати централізовану систему видалення ідентифікаторів (наприклад, як MAC присвоюються окремим компаніям мережевих карт). Швидше за все, у вас є більш м’яке визначення "вимагати". Розуміння цього рівня м'якості різко змінить отримані вами відповіді.
Корт Аммон

Відповіді:


142

Остерігайся парадоксу дня народження .

Припустимо, ви генеруєте послідовність випадкових значень (рівномірно, незалежно) з набору розміру N (у вашому випадку N = 2 ^ 32).

Тоді, як правило, парадокс дня народження стверджує, що щойно ви створили близько sqrt (N) значень, є принаймні 50% шансу, що відбулося зіткнення, тобто, що в принаймні двох однакових значеннях у створена послідовність.

Для N = 2 ^ 32, sqrt (N) = 2 ^ 16 = 65536. Отже, після того, як ви створили близько 65k ідентифікаторів, більш імовірно, що два з них стикаються, ніж ні! Якщо ви генеруєте ідентифікатор за секунду, це станеться менше ніж за добу; Потрібно сказати, що багато мережевих протоколів працюють набагато швидше.


11
+1. На моїй останній роботі один з наших партнерів фактично використовував такий підхід для створення випадкових ідентифікаторів (не для мережевих пакетів, а для спільного бізнес-об’єкта, врешті створеного кінцевими клієнтами). Коли я запитував дані на це, я виявив, що в середньому кожен день було дві-три пари дублікатів. (На щастя, це зламало речі, лише якщо дублікати були створені протягом чотирьох годин один одного, що траплялося трохи рідше. Але все ж.)
ruakh

6
(натисніть тут, щоб відобразити математику) Для того, що варто, наближення $ \ sqrt {N} $ точно до постійного коефіцієнта; для $ N = 2 ^ {32} $ фактичний поріг дорівнює 77164, оскільки це найменше значення $ n $ таке, що $ \ prod_ {k = 1} ^ {n-1} (1 - k / N) <1 / 2. $
wchargin

4
@wchargin: насправді немає нічого магічного у ймовірності потрапляння 0,5; Примітно те, що ймовірність зростає відносно швидко зі збільшенням N. Якщо 32-бітні ідентифікатори мали б невеликий, але нетривіальний шанс випадкового зіткнення, 40-бітний ідентифікатор майже не матиме.
supercat

3
@supercat: Це все правда. Я просто зрозумів, що якщо надати таку константу, можна також дати точне значення :-)
wchargin

2
@wchargin: Я вважаю за краще подумати над тим, де потрібно починати турбуватися про дублікати. Якщо вийде набагато нижче sqrt (N), ймовірність зіткнень швидко знижується, до того, що можна сміливо сказати, що вони не відбудуться, якщо не буде серйозного дефекту у випадковому генераторі.
supercat

12

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

Один з алгоритмів для генерування UUID дозволить ефективно генерувати ідентифікатор, що складається з 122 випадкових бітів і припускати, що він буде унікальним. І два інші алгоритми покладаються на те, що хеш-значення, усічене до 122 біта, є унікальним, що має приблизно однаковий ризик зіткнення.

Таким чином, існують стандарти, які покладаються на 122 біт, який достатній для того, щоб зробити випадковий ідентифікатор унікальним, але 32 біта, безумовно, недостатньо. При 32-бітових ідентифікаторах потрібно лише близько 2¹⁶ ідентифікаторів, перш ніж ризик зіткнення досягне 50%, оскільки при 2¹⁶ ідентифікаційних номерах буде близько 2 to¹ пар, кожна з яких може бути зіткненням.

Навіть на 122 біта менше, ніж я б рекомендував у будь-якому новому дизайні. Якщо для вас важливо дотримуватися певної стандартизації, використовуйте UUID. В іншому випадку використовуйте щось більше, ніж 122 біти.

Хеш-функція SHA1 з виходом 160 біт більше не вважається захищеною, що частково, оскільки 160 біт недостатньо для гарантування унікальності виходів. Сучасні хеш-функції мають виходи від 224 до 512 біт. Випадково генеровані ідентифікатори повинні націлюватись на однакові розміри, щоб забезпечити унікальність з хорошим запасом безпеки.


12
SHA-1 вважається небезпечним, оскільки існують конкретні атаки (тобто невипадкові) проти самого алгоритму, які можуть знайти зіткнення швидше, ніж груба сила, а не тому, що існує велика ймовірність випадкового зіткнення. Приблизна оцінка говорить про те, що зі 122 бітами і швидкістю генерації 1 мільярд (10 ^ 9) ідентифікаторів в секунду, це займе 73 роки, перш ніж досягти 50% шансу зіткнення.
8bittree

sqrt(2^122)= 2,3 чотиримільйона
чотири мільйонів

2
@ 8bittree Біткойн-мережа обчислює 2⁷⁰ хешів SHA2 кожні 10 хвилин. Якщо б це було SHA1, то для зіткнення знадобилося б лише тиждень. Якби UUID вироблялися з тією ж швидкістю, що і обчислює хетінові біткойн, для зіткнення знадобиться менше 2 секунд.
kasperd

Біткойн - це намагання знайти зіткнення, і він користується величезною популярністю і має спеціальне обладнання, розроблене спеціально для пошуку хешей. Тепер, звичайно, якщо ОП планує створити диво популярну криптовалюту чи щось подібне, то їм може знадобитися сотні чи тисячі біт за ідентифікатор. Але відразу припускаючи, що ці вимоги можуть сприяти набагато більшій роботі, ніж потрібно, якщо достатньо стандартної бібліотеки UUID.
8bittree

@ 8bittree Якщо використання стандартних бібліотек є будь-якою перевагою, то обов'язково перейдіть на UUID. Але витягнути кілька випадкових байтів - urandomце не більше роботи, ніж використання бібліотеки UUID. Я просто реалізував обидва в Python для порівняння, і кожен метод складав рівно 25 символів вихідного коду.
kasperd

3

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

parseToInt(toString(System.currentTimeMillis()) + toString(Random.makeInt()))

Пройде довгий шлях. Очевидно, щоб по-справжньому гарантувати унікальність, вам потрібно використовувати UUID / GUID. Але вони можуть бути дорогими для створення, вищевикладене, ймовірно, достатньо, оскільки єдина можливість перекриття, якщо випадковий генератор мав дублікат у тій же мілісекунді.


9
1 мс може бути тривалим часом у деяких системах.
Quant_dev

7
Це насправді зовсім не знижує шансів зіткнення. Імовірність зіткнення після N чисел точно дорівнює оригінальному рішенням ОП. Хитрість використання поточного часу в якості насіння зазвичай використовується при послідовному призначенні ключів.
Корт Аммон

2
@Fresheyeball Я впевнений, що це не має ефекту, якщо Random.makeInt () фактично не генерує рівномірний розподіл від мінімального значення цілого до максимального значення цілого. Для кожного минулого значення, що генерується цією функцією, існує випадкове значення від makeInt, яке за цей точний крок часу генерує це значення, створюючи зіткнення. Оскільки всі значення makeInt зрівняльні, то ймовірність зіткнення точно дорівнює ймовірності зіткнення без додавання часу.
Корт Аммон

2
@CortAmmon це не використовує поточний час як насіння , і це, безумовно, має значення, якщо ці N числа не були створені протягом однієї мілісекунди, оскільки два числа з різними частинами мітки ніколи не стикаються. Якщо ви уявляєте приклад іншої відповіді, що один пакет в секунду має 50% шансу зіткнення менше ніж за один день, у цього є 0% шансу зіткнення за один пакет в секунду, принаймні, до часу, який currentTimeMillisзавершиться.
панно

3
@hobbs Ви забуваєте про цілісне переповнення. Тепер, якщо ключовим параметром ОП була структура, що містить 2 цілих числа, одне містить System.currentTimeMillisі одне, що містить Random.makeInt(), то ймовірність зіткнення значно знижується. Однак це не те, що робить код у цьому прикладі. З огляду на будь-який попередній час та випадкове значення та будь-який поточний час, ймовірність зіткнення в першу чергу ідентична ймовірності зіткнення двох випадкових чисел.
Корт Аммон

3

Це залежить як від ймовірності відмови, так і від наслідків відмови.

Я пам’ятаю дискусію між людьми з програмного та апаратного забезпечення, де люди, що працюють з обладнанням, вважали, що алгоритм з невеликою ймовірністю неправильних результатів (щось на зразок 1 відмови за 100 років) є прийнятним, а люди з програмним забезпеченням вважали це анафемою. Виявилося, що апаратники звичайно обчислювали очікувані показники відмов, і дуже звикли до думки, що іноді все даватиме неправильні відповіді, наприклад, через порушення, спричинені космічними променями; їм було дивно, що люди з програмним забезпеченням очікували 100% надійності.


1

Звичайно, у вас досить низька ймовірність того, що два випадкових 32-бітових цілих числа є послідовними, але це не зовсім неможливо. Відповідне інженерне рішення ґрунтується на тому, якими будуть наслідки зіткнень, оцінці кількості генерованих вами цифр, терміні експлуатації, за який потрібна унікальність, і що відбувається, якщо зловмисний користувач почне намагатися викликати зіткнення.


0

Можна припустити, що випадкові числа будуть унікальними, але ви повинні бути обережними.

Припускаючи, що ваші випадкові числа розподілені однаково, ймовірність зіткнення приблизно (n 2/2 ) / k, де n - кількість випадкових чисел, які ви генеруєте, і k - кількість можливих значень, яке може приймати "випадкове" число.

Ви не ставите число астрономічно малоймовірним, тому давайте його можна приймати як 1 на 2 30 (приблизно в мільярд). Далі скажемо, що ви генеруєте 2 30 пакетів (якщо кожен пакет представляє близько кілобайт даних, це означає приблизно терабайт загальних даних, великий, але не неможливо). Ми виявляємо, що нам потрібно випадкове число з принаймні 2 89 можливими значеннями.

По-перше, ваші випадкові числа повинні бути досить великими. 32-бітове випадкове число може мати не більше 2 32 можливих значень. Для зайнятого сервера, який ніде недостатньо високий.

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

По-третє, якщо вам потрібні випадкові числа, щоб вони були унікальними через з'єднання, а не тільки в межах з'єднання, ваш генератор випадкових чисел повинен бути добре насіннєвим. Це особливо актуально, якщо ваша програма часто перезапускається.

Загалом, генератори "звичайних" випадкових чисел у мовах програмування не підходять для такого використання. Генератори випадкових чисел, що надаються бібліотеками криптографії, зазвичай є.


0

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

Це, мабуть, не відповідає більшості генераторів випадкових чисел. Більшість із них використовують кілька поліномів високого порядку, які неодноразово застосовуються до насіння.

Однак, існує багато систем, які залежать від цієї схеми, як правило, з UUID. Наприклад, кожен об'єкт і актив у Second Life має 128-бітний UUID, генерований випадковим чином, і вони рідко стикаються.


0

Багато людей вже дали якісні відповіді, але я хотів би додати кілька незначних моментів: по-перше, пункт @nomadictype про парадокс дня народження є чудовим .

Ще один момент: випадковість не є такою простою для генерування та визначення, як зазвичай люди припускають. (Насправді, фактично доступні статистичні тести на випадковість ).

Зважаючи на це, важливо усвідомлювати помилковість азартних гравців , що є помилкою статистики, коли люди припускають, що незалежні події якимось чином впливають один на одного. Випадкові події, як правило, статистично не залежать один від одного, тобто, якщо ви випадково генеруєте "10", це не змінить вашої майбутньої ймовірності генерувати більше "10" s як мінімум. (Можливо, хтось може придумати виняток із цього правила, але я би сподівався, що так би було майже для всіх генераторів випадкових чисел).

Тож моя відповідь полягає в тому, що якби ви могли припустити, що достатньо довга послідовність випадкових чисел була унікальною, вони насправді не були б випадковими числами, оскільки це була б чітка статистична закономірність. Крім того, це означатиме, що кожне нове число не є незалежною подією, тому що якщо ви генеруєте, наприклад, 10, це означатиме, що ймовірність генерації будь-яких майбутніх 10 буде 0% (це не могло статися), плюс це означатиме, що ви збільшите шанси на отримання іншого числа, ніж 10 (тобто чим більше ви генеруєте чисел, тим більша ймовірність кожного з решти цифр).

Ще одне, що слід врахувати: шанс виграти Powerball від гри в одній грі - це, наскільки я розумію, приблизно 1 на 175 мільйонів. Однак шанси того, хто виграє, значно вищі за це. Вас більше цікавлять шанси на те, що хтось "виграє" (тобто бути дублікатом), ніж шанси будь-якого конкретного числа "виграти" / бути дублікатом.


Якщо один генерує 4096-бітні ідентифікатори таким чином, що кожен біт однаковою мірою може бути 0 або 1 незалежним від будь-якого іншого біта, сформованого в тому ж або будь-якому іншому ідентифікаторі, ймовірність того, що будь-які два ідентифікатори коли-небудь збігаються, буде бути зниклим малим, навіть якби випадково генерувати інший ідентифікатор для кожного із приблизно 4,0Е81 атомів у спостережуваному Всесвіті. Те, що такі ідентифікатори майже напевно були б унікальними, ні в якому разі не робило їх "невипадковими"
supercat

@supercat Це правда - з огляду на достатньо велику кількість навряд чи буде дублікатів, але це неможливо. Насправді залежить, наскільки поганими є наслідки не унікальності, чи є те, що ОП описує, гарною ідеєю.
EJoshuaS

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

@supercat Я думаю, що ти це неправильно читаєш, дивись іншу відповідь на парадокс від дня народження, я думаю, що зіткнення набагато ймовірніше, ніж ти розраховуєш - ОП просто використовує 32-бітове число, тому я не знаю, де ти ' отримуючи 4096 з, і як nomadictype показав вірогідність можливого зіткнення з числом такої довжини насправді напрочуд високий.
EJoshuaS

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

0

Не має значення, скільки бітів ви використовуєте - ви НЕ МОЖЕТЕ гарантувати, що два "випадкових" числа будуть різними. Натомість я пропоную вам скористатися чимось на зразок IP-адреси чи іншої мережевої адреси комп’ютера та послідовним номером, бажано послідовним номером HONKIN 'BIG - 128 біт (очевидно, без підпису) звучить як хороший початок, але 256 було б краще.


-1

Ні, звичайно ні. Якщо ви не використовуєте зразки без заміни, є ймовірність дублювання.

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