O que fazer se a distribuição de hash no Redis for desigual

Prefácio

Redis é um banco de dados de pares de valores-chave, cujas chaves são armazenadas por meio de hashes. Todo o Redis pode ser considerado um hash externo. O motivo pelo qual é chamado de hash externo é porque o Redis também fornece um tipo de hash internamente, que pode ser chamado de hash interno. Quando usamos objetos hash para armazenamento de dados, para todo o Redis, ele passa por duas camadas de armazenamento hash.

 

Objeto Hash

O próprio objeto hash também é uma estrutura de armazenamento de valor-chave e a estrutura de armazenamento subjacente também pode ser dividida em dois tipos: ziplist (lista compactada) e hashtable (tabela hash). Essas duas estruturas de armazenamento também são distinguidas pela codificação:

Descrição do atributo de codificação valor de retorno do comando de codificação de objeto OBJ_ENCODING_ZIPLIST Use lista compactada para implementar ziplist de objeto hash OBJ_ENCODING_HT Use dicionário para implementar hashtable de objeto hash

 

hashtable

O valor-chave no Redis é agrupado pelo objeto dictEntry e a tabela hash é obtida empacotando o objeto dictEntry novamente. Este é o objeto da tabela hash dictht:

typedef struct dictht {    dictEntry **table;//哈希表数组    unsigned long size;//哈希表大小    unsigned long sizemask;//掩码大小,用于计算索引值,总是等于size-1    unsigned long used;//哈希表中的已有节点数} dictht;复制代码

Nota: A tabela na definição de estrutura acima é uma matriz, cada elemento da qual é um objeto dictEntry.

 

dicionário

Um dicionário, também conhecido como tabela de símbolos, matriz associativa ou mapa, tem um objeto dictht de tabela hash aninhado dentro do dicionário. A seguir está a definição de um dicionário ht:

typedef struct dictType {    uint64_t (*hashFunction)(const void *key);//计算哈希值函数    void *(*keyDup)(void *privdata, const void *key);//复制键函数    void *(*valDup)(void *privdata, const void *obj);//复制值函数    int (*keyCompare)(void *privdata, const void *key1, const void *key2);//对比键函数    void (*keyDestructor)(void *privdata, void *key);//销毁键函数    void (*valDestructor)(void *privdata, void *obj);//销毁值函数} dictType;复制代码

Entre eles, dictType define algumas funções comumente usadas, e sua estrutura de dados é definida da seguinte forma:

typedef struct dictType {    uint64_t (*hashFunction)(const void *key);//计算哈希值函数    void *(*keyDup)(void *privdata, const void *key);//复制键函数    void *(*valDup)(void *privdata, const void *obj);//复制值函数    int (*keyCompare)(void *privdata, const void *key1, const void *key2);//对比键函数    void (*keyDestructor)(void *privdata, void *key);//销毁键函数    void (*valDestructor)(void *privdata, void *obj);//销毁值函数} dictType;复制代码

Quando criamos um objeto hash, podemos obter o seguinte diagrama (alguns atributos são omitidos):

O que fazer se a distribuição de hash do Redis for desigual

 

operação de refazer

Uma matriz ht [2] é definida em dict, e duas tabelas de hash são definidas em ht [2]: ht [0] e ht [1]. O Redis usará apenas ht [0] por padrão e não usará ht [1], nem alocará espaço para a inicialização de ht [1].

Ao definir um objeto hash, qual subscrito no array hash (dictEntry [3] na figura acima) irá cair é determinado pelo cálculo do valor hash. Se ocorrer uma colisão de hash (o valor de hash calculado é o mesmo), o mesmo subscrito terá vários dictEntry, formando assim uma lista vinculada (o ponto mais à direita na figura acima aponta para a posição NULL), mas deve-se notar que a última inserção O elemento está sempre no topo da lista vinculada (ou seja, quando ocorre um conflito de hash, o nó é sempre colocado no topo da lista vinculada).

Ao ler dados, ao encontrar um nó com vários elementos, você precisa percorrer a lista vinculada, portanto, quanto mais longa a lista vinculada, pior é o desempenho. Para garantir o desempenho da tabela de hash, a tabela de hash precisa ser refeita quando uma das duas condições a seguir for atendida:

  • Quando o fator de carga é maior ou igual a 1 e dict_can_resize é 1.
  • Quando o fator de carga é maior ou igual ao limite de segurança (dict_force_resize_ratio = 5).

PS: Fator de carga = número de nós usados ​​na tabela hash / tamanho da tabela hash (ou seja: h [0] .used / h [0] .size).

 

etapa de refazer

O hash de expansão e o hash de redução são concluídos pela execução de um novo hash, que envolve alocação e liberação de espaço, principalmente por meio das cinco etapas a seguir:

  1. Alocar espaço para a tabela hash ht [1] do dicionário dict, cujo tamanho depende do número de nós salvos na tabela hash atual (ou seja: ht [0] .used):
  2. Defina o valor do atributo rehashix no dicionário como 0, indicando que a operação de rehash está sendo executada.
  3. Recalcule os valores de hash de todos os pares de valores-chave em ht [0] por sua vez e coloque-os na posição correspondente da matriz ht [1]. O valor de rehashix precisa ser incrementado em 1 após a conclusão do rehash de um par de valor-chave.
  4. Quando todos os pares de valores-chave em ht [0] são migrados para ht [1], libere ht [0], altere ht [1] para ht [0] e crie uma nova matriz ht [1], para preparar para o próximo rehash.
  5. Defina o atributo rehashix no dicionário como -1, o que significa que esta operação de rehash acabou e aguarde o próximo rehash.

 

Rehash progressivo

Essa operação de refazer o hash no Redis não é refazer tudo de  uma vez, mas refazer lentamente  o par de valores-chave em  ht [0]  para  ht [1] em várias vezes  , portanto, essa operação também é chamada de É refazer progressivo  . O rehash progressivo pode evitar a enorme quantidade de cálculos trazidos pelo rehash centralizado, que é uma ideia de dividir para conquistar.

No processo de rehash progressivo, como pode haver novos pares de valores-chave armazenados, neste momento ** a abordagem do Redis é colocar os pares de valores-chave recém-adicionados em ht [1] uniformemente, de modo a garantir ht [0] O número de pares de valores-chave só diminuirá **.

Quando o rehash da operação está sendo executado, se o servidor receber um comando da operação de solicitação do cliente, primeiro consultará  ht [0] , então os resultados parecerão menores que ht [1]  query .

 

tirolesa

Algumas características do Ziplist foram analisadas separadamente no artigo anterior. Se você quiser saber mais, clique aqui. Mas deve-se notar que a diferença entre o ziplist no objeto hash e o ziplist no objeto de lista é que o objeto hash é uma forma de valor-chave, então o ziplist também aparece como um valor-chave, e a chave e o valor estão próximos:

O que fazer se a distribuição de hash do Redis for desigual

 

ziplist e conversão de codificação hashtable

Quando um objeto hash pode atender a qualquer uma das duas condições a seguir, o objeto hash escolherá usar a codificação ziplist para armazenamento:

  • O comprimento total de todos os pares de valor-chave no objeto hash (incluindo chaves e valores) é menor ou igual a 64 bytes (esse limite pode ser controlado pelo parâmetro hash-max-ziplist-value).
  • O número de pares de valores-chave no objeto hash é menor ou igual a 512 (esse limite pode ser controlado pelo parâmetro hash-max-ziplist-entries).

Quando qualquer uma dessas duas condições não for atendida, o objeto hash escolherá usar a codificação hashtable para armazenamento.

 

Comandos comuns para objetos hash

  • valor do campo-chave hset: Defina um único campo (valor-chave do objeto hash).
  • hmset key field1 value1 field2 value2: Defina vários campos (valores-chave de objetos hash).
  • Valor do campo-chave hsetnx: Defina o valor do campo do campo na chave da tabela hash como valor.Se o campo já existir, nenhuma operação será executada.
  • hget key field: Obtenha o valor correspondente ao campo do campo na chave da tabela hash.
  • hmget key field1 field2: obtenha o valor correspondente a vários campos na chave da tabela hash.
  • hdel key field1 field2: Exclua um ou mais campos na chave da tabela hash.
  • Chave hlen: Retorna o número de campos na chave da tabela hash.
  • Incremento do campo da chave Hincrby: adiciona um incremento ao valor do campo do campo na chave da tabela hash. O incremento pode ser um número negativo. Se o campo não for um número, um erro será relatado.
  • Incremento do campo-chave hincrbyfloat: adiciona incremento ao valor do campo do campo na chave da tabela hash. O incremento pode ser um número negativo. Se o campo não for do tipo flutuante, um erro será relatado.
  • chave hkeys: obtém todos os campos na chave da tabela hash.
  • Chave hvals: obtenha os valores de todos os campos da tabela hash.

Conhecendo os comandos comuns para operar objetos hash, podemos verificar o tipo e a codificação dos objetos hash mencionados anteriormente. Para evitar a interferência de outros valores de chave antes do teste, primeiro executamos o comando flushall para limpar o banco de dados Redis.

Em seguida, execute os seguintes comandos em sequência:

hset address country chinatype addressobject encoding address复制代码

Obtenha os seguintes efeitos:

O que fazer se a distribuição de hash do Redis for desigual

 

Você pode ver que, quando há apenas um par de valor-chave em nosso objeto hash, a codificação subjacente é ziplist.

Agora alteramos o parâmetro hash-max-ziplist-entries para 2, reiniciamos o Redis e, por fim, inserimos o seguinte comando para testar:

hmset key field1 value1 field2 value2 field3 value3object encoding key复制代码

Após a saída, os seguintes resultados são obtidos:

O que fazer se a distribuição de hash do Redis for desigual

 

Como você pode ver, a codificação tornou-se uma hashtable.

 

Resumindo

Este artigo apresenta principalmente o uso de hashtable, a estrutura de armazenamento subjacente do tipo hash entre os cinco tipos de dados comumente usados ​​no Redis e como o Redis executa o re-hash quando a distribuição de hash é desigual. Por fim, aprendi sobre alguns hash comumente usados objetos Solicite e verifique a conclusão deste artigo através de alguns exemplos.

Acho que você gosta

Origin blog.csdn.net/Java0258/article/details/112990267
Recomendado
Clasificación