Aceitável confiar em ints aleatórios sendo únicos?

42

Estou implementando um protocolo de rede e exijo que os pacotes tenham identificadores exclusivos. Até agora, acabei de gerar números inteiros aleatórios de 32 bits e assumindo que é astronomicamente improvável que ocorra uma colisão durante a vida útil de um programa / conexão. Isso geralmente é considerado uma prática aceitável no código de produção ou deve-se criar um sistema mais complexo para evitar colisões?

Fénix
fonte
47
Por que o uso de um número inteiro seqüencial não é suficiente?
Whatsisname
20
Por que você não usa apenas um int incremental? GUIDs , que são projetados para ter as propriedades de exclusividade que você descreve, são 128 bits de tamanho, não 32.
Robert Harvey
21
Como alternativa, atribua um número de canal a cada computador conectado e use um ID de sequência incremental. Os dois números combinados (com o número do canal ocupando os bits de alta ordem) tornam-se seu novo ID exclusivo.
Robert Harvey
27
Se o seu "gerador de números aleatórios" garantir que um número específico não será repetido até que todos os outros números tenham sido gerados, é um gerador de números aleatórios muito ruim! Pela mesma lógica, a única sequência "aleatória" possível de sorteio seria HTHTHTHTHT .... #
alephzero
17
"Eu exijo que os pacotes tenham identificadores exclusivos" Qual é a conseqüência desse requisito ser violado? Se você precisar de identificadores exclusivos, na leitura mais estrita da palavra, deverá ter um sistema centralizado, identificando os identificadores (como a maneira como os MACs são atribuídos a empresas de placas de rede individuais). Provavelmente você tem uma definição mais suave de "exigir". Compreender esse nível de suavidade mudará drasticamente as respostas que você recebe.
Cort Ammon

Respostas:

142

Cuidado com o paradoxo do aniversário .

Suponha que você esteja gerando uma sequência de valores aleatórios (uniformemente, independentemente) a partir de um conjunto de tamanho N (N = 2 ^ 32 no seu caso).

Em seguida, a regra geral para o paradoxo do aniversário indica que, depois de gerar sobre os valores sqrt (N), há pelo menos 50% de chance de uma colisão, ou seja, de que haja pelo menos dois valores idênticos no sequência gerada.

Para N = 2 ^ 32, sqrt (N) = 2 ^ 16 = 65536. Portanto, depois de gerar cerca de 65k identificadores, é mais provável que dois deles colidam do que não! Se você gerar um identificador por segundo, isso aconteceria em menos de um dia; escusado será dizer que muitos protocolos de rede operam muito mais rápido que isso.

nomadictype
fonte
11
+1. No meu último trabalho, um de nossos parceiros realmente usou essa abordagem para gerar identificadores aleatórios (não para pacotes de rede, mas para um objeto de negócios compartilhado criado por clientes finais). Quando eu perguntei os dados com um olho nisso, descobri que, em média, havia dois a três pares de duplicatas todos os dias. (Felizmente, essas coisas só violava se as duplicatas foram criados dentro de quatro horas um do outro, o que aconteceu um pouco menos frequentemente, mas ainda..)
ruach
6
(clique aqui para renderizar matemática) Pelo que vale a pena, a aproximação $ \ sqrt {N} $ é precisa até um fator constante; para $ N = 2 ^ {32} $, o limite real é 77164, pois esse é o menor valor de $ n $, de modo que $ \ prod_ {k = 1} ^ {n-1} (1 - k / N) <1 / 2. $
wchargin
4
@charchar: Não há realmente nada mágico sobre a probabilidade de atingir 0,5; o que é notável é que a probabilidade está aumentando relativamente rápido com o aumento de N. Se os identificadores de 32 bits teriam uma chance leve, mas não trivial, de uma colisão aleatória, um identificador de 40 bits não teria quase nenhum.
Supercat 31/12
3
@ supercat: Isso é tudo verdade. Eu só percebi que se alguém fornece tal uma constante, pode muito bem dar um valor exato :-)
wchargin
2
@chargin: Eu prefiro pensar em termos de onde é preciso começar a se preocupar com duplicatas. Se alguém fica muito abaixo do sqrt (N), as probabilidades de colisões diminuem rapidamente, a ponto de dizer com segurança que elas não acontecerão, a menos que haja um defeito grave no gerador aleatório.
Supercat 31/12
12

É amplamente considerado aceitável contar com números aleatórios únicos, se esses números tiverem bits suficientes. Existem protocolos criptográficos nos quais a repetição de um número aleatório interrompe toda a segurança. E desde que não haja vulnerabilidades sérias no gerador de números aleatórios em uso, isso não foi um problema.

Um dos algoritmos para gerar UUIDs gerará efetivamente um ID que consiste em 122 bits aleatórios e assume que ele será único. E dois dos outros algoritmos contam com um valor de hash truncado para 122 bits, sendo único, o que tem aproximadamente o mesmo risco de colisões.

Portanto, existem padrões que dependem de 122 bits para tornar um ID aleatório único, mas 32 bits definitivamente não são suficientes. Com IDs de 32 bits, são necessários apenas dois IDs antes que o risco de uma colisão atinja 50%, porque com IDs de 2 bits haverá perto de 2 ³ pares cada um dos quais pode ser uma colisão.

Até 122 bits é menor do que eu recomendaria em qualquer novo design. Se seguir alguma padronização for importante para você, use UUIDs. Caso contrário, use algo maior que 122 bits.

A função hash SHA1 com uma saída de 160 bits não é mais considerada segura, o que é parcialmente porque 160 bits não são suficientes para garantir a exclusividade das saídas. Funções hash modernas têm saídas de 224 a 512 bits. Os IDs gerados aleatoriamente devem ter o mesmo tamanho para garantir exclusividade com uma boa margem de segurança.

Kasperd
fonte
12
O SHA-1 é considerado inseguro porque existem ataques específicos (ou seja, não aleatórios) contra o próprio algoritmo, que podem encontrar colisões mais rapidamente que a força bruta, não porque há uma grande chance de uma colisão aleatória. Uma estimativa aproximada diz que, com 122 bits e uma taxa de geração de 1 bilhão (10 ^ 9) de IDs por segundo, levaria mais de 73 anos para atingir 50% de chance de colisão.
8bittree
sqrt(2^122)= 2,3 quadrilhões de quadrilhões de UUIDs
noɥʇʎԀʎzɐɹƆ
2
@ 8bittree A rede bitcoin calcula 2⁷⁰ hashes SHA2 a cada 10 minutos. Se fossem hashes SHA1, levaria apenas uma semana para produzir uma colisão. Se os UUIDs fossem produzidos na mesma velocidade que o bitcoin calcula os hashes, levaria menos de 2 segundos para produzir uma colisão.
kasperd
O Bitcoin trata de tentar encontrar colisões, e é imensamente popular e teve hardware dedicado projetado especificamente para encontrar hashes. Agora, com certeza, se o OP estiver planejando criar uma criptomoeda muito popular, ou algo semelhante, ele poderá precisar de centenas ou milhares de bits por ID. Mas imediatamente assumindo que esses são os requisitos pode incentivar muito mais trabalho do que o necessário, se uma biblioteca UUID padrão for suficiente.
8bittree
@ 8bittree Se o uso de bibliotecas padrão tiver alguma vantagem, opte por UUID. Mas extrair alguns bytes aleatórios urandomnão é mais trabalho do que usar uma biblioteca UUID. Acabei de implementar os dois no Python para comparação, e cada método tinha exatamente 25 caracteres de código fonte.
kasperd
3

Eu chamaria isso de má prática. O número aleatório gerado simplesmente não cria números únicos, apenas cria números aleatórios. É provável que uma distribuição aleatória inclua algumas duplicatas. Você pode tornar essa circunstância aceitável improvável adicionando um elemento de tempo. Se você obtiver a hora atual do relógio do sistema em milissegundos. Algo assim:

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

Irá um longo caminho. Obviamente, para realmente garantir a exclusividade, você precisa usar o UUID / GUID. Mas eles podem ser caros de gerar, o acima exposto provavelmente é suficiente, pois a única possibilidade de sobreposição é se a geração aleatória tiver uma duplicata no mesmo milissegundo.

Fresheyeball
fonte
9
1ms pode demorar muito tempo em alguns sistemas.
Quant_dev 30/12/2016
7
Na verdade, isso não diminui a chance de colisão. A probabilidade de uma colisão após N números é exatamente igual à da solução original do OP. O truque de usar o tempo atual como uma semente é normalmente usado ao atribuir chaves sequencialmente.
Cort Ammon
2
@Fresheyeball Estou confiante de que isso não tem efeito, a menos que Random.makeInt () não gere uma distribuição uniforme do valor mínimo do número inteiro para o valor máximo do número inteiro. Para cada valor passado gerado por esta função, existe um valor aleatório de makeInt que, para esta etapa exata do tempo, gera esse valor, criando uma colisão. Como todos os valores de makeInt são equivalentes, a probabilidade de uma colisão é exatamente igual à da probabilidade de uma colisão sem a adição de tempo.
Cort Ammon
2
@CortAmmon, isso não está usando o tempo atual como uma semente e definitivamente faz a diferença, desde que esses números N não tenham sido gerados durante o mesmo milissegundo, porque dois números com partes diferentes de timestamp nunca colidem. Se você imaginar o exemplo da outra resposta de um pacote por segundo com 50% de chance de colisão em menos de um dia, este tem 0% de chance de colisão com um pacote por segundo, pelo menos até o momento em que se currentTimeMillisaproxima.
hobbs
3
@ Hobbs Você esquece o excesso de números inteiros. Agora, se a chave usada pelo OP era uma estrutura contendo 2 números inteiros, um contendo System.currentTimeMillise um contendo Random.makeInt(), a probabilidade de uma colisão diminui substancialmente. No entanto, não é isso que o código neste exemplo faz. Dado qualquer tempo e valor aleatório anteriores, e qualquer tempo atual, a probabilidade de colisão é idêntica à probabilidade de dois números aleatórios colidirem em primeiro lugar.
Cort Ammon
3

Depende da probabilidade de falha e das consequências da falha.

Lembro-me de um debate entre as pessoas de software e hardware, nas quais as pessoas consideravam aceitável um algoritmo com uma pequena probabilidade de resultados errados (algo como 1 falha em 100 anos), e as pessoas de software pensavam que isso era um anátema. Verificou-se que o pessoal do hardware calculava rotineiramente as taxas de falhas esperadas e estava muito acostumado à idéia de que tudo daria respostas erradas ocasionalmente, por exemplo, devido a distúrbios causados ​​por raios cósmicos; eles acharam estranho que o pessoal do software esperasse 100% de confiabilidade.

Michael Kay
fonte
1

Claro, você tem probabilidades muito baixas de dois inteiros aleatórios de 32 bits serem sequenciais, mas não é completamente impossível. A decisão de engenharia apropriada é baseada em quais seriam as consequências de colisões, uma estimativa do volume de números que você está gerando, a vida útil em que a exclusividade é necessária e o que acontece se um usuário mal-intencionado começar a tentar causar colisões.

Sean McSomething
fonte
0

Pode ser aceitável assumir que os números aleatórios serão únicos, mas você deve ter cuidado.

Supondo que seus números aleatórios sejam igualmente distribuídos, a probabilidade de uma colisão é aproximadamente (n 2/2 ) / k, em que n é o número de números aleatórios que você gera e k é o número de valores possíveis que um número "aleatório" pode assumir.

Você não coloca um número improvável em termos astronômicos, então vamos considerá-lo como 1 em 2 30 (aproximadamente um bilhão). Vamos dizer ainda que você gera 2 30 pacotes (se cada pacote representa cerca de um kilobyte de dados, isso significa cerca de um terabyte de dados totais, grande, mas não inimaginavelmente). Achamos que precisamos de um número aleatório com pelo menos 2 89 valores possíveis.

Em primeiro lugar, seus números aleatórios precisam ser grandes o suficiente. Um número aleatório de 32 bits pode ter no máximo 2 32 valores possíveis. Para um servidor ocupado que não chega nem perto do ponto alto.

Em segundo lugar, seu gerador de números aleatórios precisa ter um estado interno suficientemente grande. Se o seu gerador de números aleatórios tiver apenas um estado interno de 32 bits, não importa o tamanho do valor que você gerar, você ainda obterá no máximo 2 32 valores possíveis.

Em terceiro lugar, se você precisar que os números aleatórios sejam únicos nas conexões, e não apenas dentro de uma conexão, seu gerador de números aleatórios precisará ser bem distribuído. Isto é especialmente verdade se o seu programa for reiniciado com freqüência.

Em geral, os geradores de números aleatórios "regulares" nas linguagens de programação não são adequados para esse uso. Os geradores de números aleatórios fornecidos pelas bibliotecas de criptografia geralmente são.

Peter Green
fonte
0

Embutido em algumas das respostas acima está a suposição de que o gerador de números aleatórios é realmente 'plano' - que a probabilidade de dois números serem o próximo gerado é a mesma.

Provavelmente isso não é verdade para a maioria dos geradores de números aleatórios. A maioria usa polinômio de alta ordem, aplicado repetidamente a uma semente.

Dito isto, existem muitos sistemas por aí que dependem desse esquema, geralmente com UUIDs. Por exemplo, todos os objetos e ativos no Second Life têm um UUID de 128 bits, gerado aleatoriamente e raramente colidem.

Anniepoo
fonte
0

Muitas pessoas já deram respostas de alta qualidade, mas eu gostaria de acrescentar alguns pontos menores: primeiro, o ponto de @nomadictype sobre o paradoxo do aniversário é excelente .

Outro ponto: a aleatoriedade não é tão simples de gerar e definir quanto as pessoas podem assumir. (Na verdade, existem na realidade os testes estatísticos para aleatoriedade disponível).

Dito isso, é importante estar ciente da falácia do jogador , que é uma falácia estatística em que as pessoas assumem que eventos independentes de alguma forma se influenciam. Eventos aleatórios geralmente são estatisticamente independentes um do outro - ou seja, se você gerar um "10" aleatoriamente, isso não altera sua probabilidade futura de gerar mais "10" s no mínimo. (Talvez alguém possa apresentar uma exceção a essa regra, mas eu esperaria que esse fosse o caso de praticamente todos os geradores de números aleatórios).

Portanto, minha resposta é que, se você pudesse supor que uma sequência suficientemente longa de números aleatórios fosse única, eles não seriam realmente números aleatórios porque esse seria um padrão estatístico claro. Além disso, isso implicaria que cada novo número não é um evento independente, porque se você gerar, por exemplo, um 10, significaria que a probabilidade de gerar qualquer 10s futuro seria 0% (isso não poderia acontecer), mais isso significaria que você aumentaria as chances de obter um número diferente de 10 (ou seja, quanto mais números você gerar, maior será a probabilidade de cada um dos números restantes).

Mais uma coisa a considerar: a chance de ganhar a Powerball de jogar um único jogo é, pelo que entendi, aproximadamente 1 em 175 milhões. No entanto, as chances de alguém ganhar são consideravelmente maiores que isso. Você está mais interessado nas chances de alguém "ganhar" (ou seja, ser uma duplicata) do que nas chances de qualquer número específico "vencer" / ser uma duplicata.

EJoshuaS - Restabelecer Monica
fonte
Se alguém estiver gerando identificadores de 4096 bits de maneira que seja provável que cada bit seja 0 ou 1 independente de qualquer outro bit que tenha sido gerado no mesmo ou em qualquer outro identificador, a probabilidade de que dois identificadores correspondam seria ser muito pequeno, mesmo que se gere aleatoriamente um identificador diferente para cada um dos átomos aproximadamente 4.0E81 no universo observável. O fato de que esses identificadores quase certamente seriam únicos não os tornaria "não aleatórios" #
3012
@ supercat Isso é verdade - dado um número suficientemente grande, é altamente improvável que haja duplicatas, mas não é impossível. Realmente depende de quão ruins são as consequências da não-exclusividade se o que o OP está descrevendo é uma boa idéia.
EJoshuaS - Reintegrar Monica
Se a probabilidade de uma colisão aleatória for menor do que a probabilidade de um ataque de meteoro que oblitera os dispositivos que dependem de identificações únicas, do ponto de vista da engenharia, não há necessidade de se preocupar com a primeira. Haveria uma grande necessidade de se preocupar com algo que pudesse fazer com que os números aleatórios não fossem independentes, mas colisões aleatórias não seriam um problema.
Supercat 31/12
@ supercat Eu acho que você está interpretando mal isso, veja a outra resposta sobre o paradoxo do aniversário, acho uma colisão muito mais provável do que você está calculando - o OP está usando um número de 32 bits, então não tenho certeza de onde você está. recebendo 4096 e, como o nomadictype, mostrou a probabilidade de uma eventual colisão com um número desse comprimento, é surpreendentemente alta.
EJoshuaS - Restabelece Monica
Você está certo de que um número de 32 bits é muito curto, mesmo para pequenas populações, se as colisões forem totalmente inaceitáveis. Se alguém usa um número suficientemente grande, pode reduzir a probabilidade de colisões aleatórias a ponto de assumir com segurança que elas simplesmente não vão acontecer e, em muitos casos, usar um número maior pode ser melhor do que tentar usar outros meios de garantindo exclusividade, uma vez que o último geralmente requer acesso a transições de estado que não podem ser desfeitas ou revertidas, mesmo se o relógio do sistema for redefinido ou o sistema for recarregado a partir de um backup.
Supercat 31/12
0

Não importa quantos bits você use - você NÃO PODE garantir que dois números "aleatórios" serão diferentes. Em vez disso, sugiro que você use algo como o endereço IP ou outro endereço de rede do computador e um número seqüencial, de preferência um número seqüencial HONKIN 'BIG - 128 bits (obviamente sem sinal) parece um bom começo, mas 256 seria melhor.

Bob Jarvis
fonte
-1

Não, claro que não. A menos que a rng esteja usando amostras sem substituição, há uma chance - embora pequena - de duplicação.

Dr. Drew
fonte