
Você provavelmente já usou UUIDs em seus projetos e os considerou únicos. Vamos dar uma olhada nos principais aspectos da implementação e ver por que os UUIDs são praticamente únicos, já que há uma pequena possibilidade de ocorrência dos mesmos valores.
A implementação moderna de UUIDs pode ser rastreada até a RFC 4122, que descreve cinco abordagens diferentes para gerar esses identificadores. Examinaremos cada um deles e acompanharemos a implementação da versão 1 e da versão 4.
Teoria
UUID (identificador único universal) é um número de 128 bits usado no desenvolvimento de software como um identificador único para elementos. Sua representação textual clássica é uma série de 32 caracteres hexadecimais, separados por hífens em cinco grupos no padrão 8-4-4-4-12.
Por exemplo:
3422b448-2460-4fd2-9183-8000de6f8343
As informações de implementação do UUID estão incorporadas nesta sequência aparentemente aleatória de caracteres:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
Os valores nas posições M e N definem a versão e variante do UUID, respectivamente.
Versão
O número da versão é identificado pelos quatro bits mais significativos na posição M. Hoje, existem as seguintes versões:

Opção
Este campo define o modelo das informações embutidas no UUID. A interpretação de todos os outros bits no UUID depende do valor da variante.
Nós o determinamos pelos primeiros 1-3 bits mais significativos na posição N.

Hoje, a opção 1 é usada com mais frequência, em que
MSB0
é igual a 1
e MSB1
igual 0
. Isto significa que, dadas caracteres universais - bits seleccionados
- apenas os valores possíveis são 8
, 9
, A
ou B
.
Memo:
1 0 0 0 = 8
1 0 0 1 = 9
1 0 1 0 = A
1 0 1 1 = B
Portanto, se você vir um UUID com esses valores na posição N, este é o identificador na opção 1.
Versão 1 (tempo + ID de host único ou aleatório)
Nesse caso, o UUID é gerado assim: alguma propriedade identificadora do dispositivo que gera o UUID é adicionada à hora atual, na maioria das vezes é o endereço MAC (também conhecido como ID do nó).
O identificador é obtido concatenando um endereço MAC de 48 bits, um carimbo de data / hora de 60 bits, uma sequência de relógio "exclusiva" de 14 bits e 6 bits reservados para UUIDs de versão e variantes.
A sequência do relógio é simplesmente um valor que é incrementado cada vez que o relógio é alterado.
O carimbo de data / hora usado nesta versão é o número de intervalos de 100 nanossegundos desde 15 de outubro de 1582, a data de origem do calendário gregoriano.
Você pode estar familiarizado com o sistema Unix de tempo desde o início de uma época. É apenas um tipo diferente de Dia Zero. Existem serviços na web que podem ajudá-lo a transformar uma representação temporal em outra, então não vamos insistir nisso.
Embora essa implementação pareça bastante simples e confiável, o uso do endereço MAC da máquina na qual o identificador é gerado não permite que esse método seja considerado universal. Especialmente quando a segurança é o principal critério. Portanto, em algumas implementações, em vez do identificador de nó, são usados 6 bytes aleatórios retirados de um gerador de número aleatório protegido criptograficamente.
Construir UUID versão 1 é assim:
- Os 32 bits menos significativos do carimbo de data / hora UTC atual são usados. Estes serão os primeiros 4 bytes (8 caracteres hexadecimais) do UUID [
TimeLow
]. - Os 16 bits intermediários do carimbo de data / hora UTC atual são usados. Estes serão os próximos 2 bytes (4 caracteres hexadecimais) [
TimeMid
]. - Os próximos 2 bytes (4 caracteres hexadecimais) concatenam os 4 bits da versão UUID com os 12 MSBs restantes do carimbo de data / hora UTC atual (que tem um total de 60 bits) [
TimeHighAndVersion
]. - Os próximos 1-3 bits definem a variante da versão UUID. Os bits restantes contêm uma sequência de relógio que adiciona um pouco de aleatoriedade a essa implementação. Isso evita colisões quando vários geradores UUID estão rodando no mesmo sistema: ou o relógio do sistema está atrasado para o gerador ou a mudança de horário é retardada [
ClockSequenceHiAndRes && ClockSequenceLow
]. - Os últimos 6 bytes (12 caracteres hexadecimais, 48 bits) são o "ID do nó", que geralmente é o endereço MAC do gerador [
NodeID
].
O UUID da versão 1 é gerado usando concatenação:
TimeLow + TimeMid + TimeHighAndVersion + (ClockSequenceHiAndRes && ClockSequenceLow) + NodeID
Como essa implementação depende do relógio, precisamos lidar com situações de borda. Primeiro, para minimizar a correlação entre os sistemas, por padrão, a sequência do relógio é considerada um número aleatório - isso é feito apenas uma vez em todo o ciclo de vida do sistema. Isso nos dá o benefício adicional de oferecer suporte a IDs de nó que podem ser transportados pelos sistemas, uma vez que a sequência de relógio inicial é completamente independente do ID de nó.
Lembre-se de que o objetivo principal de usar uma sequência de relógio é adicionar alguma aleatoriedade à nossa equação. Os bits de sequência de relógio ajudam a estender o carimbo de data / hora e acomodar situações em que vários UUIDs são gerados antes mesmo de o relógio do processador mudar. Desta forma, evitamos criar os mesmos identificadores quando o relógio é atrasado (dispositivo está desligado) ou o identificador de nó muda. Se o relógio for retrocedido ou puder ter sido retrocedido (por exemplo, enquanto o sistema estava desligado) e o gerador de UUID não puder verificar se os identificadores foram gerados com carimbos de data / hora posteriores ao valor do relógio especificado, a sequência do relógio deve ser alterada. Se conhecermos seu valor anterior, podemos simplesmente aumentá-lo;caso contrário, deve ser definido aleatoriamente ou com um PRNG de alta qualidade.
Versão 2 (Segurança de um Ambiente de Computação Distribuída)
A principal diferença desta versão em relação à anterior é que em vez de "aleatoriedade" na forma dos bits menos significativos da seqüência do relógio, um identificador característico do sistema é usado aqui. Freqüentemente, é apenas o ID do usuário atual. A versão 2 é usada com menos frequência, ela difere muito pouco da versão 1, então vamos em frente.
Versão 3 (nome + hash MD5)
Se forem necessários identificadores exclusivos para nomear ou informações de nomenclatura, eles geralmente usam UUIDs de versão 3 ou 5.
Eles codificam quaisquer entidades "nomeadas" (sites, DNS, texto simples etc.) em um valor UUID. Mais importante ainda, o mesmo UUID será gerado para o mesmo namespace ou texto.
Observe que o próprio namespace é um UUID.
let namespace = “digitalbunker.dev”
let namespaceUUID = UUID3(.DNS, namespace)
// Ex:
UUID3(namespaceUUID, “/category/things-you-should-know-1/”)
4896c91b-9e61-3129-87b6-8aa299028058
UUID3(namespaceUUID, “/category/things-you-should-know-2/”)
29be0ee3-fe77-331e-a1bf-9494ec18c0ba
UUID3(namespaceUUID, “/category/things-you-should-know-3/”)
33b06619-1ee7-3db5-827d-0dc85df1f759
Nessa implementação, o namespace UUID é convertido em uma string de bytes concatenada com o nome de entrada e, em seguida, hash com MD5, resultando em 128 bits para o UUID. Em seguida, reescrevemos alguns dos bits para reproduzir com precisão a versão e as informações da versão e deixamos o resto intacto.
É importante entender que nem o namespace nem o nome de entrada podem ser calculados com base no UUID. Esta é uma operação irreversível. A única exceção é a força bruta quando um dos valores (namespace ou texto) já é conhecido pelo invasor.
Com a mesma entrada, os UUIDs gerados das versões 3 e 5 serão determinísticos.
Versão 4 (PRNG)
Implementação mais simples.
6 bits estão reservados para versão e variante, ainda faltam 122 bits. Essa versão simplesmente gera 128 bits aleatórios e, em seguida, substitui 6 deles por dados de versão e versão.
Esses UUIDs são completamente dependentes da qualidade do PRNG (gerador de números pseudo-aleatórios). Se seu algoritmo for muito simples ou não tiver valores iniciais, a probabilidade de repetição dos identificadores aumenta.
Em linguagens modernas, o UUID versão 4 é o mais usado.
Sua implementação é bastante simples:
- Geramos 128 bits aleatórios.
- Reescreva alguns bits com a versão correta e as informações da versão:
- Pegue o sétimo bit e
0x0F
AND para limpar o nibble alto. E então0x40
OR é usado para atribuir a versão 4. - Então pegamos o nono byte, realizamos a
0x3F
operação AND em ce a0x80
operação OR nele.
- Pegue o sétimo bit e
- Converta 128 bits em hexadecimal e insira hifens.
Versão 5 (nome + SHA-1-hash)
A única diferença da versão 3 é que estamos usando o algoritmo de hash SHA-1 em vez de MD5. Esta versão é preferível à terceira (SHA-1> MD5).
Prática
Uma das vantagens importantes dos UUIDs é que sua exclusividade não depende de uma autoridade de autorização central ou da coordenação entre sistemas diferentes. Qualquer um pode criar um UUID com alguma certeza de que ninguém mais irá gerar esse valor no futuro próximo.
Isso torna possível combinar identificadores criados por diferentes participantes em um banco de dados ou mover identificadores entre bancos de dados com uma probabilidade insignificante de colisão.
UUIDs podem ser usados como chaves primárias em bancos de dados, como nomes exclusivos para arquivos carregados, como nomes exclusivos para quaisquer fontes da web. Você não precisa de uma autoridade de autorização central para gerá-los. Mas esta é uma solução de dois gumes. Devido à falta de um controlador, é impossível rastrear os UUIDs gerados.
Existem mais algumas desvantagens que precisam ser resolvidas. A aleatoriedade inerente aumenta a segurança, mas torna a depuração mais difícil. Além disso, o UUID pode ser redundante em algumas situações. Digamos que não faça sentido usar 128 bits para identificar exclusivamente dados cujo tamanho total é inferior a 128 bits.
Singularidade
Pode parecer que, se você tiver tempo suficiente, poderá repetir algum valor. Especialmente no caso da versão 4. Mas na realidade não é o caso. Se você gerasse um bilhão de UUIDs por segundo em 100 anos, a chance de um dos valores se repetir seria de cerca de 50%. Isso em vista do fato de que o PRNG fornece uma quantidade suficiente de entropia (verdadeira aleatoriedade), caso contrário, a probabilidade de um duplo será maior. Um exemplo mais ilustrativo: se você gerou 10 trilhões de UUIDs, a probabilidade de dois valores idênticos aparecerem é 0,00000006%.
E no caso da versão 1, o relógio será zerado apenas no 3603. Portanto, se você não planeja manter seu serviço em execução até 1583, está seguro.
No entanto, a probabilidade de aparecimento de um duplo permanece e, em alguns sistemas, eles tentam levar isso em consideração. Mas na grande maioria dos casos, os UUIDs podem ser considerados completamente únicos. Se você precisar de mais provas, aqui está uma visualização simples da probabilidade de colisão na prática.