Como construir um sistema de armazenamento de valor-chave escalonável e altamente disponível

Este artigo foi publicado pela primeira vez na conta oficial: Mais AI (power_ai), bem-vindo para prestar atenção, programação e produtos secos AI serão entregues a tempo!

Um armazenamento de valor-chave, também conhecido como banco de dados de valor-chave, é um tipo de banco de dados não relacional. Cada identificador exclusivo é armazenado como uma chave, junto com seu valor associado. Esse emparelhamento de dados é chamado de par "chave-valor".

Em um par chave-valor, a chave deve ser exclusiva e o valor associado à chave pode ser acessado por meio da chave. As chaves podem ser texto simples ou hashes. As teclas curtas são melhores por motivos de desempenho. Como é a chave? aqui estão alguns exemplos:

  • Chave de texto simples: "last_logged_in_at"
  • Chave de hash: 253DDEC4

Os valores em um par chave-valor podem ser strings, listas, objetos, etc. Em armazenamentos de valor-chave, os valores geralmente são tratados como um objeto opaco, como Amazon dynamo [1], Memcached [2], Redis [3], etc.

O seguinte é um pedaço de dados em um armazenamento de chave-valor:

imagem-20230525200227341

Neste capítulo, você será solicitado a projetar um armazenamento de valor-chave que suporte as seguintes operações:

  • put(chave, valor) // Insere o "valor" associado à "chave"
  • get(key) // obtém o "valor" associado à "chave"

Entenda o problema e estabeleça o escopo do projeto

Não existe projeto perfeito. Cada design atinge um equilíbrio específico, com relação a leituras, gravações e compensações de uso de memória. A compensação que ainda precisa ser feita é entre consistência e disponibilidade. Neste capítulo, projetamos um armazenamento de chave-valor com os seguintes recursos:

  • O tamanho dos pares chave-valor é pequeno: menos de 10 KB.
  • Capaz de armazenar big data.
  • Alta disponibilidade: O sistema responde rapidamente também durante falhas.
  • Alta escalabilidade: o sistema pode ser dimensionado para suportar grandes conjuntos de dados.
  • Auto Scaling: adicione/remova servidores automaticamente com base no tráfego.
  • Consistência ajustável.
  • baixa latência.

Armazenamento de valor-chave de servidor único

É fácil desenvolver um armazenamento de chave-valor que resida em um único servidor. A abordagem intuitiva é armazenar pares chave-valor em uma tabela hash, mantendo tudo na memória. Embora o acesso à memória seja rápido, pode não ser possível colocar tudo na memória devido a restrições de espaço. Duas otimizações podem ser feitas para armazenar mais dados em um único servidor:

  • compressão de dados
  • Armazene apenas os dados usados ​​com frequência na memória, o restante no disco

Mesmo com essas otimizações, um único servidor pode atingir rapidamente sua capacidade. Um armazenamento de chave-valor distribuído é necessário para dar suporte a big data.

Armazenamento de chave-valor distribuído

Um armazenamento de chave-valor distribuído também é conhecido como tabela de hash distribuída, que distribui pares de chave-valor em vários servidores. Ao projetar sistemas distribuídos, é importante entender a teoria CAP ( Consistência C , Disponibilidade A , Tolerância de Partição P ).

teorema CAP

O teorema CAP afirma que é impossível para um sistema distribuído fornecer simultaneamente mais de duas das três garantias a seguir: consistência, disponibilidade e tolerância à partição. Vamos esclarecer algumas definições.

Consistência : Consistência significa que todos os clientes veem os mesmos dados ao mesmo tempo, independentemente do nó ao qual estão conectados.

Disponibilidade : Disponibilidade significa que qualquer cliente que solicitar dados obterá uma resposta, mesmo que alguns nós estejam inativos.

Tolerância de Partição : Uma partição indica uma perda de comunicação entre dois nós. Tolerância de partição significa que o sistema continua a funcionar no caso de uma partição de rede.

O teorema CAP afirma que uma das três propriedades deve ser sacrificada em favor das outras duas, conforme mostrado na Figura 6-1.

imagem-20230525200252771

Hoje, os armazenamentos de valor-chave são classificados de acordo com as duas características do CAP que suportam:

Sistemas CP (consistência e tolerância de partição) : os armazenamentos de valor-chave CP suportam consistência e tolerância de partição em detrimento da disponibilidade.

Sistemas AP (disponibilidade e tolerância de partição) : os armazenamentos de valor-chave AP suportam disponibilidade e tolerância de partição em detrimento da consistência.

Sistemas CA (Consistência e Disponibilidade) : Os armazenamentos de valor-chave da CA suportam consistência e disponibilidade em detrimento da tolerância de partição. Como as falhas de rede são inevitáveis, os sistemas distribuídos devem tolerar partições de rede. Portanto, não há sistema CA em aplicações práticas.

O que você leu acima é principalmente a parte de definição. Para facilitar o entendimento, vejamos alguns exemplos concretos. Em um sistema distribuído, os dados geralmente são replicados várias vezes. Suponha que os dados sejam replicados em três nós de réplica n1 , n2 e n3 , conforme mostrado na Figura 6-1.

situação ideal

Em um mundo ideal, as partições de rede nunca ocorreriam. Os dados gravados em n1 são copiados automaticamente para n2 e n3 . Tanto a consistência quanto a disponibilidade são alcançadas.

imagem-20230525200306382

Sistemas Distribuídos do Mundo Real

Em um sistema distribuído, as partições são inevitáveis ​​e, quando ocorrem, devemos escolher entre consistência e disponibilidade. Na Figura 6-3, n3 está desativado e incapaz de se comunicar com n1 e n2 . Se um cliente gravar dados em n1 ou n2 , os dados não serão propagados para n3. Se os dados forem gravados em n3 , mas não tiverem sido propagados para n1 e n2 , então n1 e n2 terão dados obsoletos.

imagem-20230525200323569

Se escolhermos consistência em vez de disponibilidade (sistema CP), teremos que bloquear todas as operações de gravação em n1 e n2 para evitar inconsistência de dados entre esses três servidores, o que tornaria o sistema indisponível. Os sistemas bancários normalmente têm requisitos de consistência extremamente altos. Por exemplo, é crítico para um sistema bancário exibir informações de saldo atualizadas. Se ocorrer uma inconsistência devido a uma partição de rede, o sistema bancário retorna um erro até que a inconsistência seja resolvida.

No entanto, se escolhermos a disponibilidade em detrimento da consistência (sistema AP), mesmo que o sistema retorne dados obsoletos, ele continuará aceitando leituras. Para operações de gravação, n1 e n2 continuarão aceitando gravações e os dados serão sincronizados com n3 quando a partição de rede for resolvida .

Escolher a garantia CAP correta para seu cenário de uso é uma etapa importante na construção de um armazenamento de chave-valor distribuído. Você pode discutir isso com o entrevistador e, em seguida, projetar o sistema com base na discussão.

Componentes do sistema

Nesta seção, discutimos os seguintes componentes principais e tecnologias usadas na construção de um armazenamento de chave-valor:

  • partição de dados
  • replicação de dados
  • consistência
  • resolução de inconsistência
  • Solução de problemas
  • Diagrama de arquitetura do sistema
  • escrever caminho
  • caminho de leitura

O seguinte é amplamente baseado em três sistemas populares de armazenamento de valor-chave: Dynamo [4], Cassandra [5] e BigTable [6].

partição de dados

Para aplicativos grandes, não é possível ajustar o conjunto de dados completo em um único servidor. A maneira mais fácil de realizar essa tarefa é dividir os dados em partições menores e armazená-los em vários servidores. Ao particionar dados, existem dois desafios:

  • Distribua os dados uniformemente entre vários servidores.
  • Minimize a movimentação de dados ao adicionar ou remover nós.

O hashing consistente, discutido no Capítulo 5, é uma ótima solução para esses problemas. Vamos revisar novamente como o hashing consistente funciona em alto nível.

  • Primeiro, coloque o servidor em um hash ring. Na Figura 6-4, oito servidores, denominados s0, s1, ..., s7 , são colocados no anel hash.
  • Em seguida, uma chave é hash no mesmo anel e é armazenada no primeiro servidor encontrado à medida que você se move no sentido horário. Por exemplo, key0 é armazenado em s1 de acordo com esta lógica .

imagem-20230525200347075

O uso de hash consistente para particionar dados tem as seguintes vantagens:

Dimensionamento automático: os servidores podem ser adicionados e removidos automaticamente com base na carga.

Heterogeneidade: O número de nós virtuais de um servidor é proporcional à capacidade do servidor. Por exemplo, servidores com maior capacidade recebem mais nós virtuais.

replicação de dados

Para alta disponibilidade e confiabilidade, os dados devem ser replicados de forma assíncrona em N servidores, onde N é um parâmetro configurável. Esses N servidores são selecionados de acordo com a seguinte lógica: depois que uma chave é mapeada para uma determinada posição no anel de hash, caminhe no sentido horário a partir dessa posição e selecione os primeiros N servidores no anel para armazenar cópias de dados . Na Figura 6-5 ( N = 3 ), key0 é replicado em s1, s2 e s3 .

imagem-20230525200401723

No caso de nós virtuais, os primeiros N nós no anel podem pertencer a menos de N servidores físicos. Para evitar esse problema, selecionamos apenas servidores exclusivos ao executar a lógica de caminhada no sentido horário.

Devido a falhas de energia, problemas de rede, desastres naturais, etc., os nós no mesmo centro de dados geralmente falham ao mesmo tempo. Para maior confiabilidade, as réplicas são colocadas em diferentes datacenters, que são conectados por rede de alta velocidade.

consistência

Como os dados são replicados em vários nós, eles devem ser sincronizados nas réplicas. O consenso do quorum (quórum) pode garantir a consistência das operações de leitura e gravação. Primeiro vamos definir alguns conceitos.

N = número de réplicas

W =quorum de gravação de tamanho W. Para que uma operação de gravação seja considerada bem-sucedida,as confirmações da operação de gravação devem ser obtidas das réplicas do W.

R =quorum de leitura de tamanho R. Para considerar uma operação de leitura bem-sucedida, a operação de leitura deve aguardar respostas de pelo menos R réplicas.

Considere o seguinte exemplo para N = 3 na Figura 6-6 .

imagem-20230525200418446

W = 1 não significa que os dados são gravados em um servidor. Por exemplo, para a configuração na Figura 6-6, os dados são replicados em s0 , s1 e s2 . W = 1 significa que o coordenador deve receber pelo menos uma confirmação antes de considerar a operação de gravação bem-sucedida. Por exemplo, se obtivermos uma confirmação de s1 , não precisamos esperar pelas confirmações de s0 e s2 . O coordenador atua como um proxy entre clientes e nós.

A configuração de W, R e N é uma troca típica entre latência e consistência. Se W = 1 ou R = 1 , a operação retorna rapidamente porque o coordenador precisa apenas aguardar uma resposta de qualquer uma das réplicas. Se W ou R > 1 , o sistema oferece melhor consistência, porém as consultas serão mais lentas porque o coordenador tem que esperar uma resposta da réplica mais lenta.

Se W + R > N , a consistência forte é garantida porque deve haver pelo menos um nó sobreposto com dados atualizados para garantir a consistência.

Como configurar N, W e R para se adequar ao nosso caso de uso? Aqui estão algumas configurações possíveis:

Se R = 1 e W = N , o sistema é otimizado para leituras rápidas.

Se W = 1 e R = N , o sistema é otimizado para gravações rápidas.

Se W + R > N , a consistência forte é garantida (geralmente N = 3, W = R = 2 ).

Se W + R <= N , consistência forte não pode ser garantida.

Dependendo dos requisitos, podemos ajustar os valores de W, R, N para atingir o nível de consistência desejado.

modelo de consistência

O modelo de consistência é outro fator importante a ser considerado ao projetar um armazenamento de chave-valor. Um modelo de consistência define o grau de consistência dos dados e existem muitos modelos de consistência possíveis:

  • Consistência forte: o valor retornado por qualquer operação de leitura corresponde ao resultado do item de dados gravados atualizado mais recentemente. Os clientes nunca veem dados obsoletos.
  • Consistência fraca: as operações de leitura subsequentes podem não ver o valor atualizado mais recente.
  • Consistência eventual: Esta é uma forma específica de consistência fraca. Com tempo suficiente, todas as atualizações são propagadas e todas as réplicas são consistentes.

A consistência forte geralmente é alcançada pela imposição de réplicas para não aceitar novas operações de leitura/gravação até que cada réplica esteja de acordo com a gravação atual. Essa abordagem não é ideal para sistemas altamente disponíveis, pois pode bloquear novas operações. Dynamo e Cassandra empregam consistência eventual, que é nosso modelo de consistência recomendado para armazenamentos de chave-valor. A partir de gravações simultâneas, a consistência eventual permite que valores inconsistentes entrem no sistema e força os clientes a ler valores para reconciliar. A próxima seção explica como o controle de versão funciona para obter a coordenação.

Resolução de inconsistência: controle de versão

A replicação fornece alta disponibilidade, mas leva a inconsistências entre as réplicas. Controle de versão e bloqueios de vetor são usados ​​para resolver inconsistências. O controle de versão refere-se ao tratamento de cada modificação de dados como uma nova versão imutável dos dados. Antes de falarmos sobre controle de versão, vamos explicar como acontecem as inconsistências com um exemplo:

Conforme mostrado na Figura 6-7, os nós de réplica n1 e n2 têm o mesmo valor. Vamos chamar esse valor de valor original. O servidor 1 e o servidor 2 buscam o mesmo valor para operações *get("name")*.

imagem-20230525200443156

Em seguida, o Servidor 1 altera o nome para "johnSanFrancisco" e o Servidor 2 altera o nome para "johnNewYork", conforme mostrado na Figura 6-8. Essas duas alterações são feitas simultaneamente. Agora temos valores conflitantes chamados versões v1 e v2 .

imagem-20230525200456151

Neste exemplo, o valor original pode ser ignorado, pois a modificação é baseada nele. No entanto, não há uma maneira clara de resolver conflitos entre as duas últimas versões. Para resolver este problema, precisamos de um sistema de versionamento que possa detectar conflitos e reconciliá-los. Os relógios vetoriais são uma técnica comum para resolver esse problema. Vamos ver como funciona o relógio vetorial.

Um relógio vetorial é um par *[servidor, versão]* associado a um item de dados. Ele pode ser usado para verificar se uma versão é anterior, posterior ou está em conflito com outras versões.

Suponha que um relógio vetorial seja representado por D([S1, v1], [S2, v2], …, [Sn, vn]) , onde D é um item de dados, v1 é um contador de versão, s1 é um número de servidor etc. . Se um item de dados D for gravado no servidor Si , o sistema deverá executar uma das seguintes tarefas.

  • Se *[Si, vi] existir, incremente vi*.
  • Caso contrário, crie uma nova entrada *[Si, 1]*.

A lógica abstrata acima é explicada com exemplos concretos na Figura 6-9.

imagem-20230525200550038

  1. O cliente grava o item de dados D1 no sistema e a operação de gravação é tratada pelo servidor Sx , que agora possui o relógio vetorial D1[(Sx, 1)] .

  2. Outro cliente lê o D1 mais recente , atualiza-o para D2 e ​​o escreve de volta. D2 é derivado de D1 , portanto substitui D1 . Suponha que a operação de gravação seja tratada pelo mesmo servidor Sx , agora Sx tem relógio vetorial D2([Sx, 2]) .

  3. Outro cliente lê o D2 mais recente , atualiza-o para D3 e o escreve de volta. Suponha que a operação de gravação seja tratada pelo servidor Sy , e agora Sy tem o relógio vetorial D3([Sx, 2], [Sy, 1])) .

  4. Outro cliente lê o D2 mais recente , atualiza-o para D4 e o escreve de volta. Suponha que a operação de gravação seja tratada pelo servidor Sz , e agora Sz tem D4([Sx, 2], [Sz, 1])) .

  5. Quando outro cliente lê D3 e D4 , ele encontra um conflito causado pelo item de dados D2 sendo modificado por Sy e Sz . Os conflitos são resolvidos pelo cliente e os dados atualizados são enviados ao servidor. Suponha que a operação de gravação seja tratada por Sx , e agora Sx tem D5([Sx, 3], [Sy, 1], [Sz, 1]) . Explicaremos como detectar conflitos mais tarde.

Usando relógios vetoriais, é fácil dizer que a versão X é um ancestral da versão Y (ou seja, não há conflito) se os contadores de versão para cada participante no relógio vetorial da versão Y forem maiores ou iguais aos contadores da versão X . Por exemplo, o relógio vetorial *D([s0, 1], [s1, 1]) é um ancestral de D([s0, 1], [s1, 2])*. Portanto, nenhum conflito é registrado.

Da mesma forma, a versão X pode ser considerada irmã de Y se houver algum participante cujo contador no relógio vetorial de Y seja menor que o contador correspondente em X ( ou seja , há um conflito). Por exemplo, os dois relógios vetoriais a seguir indicam um conflito: D([s0, 1], [s1, 2]) e D([s0, 2], [s1, 1]).

Embora os relógios vetoriais possam resolver conflitos, há duas desvantagens significativas. Primeiro, o relógio vetorial aumenta a complexidade do cliente, pois ele precisa implementar a lógica de resolução de conflitos.

Em segundo lugar, os pares *[servidor:versão]* no relógio vetorial podem crescer rapidamente. Para resolver este problema, definimos um limite para o comprimento, se o limite for excedido, o par mais antigo é excluído. Isso pode levar a ineficiências na mediação, uma vez que as relações entre os descendentes não podem ser determinadas com precisão. No entanto, de acordo com o artigo da Dynamo [4], a Amazon não encontrou esse problema na produção; portanto, pode ser uma solução aceitável para a maioria das empresas.

Lidar com falha

Como em qualquer sistema de grande escala, as falhas não são apenas inevitáveis, mas comuns. Lidar com cenários de falha é muito importante. Nesta seção, primeiro introduzimos técnicas para detectar falhas. Em seguida, discutiremos estratégias comuns de solução de problemas.

Detecção de falha

Em um sistema distribuído, não basta acreditar que um servidor está fora do ar só porque outro servidor diz que está fora do ar. Normalmente, pelo menos duas fontes independentes de informações são necessárias para sinalizar um servidor como inativo.

Conforme mostrado na Figura 6-10, o multicast omnidirecional é uma solução simples e direta. No entanto, isso é ineficiente quando há muitos servidores no sistema.

imagem-20230525200611485

Uma solução melhor é usar um método distribuído de detecção de falhas, como o protocolo gossip. O protocolo fofoca funciona da seguinte forma:

  • Cada nó mantém uma lista de membros do nó, que contém IDs de membros e contadores de heartbeat.
  • Cada nó incrementa periodicamente seu contador de heartbeat.
  • Cada nó envia pulsações periodicamente para um conjunto aleatório de nós, que por sua vez se propaga para outro conjunto de nós.
  • Assim que o nó receber a pulsação, a lista de membros será atualizada com as informações mais recentes.
  • Se a pulsação não aumentar dentro de um período de tempo predeterminado, o membro é considerado offline.

imagem-20230525200627631

Conforme mostrado na Figura 6-11:

  • O nó s0 mantém a lista de membros do nó mostrada à esquerda.
  • O nó s0 percebe que o contador de heartbeat do nó s2 (ID do membro = 2) não é incrementado há muito tempo.
  • O nó s0 envia uma pulsação contendo informações sobre s2 para um conjunto de nós aleatórios. Uma vez que outros nós confirmem que o contador de heartbeat de s2 não foi atualizado por um longo tempo, o nó s2 é marcado como inativo e esta informação é propagada para outros nós.

Lidar com falhas temporárias

Após a detecção de uma falha por meio do protocolo gossip, o sistema precisa implantar determinados mecanismos para garantir a disponibilidade. Em uma abordagem de quorum estrita, conforme mostrado na seção de consenso de quorum, as operações de leitura e gravação podem ser bloqueadas.

Uma técnica chamada "arbitragem livre" [4] é usada para melhorar a disponibilidade. Em vez de impor requisitos de quorum, o sistema seleciona os servidores íntegros W superiores no hash ring para operações de gravação e os servidores íntegros R superiores para operações de leitura. Servidores offline serão ignorados.

Se um servidor estiver indisponível devido a uma falha na rede ou no servidor, outro servidor tratará temporariamente das solicitações. Quando o servidor inativo volta a funcionar, as alterações são adiadas para consistência de dados. Este processo é chamado Handoff Insinuado. Como s2 na Figura 6-12 não está disponível, s3 manipulará temporariamente as operações de leitura e gravação. Quando s2 voltar a ficar online, s3 entregará os dados de volta para s2 .

imagem-20230525200643811

Lidando com falhas permanentes

Hinted Handoff é usado para lidar com falhas temporárias. E se a réplica estiver permanentemente indisponível? Para lidar com esses casos, implementamos um protocolo anti-entropia para manter as réplicas sincronizadas. A antientropia envolve a comparação de todos os dados nas réplicas e a atualização de cada réplica para a versão mais recente. Usamos uma estrutura chamada árvore Merkle para detectar inconsistências e minimizar a quantidade de dados transferidos.

Citando da Wikipedia [7]: "Uma árvore hash ou árvore Merkle é uma árvore na qual cada nó não folha é marcado com um hash do rótulo ou valor (no caso de um nó folha) de seus nós filhos. Ha Histree permite a verificação eficiente e segura do conteúdo de grandes estruturas de dados."

Supondo que o keyspace seja de 1 a 12, as etapas a seguir mostram como construir uma árvore Merkle. As caixas destacadas indicam inconsistências.

Passo 1: Divida o keyspace em baldes (4 em nosso exemplo) conforme mostrado na Figura 6-13. Um balde é usado como nó de nível raiz para manter o limite de profundidade da árvore.

imagem-20230525200700209

Passo 2: Uma vez que o balde é criado, faça o hash de cada chave no balde usando um método de hash uniforme (Figura 6-14).

imagem-20230525200711090

Passo 3: Crie um nó de hash para cada balde (Figura 6-15).

imagem-20230525200720689

Etapa 4: construa a árvore até o nó raiz calculando os hashes dos nós filhos (Figura 6-16).

imagem-20230525200732461

Para comparar duas árvores Merkle, primeiro compare os hashes raiz. Se os hashes raiz corresponderem, ambos os servidores terão os mesmos dados. Se os hashes raiz forem diferentes, os hashes filhos esquerdos serão comparados e os hashes filhos direitos. Você pode percorrer a árvore para descobrir quais depósitos não estão sincronizados e sincronizar apenas esses depósitos.

Com as árvores Merkle, a quantidade de dados que precisa ser sincronizada é proporcional à diferença entre as duas réplicas, não à quantidade de dados que elas contêm. Em um sistema do mundo real, o tamanho do balde é bastante grande. Por exemplo, uma configuração possível é ter um milhão de baldes por bilhão de chaves, de modo que cada balde contenha apenas 1.000 chaves.

Lidando com Falhas no Data Center

As falhas do data center podem ocorrer devido a interrupções de energia, interrupções de rede, desastres naturais, etc. Para construir um sistema que possa lidar com falhas do data center, é importante replicar os dados para vários data centers. Mesmo que um datacenter esteja totalmente off-line, os usuários ainda podem acessar os dados por meio de outros datacenters.

Diagrama de arquitetura do sistema

Agora que discutimos as diferentes considerações técnicas ao projetar um armazenamento de valor-chave, podemos voltar nossa atenção para o diagrama de arquitetura, mostrado na Figura 6-17.

imagem-20230525200746256

As principais características da arquitetura são as seguintes:

  • Os clientes se comunicam com o armazenamento de chave-valor por meio de uma API simples: get(key) e put(key,value) .
  • Um coordenador é um nó que atua como um proxy entre os clientes e o armazenamento de valor-chave.
  • Os nós são distribuídos no anel usando hashing consistente.
  • O sistema é totalmente descentralizado, adicionando e movendo nós pode ser feito automaticamente.
  • Os dados são replicados em vários nós.
  • Como cada nó tem o mesmo conjunto de responsabilidades, não há ponto único de falha.

Como o projeto é descentralizado, cada nó executa muitas tarefas, conforme mostrado na Figura 6-18.

imagem-20230525200759966

escrever caminho

A Figura 6-19 explica o que acontece quando uma solicitação de gravação é direcionada a um nó específico. Observe que o projeto proposto do caminho de gravação/leitura é baseado principalmente na arquitetura do Cassandra [8].

imagem-20230525200811822

  1. As solicitações de gravação são mantidas no arquivo de log de confirmação.

  2. Os dados são mantidos no cache de memória.

  3. Quando o memcache está cheio ou atinge um limite predefinido, os dados são liberados para o disco SSTable [9]. Nota: Uma tabela de strings classificada (SSTable) é uma lista classificada de pares. Para os leitores que desejam saber mais sobre o SSTable, consulte a Referência [9].

caminho de leitura

Depois que uma solicitação de leitura é direcionada a um nó específico, primeiro é verificado se os dados estão no cache de memória. Se sim, os dados serão retornados ao cliente, conforme mostra a Figura 6-20.

imagem-20230525200822818

Se os dados não estiverem na memória, eles serão recuperados do disco. Precisamos de uma maneira eficiente de descobrir qual SSTable contém essa chave. Filtros Bloom [10] são normalmente usados ​​para resolver este problema.

A Figura 6-21 mostra o caminho de leitura quando os dados não estão na memória.

imagem-20230525200837584

  1. O sistema primeiro verifica se os dados estão na memória. Se não, vá para a etapa 2.
  2. Se os dados não estiverem na memória, o sistema verifica o filtro bloom.
  3. Os filtros Bloom são usados ​​para determinar quais SSTables provavelmente conterão a chave.
  4. SSTable retorna o resultado do conjunto de dados.
  5. Os resultados do conjunto de dados são retornados ao cliente.

Resumir

Este capítulo apresenta muitos conceitos e técnicas. Para ajudá-lo a se lembrar, a tabela a seguir resume os recursos usados ​​pelos armazenamentos de valor-chave distribuídos e as tecnologias correspondentes.

imagem-20230525200853259

Referências

[1] Amazon DynamoDB: https://aws.amazon.com/dynamodb/

[2] memcached: https://memcached.org/

[3] Redis: https://redis.io/

[4] Dynamo: armazenamento de valor-chave altamente disponível da Amazon: https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf

[5] Cassandra: https://cassandra.apache.org/

[6] Bigtable: um sistema distribuído de armazenamento de dados estruturados: https://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf

[7] Árvore Merkle: https://en.wikipedia.org/wiki/Merkle_tree

[8] Arquitetura Cassandra: https://cassandra.apache.org/doc/latest/architecture/

[9] SStable: https://www.igvita.com/2012/02/06/sstable-and-log-structured-storage-leveldb/

[10] Filtro Bloom https://en.wikipedia.org/wiki/Bloom_filter

Olá, sou Shisan, um piloto veterano que desenvolve há 7 anos e uma empresa estrangeira há 5 anos na Internet há 2 anos. Posso derrotar Ah San e Lao Mei e também fui arruinado por comentários de relações públicas. Ao longo dos anos, trabalhei meio período, abri um negócio, assumi o trabalho privado e misturei o trabalho. Ganhou dinheiro e perdeu dinheiro. Ao longo do caminho, meu sentimento mais profundo é que não importa o que você aprenda, você deve continuar aprendendo. Contanto que você possa perseverar, é fácil conseguir ultrapassagens nas curvas! Portanto, não me pergunte se é tarde demais para fazer o que faço agora. Se você ainda não tem uma direção, pode me seguir [conta pública: Mais AI (power_ai)], onde frequentemente compartilharei algumas informações de ponta e conhecimento de programação para ajudá-lo a acumular capital para curvas e ultrapassagens.

Acho que você gosta

Origin blog.csdn.net/smarter_AI/article/details/131819407
Recomendado
Clasificación