Gerando IDs legíveis / utilizáveis ​​por humanos, curtos, mas exclusivos

86
  • Precisa lidar com> 1000, mas <10000 novos registros por dia

  • Não é possível usar GUID / UUIDs, números de incremento automático etc.

  • Idealmente, deve ter 5 ou 6 caracteres, pode ser alfa, é claro

  • Gostaria de reutilizar algos bem conhecidos existentes, se disponíveis

Tem alguma coisa aí?

Kumar
fonte
Por que não usar um INT ou BIGINT que é autoincrementado? É provavelmente o mais legível e pode controlar facilmente o volume.
Malk,
de acordo com o Q acima, tentando mantê-lo em 5/6 caracteres no máximo e suportar até 9.999 novos registros por dia
Kumar
@Kumar - E se você precisar de mais de 9.999 registros em um dia? Sua solução proposta não parece sustentável.
ChaosPandion
@ChaosPandion: Acho que essas são provavelmente estimativas aproximadas de carga / tráfego, em vez de limites rígidos. Não sei por que você deseja definir um limite arbitrário para o número de transações diárias.
Paul Sasik
Você poderia codificá-lo na base 64 e usá-lo. Não tenho certeza se você poderia reduzi-lo para um tamanho menor e ainda usar caracteres legíveis. Mas eu argumentaria que a base 64 é muito menos legível do que a base 32 porque requer a adição de um qualificador extra à maioria dos caracteres (f maiúsculo, o inferior, o inferior contra apenas f, oo).
Malk,

Respostas:

118

A base 62 é usada por tinyurl e bit.ly para os URLs abreviados. É um método bem conhecido para a criação de IDs "exclusivos" e legíveis por humanos. Claro, você terá que armazenar os IDs criados e verificar se há duplicatas na criação para garantir a exclusividade. (Veja o código no final da resposta)

Métricas de exclusividade de base 62

5 caracteres na base 62 darão a você 62 ^ 5 IDs únicos = 916.132.832 (~ 1 bilhão) Com 10k IDs por dia, você ficará bem por 91k + dias

6 caracteres na base 62 darão a você 62 ^ 6 IDs únicos = 56.800.235.584 (56+ bilhões) Com 10.000 IDs por dia, você estará ok por mais de 5 milhões de dias

Métricas de exclusividade de base 36

6 caracteres fornecerão 36 ^ 6 IDs únicos = 2.176.782.336 (2+ bilhões)

7 caracteres fornecerão 36 ^ 7 IDs exclusivos = 78.364.164.096 (mais de 78 bilhões)

Código:

public void TestRandomIdGenerator()
{
    // create five IDs of six, base 62 characters
    for (int i=0; i<5; i++) Console.WriteLine(RandomIdGenerator.GetBase62(6));

    // create five IDs of eight base 36 characters
    for (int i=0; i<5; i++) Console.WriteLine(RandomIdGenerator.GetBase36(8));
}

public static class RandomIdGenerator 
{
    private static char[] _base62chars = 
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
        .ToCharArray();

    private static Random _random = new Random();

    public static string GetBase62(int length) 
    {
        var sb = new StringBuilder(length);

        for (int i=0; i<length; i++) 
            sb.Append(_base62chars[_random.Next(62)]);

        return sb.ToString();
    }       

    public static string GetBase36(int length) 
    {
        var sb = new StringBuilder(length);

        for (int i=0; i<length; i++) 
            sb.Append(_base62chars[_random.Next(36)]);

        return sb.ToString();
    }
}

Resultado:

z5KyMg
wd4SUp
uSzQtH
UPrGAT
UIf2IS

QCF9GNM5
0UV3TFSS
3MG91VKP
7NTRF10T
AJK3AJU7
Paul Sasik
fonte
3
parece fantástico, algo que não diferencia maiúsculas de minúsculas?
Kumar
2
Se você quiser evitar a distinção entre maiúsculas e minúsculas, pode usar a base 36: codeproject.com/Articles/10619/Base-36-type-for-NET-C, mas para obter tantas permutações como base 62, você precisará usar mais caracteres em seu EU IRIA. É uma troca. Ou você pode tentar usar outros caracteres além do alfa, mas isso fica feio para os usuários.
Paul Sasik
2
aqui stackoverflow.com/questions/9543892/… e muito obrigado
Kumar
11
Um pensamento. Talvez tire as vogais para evitar a geração acidental de palavrões. Especialmente se for voltado para o público.
Damien Sawyer
4
Dependendo de onde você estiver usando isso (especialmente se for esperado que humanos leiam e reinsira os códigos), você pode querer considerar a remoção de caracteres frequentemente confusos: 0 / O e I / l / 1. Em alguns casos, isso pode ser atenuado por uma boa escolha de fonte, mas não posso dizer a partir da pergunta se o OP terá controle sobre isso.
GrandOpener de
17

Eu recomendo http://hashids.org/ que converte qualquer número (por exemplo, DB ID) em uma string (usando sal).

Ele permite decodificar essa string de volta para o número. Portanto, você não precisa armazená-lo no banco de dados.

Tem bibliotecas para JavaScript, Ruby, Python, Java, Scala, PHP, Perl, Swift, Clojure, Objective-C, C, C ++ 11, Go, Erlang, Lua, Elixir, ColdFusion, Groovy, Kotlin, Nim, VBA, CoffeeScript e para Node.js e .NET.

Slawa
fonte
1
Você pode fornecer outras opções semelhantes à sua proposta? - - É muito interessante. Gostaria de saber se existe alguma opção padrão como essa no PostgreSQL.
Léo Léopold Hertz 준영
1
Aqui está a versão .NET dele, mas você pode explicar como funciona sem a necessidade de armazená-lo no banco de dados? Posso gerar apenas randoms únicos sem fornecer números como entrada e sem sal?
shaijut
@Slawa Eu preciso de algo como hashids para .NET mas o hash final será armazenado no banco de dados em uma coluna com comprimento fixo, isso é possível dizer sempre gerar hash com comprimento máximo de N?
Anon Dev
6

Eu tinha requisitos semelhantes aos do OP. Pesquisei as bibliotecas disponíveis, mas a maioria delas é baseada na aleatoriedade e eu não queria isso. Não consegui realmente encontrar nada que não fosse baseado em aleatório e ainda muito curto ... Então acabei rolando meu próprio baseado na técnica que o Flickr usa , mas modifiquei para exigir menos coordenação e permitir períodos mais longos offline.

Em resumo:

  • Um servidor central emite blocos de IDs com 32 IDs cada
  • O gerador de ID local mantém um conjunto de blocos de ID para gerar um ID toda vez que um for solicitado. Quando o pool fica baixo, ele busca mais blocos de ID no servidor para preenchê-lo novamente.

Desvantagens:

  • Requer coordenação central
  • Os IDs são mais ou menos previsíveis (menos do que os IDs DB normais, mas não são aleatórios)

Vantagens

  • Permanece dentro de 53 bits (tamanho máximo Javascript / PHP para números inteiros)
  • IDs muito curtos
  • Base 36 codificada tão fácil para humanos ler, escrever e pronunciar
  • Os IDs podem ser gerados localmente por um longo tempo antes de precisar entrar em contato com o servidor novamente (dependendo das configurações do pool)
  • Teoricamente sem chance de colisões

Publiquei uma biblioteca Javascript para o lado do cliente, bem como uma implementação de servidor Java EE. Implementar servidores em outros idiomas também deve ser fácil.

Aqui estão os projetos:

suid - IDs exclusivos de serviço distribuídos que são curtos e agradáveis

suid-server-java - Implementação de servidor Suid para a pilha de tecnologia Java EE.

Ambas as bibliotecas estão disponíveis sob uma licença de código aberto Creative Commons. Esperando que isso possa ajudar alguém que esteja procurando por IDs curtos e exclusivos.

Stijn de Witt
fonte
Você pode comparar stackoverflow.com/a/29372036/54964 com sua proposta suid?
Léo Léopold Hertz 준영
1
É baseado em números aleatórios. É muito bom, na verdade. Mas seus IDs não serão tão curtos quanto podem ser. Escrevi SUID para começar a numerar em 1, então você começará com IDs extremamente curtos . Pense em 3 ou 4 personagens. Além disso, tem algumas outras vantagens interessantes ter IDs (aproximadamente) ordenados de forma incremental, além de começar com os realmente curtos.
Stijn de Witt
3

Usei a base 36 quando resolvi esse problema para um aplicativo que estava desenvolvendo há alguns anos. Eu precisava gerar um número razoavelmente único legível por humanos (pelo menos dentro do ano civil atual). Optei por usar o tempo em milissegundos a partir da meia-noite de 1º de janeiro do ano atual (portanto, a cada ano, os carimbos de data / hora podem ser duplicados) e convertê-lo em um número base 36. Se o sistema em desenvolvimento tivesse um problema fatal, ele gerava o número de base 36 (7 caracteres) que era exibido para um usuário final através da interface da web, que poderia então retransmitir o problema encontrado (e o número) para uma pessoa de suporte técnico poderia então usá-lo para encontrar o ponto nos logs onde o stacktrace começou). Um número como 56af42g7é infinitamente mais fácil para um usuário ler e retransmitir do que um carimbo de data / hora como 2016-01-21T15: 34: 29.933-08: 00 ou um UUID aleatório como 5f0d3e0c-da96-11e5-b5d2-0a1d41d68578 .

Warren Smith
fonte
4
Você pode fornecer um pseudocódigo de forma estruturada sobre sua proposta? Parece interessante.
Léo Léopold Hertz 준영
0

Eu realmente gosto da simplicidade de apenas codificar um GUID usando o formato Base64 e truncar o == final para obter uma string de 22 caracteres (é necessária uma linha de código e você sempre pode convertê-la de volta para GUID). Infelizmente, às vezes inclui os caracteres + e /. OK para banco de dados, não é ótimo para URLs, mas me ajudou a apreciar as outras respostas :-)

De https://www.codeproject.com/Tips/1236704/Reducing-the-string-Length-of-a-Guid por Christiaan van Bergen

Descobrimos que converter o Guid (16 bytes) em uma representação ASCII usando Base64 resultou em um messageID utilizável e ainda único de apenas 22 caracteres.

var newGuid = Guid.NewGuid();
var messageID = Convert.ToBase64String(newGuid.ToByteArray());

var message22chars = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0,22);

Por exemplo: o Guid 'e6248889-2a12-405a-b06d-9695b82c0a9c' (comprimento da string: 36) receberá uma representação Base64: 'iYgk5hIqWkCwbZaVuCwKnA ==' (comprimento da string: 24)

A representação Base64 termina com os caracteres '=='. Você pode apenas truncá-los, sem qualquer impacto na exclusividade. Deixando você com um identificador de apenas 22 caracteres de comprimento.

Ekus
fonte