Geração de número de sequência distribuída?


103

Eu geralmente implementei a geração de números de sequência usando sequências de banco de dados no passado.

por exemplo, usando o tipo SERIAL Postgres http://www.neilconway.org/docs/sequences/

Estou curioso para saber como gerar números de sequência para grandes sistemas distribuídos onde não há banco de dados. Alguém tem alguma experiência ou sugestão de uma prática recomendada para obter a geração de números de sequência de maneira segura para threads para vários clientes?


Esta pergunta é antiga, mas por favor veja minha nova resposta stackoverflow.com/questions/2671858/…
Jesper M

Como você usa nextval.org? O site é um pouco estranho e não sei do que se trata. É algum comando Unix? Ou algum serviço em nuvem?
diegosasw

Respostas:


116

OK, esta é uma pergunta muito antiga, que estou vendo pela primeira vez agora.

Você precisará diferenciar entre números de sequência e IDs exclusivos que são (opcionalmente) classificáveis ​​livremente por um critério específico (normalmente tempo de geração). Os verdadeiros números de sequência implicam no conhecimento do que todos os outros trabalhadores fizeram e, como tal, requerem um estado compartilhado. Não há maneira fácil de fazer isso de maneira distribuída e em grande escala. Você pode examinar coisas como broadcasts de rede, intervalos de janela para cada trabalhador e tabelas de hash distribuídas para IDs de trabalhador exclusivos , mas é muito trabalhoso.

IDs exclusivos são outra questão, existem várias boas maneiras de gerar IDs exclusivos de maneira descentralizada:

a) Você pode usar o serviço de rede Snowflake ID do Twitter . Snowflake é um:

  • Serviço em rede, ou seja, você faz uma chamada de rede para obter uma ID exclusiva;
  • que produz IDs exclusivos de 64 bits que são ordenados por tempo de geração;
  • e o serviço é altamente escalável e (potencialmente) altamente disponível; cada instância pode gerar muitos milhares de IDs por segundo, e você pode executar várias instâncias em sua LAN / WAN;
  • escrito em Scala, é executado no JVM.

b) Você poderia gerar os IDs exclusivos nos próprios clientes, usando uma abordagem derivada de como os UUIDs e os IDs do Snowflake são feitos. Existem várias opções, mas algo como:

  • Os 40 bits mais significativos ou mais: um carimbo de data / hora; o tempo de geração do ID. (Estamos usando os bits mais significativos para o carimbo de data / hora para tornar os IDs classificáveis ​​por tempo de geração.)

  • Os próximos 14 ou mais bits: Um contador por gerador, em que cada gerador aumenta em um para cada novo ID gerado. Isso garante que os IDs gerados no mesmo momento (mesmos carimbos de data / hora) não se sobreponham.

  • Os últimos 10 bits ou mais: um valor exclusivo para cada gerador. Usando isso, não precisamos fazer nenhuma sincronização entre os geradores (o que é extremamente difícil), pois todos os geradores produzem IDs não sobrepostos por causa desse valor.

c) Você poderia gerar os IDs nos clientes, usando apenas um carimbo de data / hora e um valor aleatório. Isso evita a necessidade de conhecer todos os geradores e atribuir a cada gerador um valor único. Por outro lado, não é garantido que tais IDs sejam globalmente exclusivos, mas muito provavelmente serão exclusivos. (Para colidir, um ou mais geradores teriam que criar o mesmo valor aleatório ao mesmo tempo.) Algo na linha de:

  • Os 32 bits mais significativos: Timestamp, o tempo de geração do ID.
  • Os 32 bits menos significativos: 32 bits de aleatoriedade, gerados novamente para cada ID.

d) A maneira mais fácil é usar UUIDs / GUIDs .


O Cassandra suporta contadores ( cassandra.apache.org/doc/cql3/CQL.html#counters ), mas existem algumas limitações.
Piyush Kansal de

É fácil definir a posição dos números de sequência para o índice de bitmap, mas a id exclusiva às vezes é muito longa (64 bits ou 128 bits). Como pode o mapeamento de id exclusiva para uma posição de índice de bitmap? Obrigado.
brucenan

2
realmente gostei da opção #b ..... poderia permitir grande escala e não causar muitos problemas de simultaneidade
puneet

2
twitter/snowflakenão é mais mantido
Navin

Se você quiser uma implementação licenciada do Apache2 da opção B, verifique bitbucket.org/pythagorasio/common-libraries/src/master/… Você também pode obtê-la em maven io.pythagoras.common: Distribution-sequence-id-generator: 1.0 .0
Wpigott

16

Agora existem mais opções.

Embora essa pergunta seja "velha", eu cheguei aqui, então acho que pode ser útil deixar as opções que eu conheço (até agora):

  • Você pode tentar Hazelcast . Em sua versão 1.9, inclui uma implementação distribuída de java.util.concurrent.AtomicLong
  • Você também pode usar o Zookeeper . Ele fornece métodos para a criação de nós de sequência (anexados aos nomes de znode, embora eu prefira usar os números de versão dos nós). Porém, tenha cuidado com este: se você não quiser números perdidos em sua sequência, pode não ser o que você deseja.

Felicidades


3
Zookeeper foram as opções que escolhi
Jon

Jon, obrigado por apontar para esse tópico, esse é exatamente o tipo de solução que eu estava pensando. BTW, você fez o código para superar a limitação de MAX_INT?
Paolo,

15

Você poderia fazer com que cada nó tivesse um ID exclusivo (que você pode ter de qualquer maneira) e, em seguida, acrescentá-lo ao número de sequência.

Por exemplo, o nó 1 gera a sequência 001-00001 001-00002 001-00003 etc. e o nó 5 gera 005-00001 005-00002

Único :-)

Como alternativa, se você quiser algum tipo de sistema centralizado, pode considerar que seu servidor de seqüência seja distribuído em blocos. Isso reduz a sobrecarga significativamente. Por exemplo, em vez de solicitar um novo ID do servidor central para cada ID que deve ser atribuído, você solicita IDs em blocos de 10.000 do servidor central e só precisa fazer outra solicitação de rede quando acabar.


1
Eu gosto do seu ponto sobre a geração de id de lote, mas isso apenas limita qualquer possibilidade de cálculo em tempo real.
ishan

Eu implementei um mecanismo semelhante. Além de os clientes armazenarem em cache um bloco de sequências, adicionei vários servidores-hosts que armazenam em cache os blocos de sequências. Um (único) gerador mestre é mantido em algum armazenamento altamente disponível ou em um host mestre único, acessível apenas à frota de hosts servidores. O armazenamento em cache do servidor também nos ajudaria a ter mais tempo de atividade, apesar de o único mestre ficar inativo por um momento.
Janakiram

11

Isso pode ser feito com Redisson . Ele implementa uma versão distribuída e escalonável do AtomicLong. Aqui está um exemplo:

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

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

8

Se realmente tiver que ser sequencial globalmente, e não simplesmente exclusivo, eu consideraria a criação de um serviço único e simples para dispensar esses números.

Os sistemas distribuídos dependem da interação de muitos pequenos serviços e, para esse tipo de tarefa simples, você realmente precisa ou realmente se beneficiaria de alguma outra solução distribuída complexa?


3
... e o que acontece quando o servidor que está executando esse serviço cai?
Navin

Tem um alerta que diz a alguém para iniciar outro? Às vezes isso vai ficar bem. Acho que a resposta é tentar dizer "mantenha as coisas em perspectiva". A solução distribuída perfeita tem suas próprias desvantagens e, às vezes, mais simples é melhor.
nic ferrier

6

Existem algumas estratégias; mas nenhum que eu conheça pode ser realmente distribuído e dar uma sequência real.

  1. tem um gerador de número central. não precisa ser um grande banco de dados. memcachedtem um contador atômico rápido, na grande maioria dos casos é rápido o suficiente para todo o cluster.
  2. separe um intervalo inteiro para cada nó (como a resposta de Steven Schlanskter )
  3. use números aleatórios ou UUIDs
  4. usar alguns dados, junto com o ID do nó, e hash tudo (ou hmac it)

pessoalmente, gostaria de usar UUIDs ou memcached, se quiser ter um espaço quase contíguo.


5

Por que não usar um gerador de UUID (thread-safe)?

Eu provavelmente deveria expandir isso.

Os UUIDs têm garantia de serem globalmente exclusivos (se você evitar aqueles baseados em números aleatórios, onde a exclusividade é altamente provável).

Seu requisito "distribuído" é atendido, independentemente de quantos geradores UUID você usa, pela exclusividade global de cada UUID.

Seu requisito de "thread safe" pode ser atendido escolhendo geradores UUID "thread safe".

Presume-se que seu requisito de "número de sequência" seja atendido pela exclusividade global garantida de cada UUID.

Observe que muitas implementações de números de sequência de banco de dados (por exemplo, Oracle) não garantem o aumento monotônico ou (até mesmo) o aumento dos números de sequência (por "conexão"). Isso ocorre porque um lote consecutivo de números de sequência é alocado em blocos "armazenados em cache" por conexão. Isso garante exclusividade global e mantém a velocidade adequada. Mas os números de sequência realmente alocados (ao longo do tempo) podem ser confundidos quando estão sendo alocados por várias conexões!


1
Embora os UUIDs funcionem, o problema com eles é que você deve ter cuidado ao armazená-los se, em última instância, precisar indexar as chaves geradas. Normalmente, eles também ocupam muito mais espaço do que uma sequência monotonicamente aumentada. Consulte percona.com/blog/2014/12/19/store-uuid-optimized-way para uma discussão sobre como armazená-los com o MySQL.
Pavel de

2

A geração de ID distribuída pode ser arquivada com Redis e Lua. A implementação disponível no Github . Ele produz ids exclusivos distribuídos e classificáveis ​​por K.


2

Sei que essa é uma questão antiga, mas também estávamos diante da mesma necessidade e não foi possível encontrar a solução que atendesse a nossa necessidade. Nosso requisito era obter uma sequência única (0,1,2,3 ... n) de ids e, portanto, o floco de neve não ajudou. Criamos nosso próprio sistema para gerar os ids usando o Redis. O Redis é de thread único, portanto, seu mecanismo de lista / fila sempre forneceria 1 pop por vez.

O que fazemos é, Criamos um buffer de ids. Inicialmente, a fila terá de 0 a 20 ids que estão prontos para serem despachados quando solicitados. Vários clientes podem solicitar um id e o redis exibirá 1 id por vez. Após cada pop da esquerda, inserimos BUFFER + currentId à direita, o que mantém a lista de buffers funcionando. Implementação aqui


0

Eu escrevi um serviço simples que pode gerar números longos de 64 bits não sequenciais semi-únicos. Ele pode ser implantado em várias máquinas para redundância e escalabilidade. Ele usa ZeroMQ para mensagens. Para mais informações sobre como funciona, veja a página do github: zUID


0

Usando um banco de dados, você pode alcançar mais de 1.000 incrementos por segundo com um único núcleo. É muito fácil. Você pode usar seu próprio banco de dados como back-end para gerar esse número (como deveria ser seu próprio agregado, em termos de DDD).

Eu tive o que parece ser um problema semelhante. Eu tinha várias partições e queria obter um contador de deslocamento para cada uma. Eu implementei algo assim:

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

Em seguida, executei a seguinte instrução:

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

Se seu aplicativo permitir, você pode alocar um bloco de uma vez (foi o meu caso).

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

Se você precisar de mais rendimento e não puder alocar compensações com antecedência, poderá implementar seu próprio serviço usando o Flink para processamento em tempo real. Consegui obter cerca de 100 mil incrementos por partição.

Espero que ajude!


0

O problema é semelhante a: No mundo iscsi, onde cada luns / volumes devem ser identificados de forma única pelos iniciadores executados no lado do cliente. O padrão iscsi diz que os primeiros bits devem representar as informações do provedor / fabricante do armazenamento, e o restante deve aumentar monotonicamente.

Da mesma forma, pode-se usar os bits iniciais no sistema distribuído de nós para representar o nodeID e o resto pode ser monotonicamente crescente.


1
por favor, adicione mais alguns detalhes
Ved Prakash

0

Uma solução decente é usar uma geração baseada em muito tempo. Isso pode ser feito com o apoio de um banco de dados distribuído.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.