08 redis clássicos cinco tipos de dados e implementação subjacente

Redis é um par chave-valor KV de banco de dados de dicionário

  • Redis é um sistema de armazenamento de valor-chave, onde o tipo de chave é geralmente uma string e o tipo de valor é um objeto redis (redisObject)
  • insira a descrição da imagem aqui
  • Redis define a estrutura redisObject para representar string, hash, lista, conjunto, zset e outros tipos de dados
    • Introdução à sintaxe da estrutura struct da linguagem C
    • insira a descrição da imagem aqui
    • insira a descrição da imagem aqui
    • Todo objeto no Redis é uma estrutura redisObject
    • O que são dicionários e KV
    • insira a descrição da imagem aqui
    • insira a descrição da imagem aqui
    • Cada par chave-valor terá um dictEntry
  • A relação entre redisObject + tipo de dados Redis + todos os métodos de codificação Redis (implementação subjacente)
  • insira a descrição da imagem aqui

Análise do código-fonte da linguagem C subjacente das cinco estruturas principais

Começando com o conjunto hello world

  • Tome set hello word como exemplo, porque o Redis é um banco de dados de pares chave-valor KV, cada par chave-valor terá um dictEntry (localização do código-fonte: dict.h), que aponta para ponteiros para chave e valor, e próximo aponta para o próximo dictEntry.
  • A chave é uma string, mas o Redis não usa diretamente a matriz de caracteres de C, mas a armazena no SDS personalizado pelo redis.
  • O valor não é armazenado diretamente como uma string nem diretamente no SDS, mas em um redisObject.
  • Na verdade, qualquer um dos cinco tipos de dados comumente usados ​​é armazenado por meio do redisObject.
  • insira a descrição da imagem aqui
  • Cada par chave-valor terá um dictEntry

O papel da estrutura redisObject

O significado de cada campo de RedisObject

  • insira a descrição da imagem aqui
  • insira a descrição da imagem aqui
  • O tipo de 4 bits indica o tipo de dados específico
  • A codificação de 4 bits indica o método de codificação física desse tipo, veja a tabela abaixo, e o mesmo tipo de dados pode ter diferentes métodos de codificação.
    (Por exemplo, String fornece 3 tipos: int embstr raw)
  • O campo lru indica que quando a memória excede o limite, o algoritmo LRU é usado para limpar os objetos da memória.
  • refcount representa a contagem de referência do objeto.
  • O ponteiro ptr aponta para um ponteiro para a estrutura de dados subjacente real.

O caso

insira a descrição da imagem aqui

Relação entre tipos de dados e estruturas de dados

Quando os programadores escrevem código, eles pensam no fundo de seus cérebros

insira a descrição da imagem aqui

Introdução à estrutura de dados String

3 principais formatos de codificação
  • int
    • Contém um inteiro com sinal de 64 bits (8 bytes) do tipo long (long integer)
    • Somente números inteiros usam int. Se for um número de ponto flutuante, o Redis realmente converte o número de ponto flutuante em um valor de string primeiro e depois o salva.
  • embstr
    • Representa SDS (Simple Dynamic String) no formato embstr, salvando strings com comprimento menor que 44 bytes
    • EMBSTR, como o nome sugere: string incorporada, significa String incorporada
  • cru
    • Salvar strings com comprimento maior que 44 bytes
3 grandes casos de codificação

insira a descrição da imagem aqui

Exibição de strings em linguagem C
- Redis没有直接复用C语言的字符串,而是新建了属于自己的结构-----SDS
- 在Redis数据库里,包含字符串值的键值对都是由SDS实现的(Redis中所有的键都是由字符串对象实现的即底层是由SDS实现,Redis中所有的值对象中包含的字符串对象底层也是由SDS实现)。
Por que Redis redesenha uma estrutura de dados SDS?
  • A linguagem C não possui o tipo String em Java, então ela só pode ser realizada por seu próprio char[]. O método de armazenamento da string na linguagem C, se você deseja obter o comprimento de "Redis", você precisa para percorrer desde o início até encontrar '\ 0'. Portanto, Redis não usa diretamente o tradicional identificador de string da linguagem C, mas constrói um tipo abstrato chamado simple dynamic string SDS (simple dynamic string) e usa SDS como string padrão do Redis.
  • insira a descrição da imagem aqui
Três códigos principais
  • formato de codificação INT
    • definir k1 123
    • Quando o conteúdo do valor da chave de string puder ser representado por um inteiro com sinal de 64 bits, o Redis converterá o valor da chave em um tipo longo para armazenamento, que corresponde ao tipo de codificação OBJ_ENCODING_INT. A estrutura da memória interna é representada da seguinte forma:
    • insira a descrição da imagem aqui
    • Quando o Redis é iniciado, ele pré-cria 10.000 variáveis ​​redisObject que armazenam 0 a 9.999, respectivamente, como objetos compartilhados, o que significa que, se o valor da chave da string definida estiver entre 0 a 10.000, ele pode apontar diretamente para o objeto compartilhado sem criar um novo objeto, o valor da chave não ocupa espaço neste momento!
    • insira a descrição da imagem aqui
  • formato de codificação EMBSTR
    • definir k1 abc
    • insira a descrição da imagem aqui
    • Para strings com comprimento menor que 44, o Redis usa o método OBJ_ENCODING_EMBSTR para o valor da chave. EMBSTR, como o nome indica: string incorporada, significa uma String incorporada. Em termos de estrutura de memória, a estrutura de string sds e seu objeto redisObject correspondente são alocados no mesmo espaço de memória contínuo, e a string sds é incorporada ao objeto redisObject.
    • insira a descrição da imagem aqui
    • insira a descrição da imagem aqui
  • Formato de codificação RAW
    • set k1 Uma cadeia de caracteres com comprimento maior que 44, basta escrever
    • Quando o valor da chave da string é uma string ultralonga com comprimento maior que 44, o Redis mudará o método de codificação interna do valor da chave para o formato OBJ_ENCODING_RAW, que é diferente do método de codificação OBJ_ENCODING_EMBSTR. string sds A memória e a memória do redisObject do qual ele depende não são mais contínuas
    • insira a descrição da imagem aqui
  • Obviamente não ultrapassou o limite, por que ficou cru
    • insira a descrição da imagem aqui
  • Diagrama Lógico de Transformação
    • insira a descrição da imagem aqui
Conclusão do caso
  • Somente números inteiros usam int. Se for um número de ponto flutuante, o Redis realmente converte o número de ponto flutuante em um valor de string primeiro e depois o salva.
  • As estruturas de dados subjacentes dos tipos embstr e raw são, na verdade, SDS (string dinâmica simples, uma estrutura definida como sdshdr dentro do Redis).
  • insira a descrição da imagem aqui
  • insira a descrição da imagem aqui
Resumir
  • O Redis usa internamente diferentes formatos de codificação de acordo com diferentes valores de chave fornecidos pelos usuários e seleciona de forma adaptativa um formato de codificação interno mais otimizado, e tudo isso é totalmente transparente para os usuários!
  • insira a descrição da imagem aqui

Introdução à estrutura de dados Hash

O caso

  • hash-max-ziplist-entries: O número máximo de elementos na coleção de hash ao usar a lista compactada para salvar.
  • hash-max-ziplist-value: O comprimento máximo de um único elemento em uma coleção de hash quando salvo usando uma lista compactada.
  • Quando o número de campos de uma chave do tipo Hash for menor que hash-max-ziplist-entries e o comprimento de cada nome de campo e valor de campo for menor que hash-max-ziplist-value, o Redis usará OBJ_ENCODING_ZIPLIST para armazenar a chave, e qualquer uma das condições anteriores não atender Ele será convertido para o método de codificação de OBJ_ENCODING_HT
  • insira a descrição da imagem aqui
  • insira a descrição da imagem aqui
para concluir
  • O número de pares chave-valor armazenados no objeto hash é menor que 512;
  • Se os comprimentos de string de chave e valor de todos os pares de valor-chave forem menores ou iguais a 64 bytes (uma letra em inglês e um byte), use ziplist, caso contrário, use hashtable
  • Ziplist pode ser atualizado para hashtable, mas vice-versa
    • Depois que a lista compactada é convertida em uma tabela de hash, o tipo de hash sempre será armazenado na tabela de hash e não será convertido de volta para a lista compactada.
    • As tabelas hash não são tão eficientes quanto as listas compactadas em termos de economia de espaço de memória.
processo

insira a descrição da imagem aqui

Análise do código-fonte

ziplist.c
Como é a ziplist?
  • Ziplist é uma lista encadeada duplamente codificada. Ela não armazena ponteiros para o nó da lista encadeada anterior e o próximo nó da lista encadeada, mas armazena o comprimento do nó anterior e o comprimento do nó atual. Ao sacrificar parte da leitura e desempenho de gravação, em troca de utilização eficiente do espaço de memória, economia de memória, é uma ideia de trocar tempo por espaço. É usado apenas em cenários com um pequeno número de campos e pequenos valores de campo
  • insira a descrição da imagem aqui
  • insira a descrição da imagem aqui
  • insira a descrição da imagem aqui
O que significam os componentes do ziplist?

insira a descrição da imagem aqui

Obviamente, existe uma lista encadeada, por que existe uma lista encadeada comprimida?
  • Uma lista duplamente encadeada comum terá dois ponteiros.Quando os dados armazenados são pequenos, o tamanho dos dados reais que armazenamos pode não ser tão grande quanto a memória ocupada pelos ponteiros, o que não vale a pena. ziplist é uma lista especial duplamente encadeada que não mantém um ponteiro bidirecional: prev next; em vez disso, ele armazena o comprimento da entrada anterior e o comprimento da entrada atual e calcula onde o próximo elemento é baseado no comprimento. Sacrificar o desempenho da leitura para obter espaço de armazenamento eficiente, porque (no caso de strings curtas) o armazenamento de ponteiros consome mais memória do que o armazenamento de comprimentos de entrada. Este é um típico "tempo para o espaço".
  • As listas encadeadas geralmente são descontínuas na memória e a travessia é relativamente lenta e o ziplist pode resolver esse problema muito bem. A travessia de arrays comuns é encontrar o próximo elemento de acordo com o tipo de dados armazenado no array (por exemplo, o array de O tipo int é acessado em Você só precisa mover um sizeof(int) de cada vez para um elemento), mas o comprimento de cada nó da ziplist pode ser diferente e é impossível para nós dimensionarmos diretamente o sizeof(entry) quando enfrentamos nós de comprimentos diferentes, então ziplist tem que Algumas informações de deslocamento necessárias são registradas em cada nó, para que ele possa pular para o nó anterior ou o próximo nó.
  • Existe um parâmetro len no nó principal, que é semelhante ao SDS mencionado no tipo string e é usado para registrar o comprimento da lista encadeada. Portanto, ao obter o comprimento da lista encadeada, não há necessidade de percorrer toda a lista encadeada, basta obter o valor len diretamente. Essa complexidade de tempo é O(1)
  • insira a descrição da imagem aqui
Composição de nós de lista compactada
  • Uma lista compactada é uma estrutura de dados sequencial composta por uma série de blocos de memória contíguos especialmente codificados implementados pelo Redis para economizar espaço, essencialmente uma matriz de bytes
  • No modelo, essas matrizes contínuas são divididas em 3 partes, que são cabeçalho+entrada coleção+final
    • O cabeçalho é composto por zlbytes+zltail+zllen
    • a entrada é um nó
    • zlend é um único byte 255 (1111 1111), usado como identificador final de ZipList. Veja abaixo: Estrutura de lista comprimida: composta de cinco partes: zlbytes, zltail, zllen, entry, zlend
    • insira a descrição da imagem aqui
    • zlbytes 4 bytes, registra o número de bytes de memória ocupados por toda a lista compactada.
      zltail 4 bytes, registra a posição do nó final da tabela de lista compactada.
      zllen 2 bytes, registre o número de nós da lista compactada.
      nó da lista zlentry, o comprimento é variável, determinado pelo conteúdo.
      zlend 1 byte, 0xFF marca o fim da compactação.
Análise da estrutura da entidade zlentry

insira a descrição da imagem aqui

  • Estrutura do nó zlentry da lista comprimida: cada zlentry é composto pelo comprimento do nó anterior, codificação e dados de entrada
  • insira a descrição da imagem aqui
  • Nó anterior: (número de bytes de memória ocupados pelo nó anterior) indica o comprimento do zlentry anterior e prev_len tem dois valores: 1 byte ou 5 bytes. Quando o valor é 1 byte, significa que o comprimento da entrada anterior é menor que 254 bytes. Embora a faixa de valores que pode ser representada por um valor de 1 byte seja de 0 a 255, o valor padrão de zlend na lista compactada é 255. Portanto, 255 é usado por padrão para indicar o final de toda a lista compactada , e outros locais que indicam o comprimento não podem ser usados. 255 é o valor. Portanto, quando o comprimento da entrada anterior for menor que 254 bytes, o valor de prev_len é 1 byte, caso contrário, o valor é 5 bytes.
  • codificação : registra o tipo e o comprimento do conteúdo do nó para salvar os dados.
  • content : salva o conteúdo de dados real
  • insira a descrição da imagem aqui
acesso à ziplist

insira a descrição da imagem aqui

t_hash.c

Conhecido como dicionário (dictionary), é uma estrutura de array + lista encadeada
insira a descrição da imagem aqui

Análise de codificação OBJ_ENCODING_HT
  • O método de codificação OBJ_ENCODING_HT é a estrutura real da tabela de hash, ou estrutura do dicionário, que pode atingir operações de leitura e gravação de complexidade O(1), por isso é muito eficiente.
  • Cada par chave-valor terá um dictEntry
  • Dentro do Redis, do tipo OBJ_ENCODING_HT à estrutura de dados subjacente da tabela de hash real é aninhada camada por camada, e o relacionamento organizacional é mostrado no diagrama:
  • insira a descrição da imagem aqui

Introdução à estrutura de dados de lista

O caso

insira a descrição da imagem aqui

  • Configuração de compressão Ziplist: list-compress-depth 0
    • Indica o número de nós que não são compactados em ambas as extremidades de uma lista rápida. Os nós aqui referem-se aos nós da lista duplamente vinculada da lista rápida, não ao número de itens de dados na ziplist
    • O significado do valor do parâmetro list-compress-depth é o seguinte:
      0: É um valor especial, ou seja, sem compressão. Este é o padrão para Redis.
      1: indica que um nó em ambas as extremidades da lista rápida não está compactado e o nó do meio está compactado.
      2: indica que dois nós em ambas as extremidades da lista rápida não estão compactados e o nó do meio está compactado.
      3: indica que 3 nós em ambas as extremidades da lista rápida não estão compactados e os nós no meio estão compactados.
      E assim por diante…
  • Configuração de entrada na ziplist: list-max-ziplist-size -2
    • Quando assume um valor positivo, significa que o comprimento da ziplist em cada nó da lista rápida é limitado de acordo com o número de itens de dados. Por exemplo, quando esse parâmetro é definido como 5, significa que a ziplist de cada nó da lista rápida contém no máximo 5 itens de dados. Ao assumir um valor negativo, significa que o comprimento da ziplist em cada nó da lista rápida é limitado de acordo com o número de bytes ocupados. Neste momento, ele pode assumir apenas cinco valores de -1 a -5, e
      o significado de cada valor é o seguinte:
      -5: O tamanho da ziplist em cada nó da lista rápida não pode exceder 64 Kb. (Nota: 1kb => 1024 bytes)
      -4: O tamanho da ziplist em cada nó da lista rápida não pode exceder 32 Kb.
      -3: O tamanho da ziplist em cada nó da lista rápida não pode exceder 16 Kb.
      -2: O tamanho da ziplist em cada nó da lista rápida não pode exceder 8 Kb. (-2 é o valor padrão fornecido pelo Redis)
      -1: O tamanho da ziplist em cada nó da lista rápida não pode exceder 4 Kb.

Um formato de codificação de Lista

  • A lista é armazenada em uma lista rápida, que armazena uma lista duplamente encadeada, e cada nó é uma ziplist
  • insira a descrição da imagem aqui
  • lista rápida
    • Na versão inferior do Redis, a estrutura de dados subjacente usada por lista é ziplist+linkedList;
    • A estrutura de dados subjacente na versão superior do Redis é a lista rápida (substitui ziplist+linkedList) e a lista rápida também usa ziplist.
    • Quicklist é na verdade uma mistura de zipList e linkedList. Ele divide linkedList em segmentos, usa zipList para armazenamento compacto de cada segmento e usa ponteiros bidirecionais para conectar vários zipLists em série.
    • insira a descrição da imagem aqui

Análise do código-fonte

  • lista rápida.h, cabeça e cauda apontam para a cabeça e cauda da lista bidirecional
  • insira a descrição da imagem aqui
  • insira a descrição da imagem aqui
  • *zl em quicklistNode aponta para uma ziplist, e uma ziplist pode armazenar vários elementos
  • insira a descrição da imagem aqui
  • insira a descrição da imagem aqui

Introdução à estrutura de dados definida

O caso

  • O Redis usa intset ou hashtable para armazenar o conjunto. Se os elementos forem todos do tipo inteiro, use intset para armazená-los.
  • Se não for um tipo inteiro, use hashtable (array + lista encadeada para armazenar a estrutura). A chave é o valor do elemento e o valor é nulo.
  • insira a descrição da imagem aqui

Introdução à estrutura de dados do ZSet

O caso

  • Quando o número de elementos contidos no conjunto classificado excede o valor da propriedade do servidor server.zset_max_ziplist_entries (o valor padrão é 128) ou o comprimento do membro do elemento recém-incluído no conjunto classificado é maior que a propriedade do servidor. Quando o valor de server.zset_max_ziplist_value (o valor padrão é 64), o redis usará a tabela de salto como a implementação subjacente do conjunto ordenado. Caso contrário, ziplist será usado como a implementação subjacente da coleção ordenada
  • insira a descrição da imagem aqui
  • insira a descrição da imagem aqui

pequeno resumo

A relação entre tipos de dados redis e estruturas de dados

insira a descrição da imagem aqui

As estruturas de dados subjacentes correspondentes a diferentes tipos de dados

  1. String
    int: inteiro longo de 8 bytes.
    embstr: Uma string de 44 bytes ou menos.
    raw: Uma string maior que 44 bytes.
    O Redis decidirá qual implementação de codificação interna usar com base no tipo e comprimento do valor atual.

  2. Hash
    ziplist (lista compactada): Quando o número de elementos do tipo hash é menor que a configuração hash-max-ziplist-entries (512 por padrão) e todos os valores são menores que a configuração hash-max-ziplist-value ( 64 bytes por padrão), o Redis usará ziplist como a implementação interna de hashing. Ziplist usa uma estrutura mais compacta para obter armazenamento contínuo de vários elementos, portanto, é melhor do que hashtable para economizar memória.
    hashtable (tabela de hash): quando o tipo de hash não pode atender às condições de ziplist, o Redis usará hashtable como a implementação interna de hash, porque a eficiência de leitura e gravação de ziplist diminuirá neste momento e a complexidade do tempo de leitura e gravação de hashtable é O (1).

  3. List
    ziplist (lista compactada): quando o número de elementos na lista é menor que a configuração list-max-ziplist-entries (padrão 512) e o valor de cada elemento na lista é menor que list-max-ziplist-value configuração (padrão 64 bytes), o Redis escolherá ziplist como a implementação interna da lista para reduzir o uso de memória.
    linkedlist (lista vinculada): quando o tipo de lista não pode atender às condições da ziplist, o Redis usará a linkedlist como a implementação interna da lista. A combinação de ziplist de lista rápida e lista vinculada Lista vinculada com ziplist como nó (lista vinculada)

  4. Definir
    intset (conjunto inteiro): quando os elementos no conjunto são todos inteiros e o número de elementos é menor que a configuração set-max-intset-entries (512 por padrão), o Redis usará intset como a implementação interna do conjunto , reduzindo assim o uso de uso de memória.
    hashtable (tabela de hash): quando o tipo de coleção não pode atender às condições de intset, o Redis usará hashtable como a implementação interna da coleção.

  5. Ziplist de conjunto ordenado
    (lista compactada): quando o número de elementos no conjunto ordenado é menor que a configuração zset-max-ziplist-entries (padrão 128) e o valor de cada elemento é menor que zset-max-ziplist- configuração de valor (padrão 64 bytes), Redis usará ziplist como a implementação interna da coleção ordenada, ziplist pode efetivamente reduzir o uso de memória.
    Skiplist (skip list): Quando a condição ziplist não for atendida, a coleção ordenada usará a skiplist como implementação interna, porque a eficiência de leitura e gravação da ziplist diminuirá neste momento.

Complexidade de tempo de tipos de dados redis e estruturas de dados

insira a descrição da imagem aqui

skiplist questões de teste de superfície de salto

o que é

  • Uma lista de salto é uma lista encadeada ordenada que pode implementar pesquisa binária
  • Uma skiplist é uma estrutura que troca espaço por tempo.
  • Devido à lista vinculada, a pesquisa binária não pode ser executada, portanto, a ideia do índice do banco de dados é usada para extrair os nós principais (índices) na lista vinculada, primeiro pesquisar nos nós principais e, em seguida, inserir a lista vinculada inferior para procurar.
  • Extraia nós de chave multicamada para formar uma tabela de salto
  • Em resumo, lista de atalhos = lista encadeada + índice multinível

Fale sobre as vantagens e desvantagens da lista vinculada e da matriz? Por que levar à lista de salto

Pontos de dor

insira a descrição da imagem aqui
insira a descrição da imagem aqui

Complexidade de tempo da lista de pular

  • Primeiramente, aumentamos o span de cada nível de índice em 2 vezes, ou seja, reduzimos o número de passos em 2 vezes, então fica n/2, n/4, n/8 e assim por diante;
  • O número de nós de índice de nível k é n/(2^k);
  • Suponha que o índice tenha h níveis e o índice mais alto tenha 2 nós; n/(2^h) = 2, dessa fórmula podemos obter h = log2(N)-1;
  • Então, no final, a complexidade de tempo de pular a lista é O(logN)

Complexidade de espaço da lista de pular

  • Primeiro, o comprimento da lista encadeada original é n
  • Se o índice tiver um nó de índice a cada 2 nós, o número de nós em cada camada de índice: n/2, n/4, n/8 … , 8, 4, 2 e assim por diante
  • Ou então há um nó de índice para cada 3 nós, o número de nós em cada índice de camada: n/3, n/9, n/27 ... , 9, 3, 1 e assim por diante;
  • Então a complexidade do espaço é O(n);

Vantagens e desvantagens

  • A tabela de salto é a solução espaço-por-tempo mais comum e suas vantagens só podem ser mostradas quando a quantidade de dados é grande. E só deve ser usado ao ler mais e escrever menos, portanto seu escopo de aplicação ainda deve ser relativamente limitado
  • O custo de manutenção é relativamente alto - ao adicionar ou excluir, todos os índices precisam ser atualizados novamente; finalmente, a complexidade de tempo de atualização no processo de adição e exclusão também é O(log n)

perguntas comuns da entrevista

  • O Redis é de thread único, como você entende isso e por que o autor o projetou dessa maneira?
  • Você entende a lista de atalhos do redis? Quais são as características dessa estrutura de dados
  • Como usá-lo no projeto redis? O que você sabe sobre a estrutura de dados do redis? Como usar o filtro Bloom?
  • Como entender a multiplexação redis multiplexação io, por que um único thread pode resistir a um qps tão alto
  • A implementação subjacente do zset do redis
  • Vamos falar sobre a tabela de salto do redis, resolver esses problemas, como sobre a complexidade de tempo e a complexidade de espaço

Acho que você gosta

Origin blog.csdn.net/m0_56709616/article/details/131023970
Recomendado
Clasificación