Este artigo não é um artigo de ensino, é apenas para uma visão geral rápida dos pontos de conhecimento
Índice
prefácio
O tipo de superfície e o tipo de fundo do Redis
Para melhorar o desempenho de leitura e gravação, no caso de diferentes tipos e volumes de dados, diferentes métodos de armazenamento e gerenciamento precisam ser usados. Para melhorar a taxa de transferência do servidor, garantindo a facilidade de uso do redis. redis distingue a relação entre a estrutura de dados subjacente e o objeto redis . Por exemplo: quando a quantidade de dados HashMap é pequena, a camada inferior é armazenada na estrutura ziplist em vez de dict; quando o conjunto é uma coleção de inteiros puros, use intset em vez de dict e assim por diante . Aqui, a estrutura de dados e os objetos de superfície da implementação subjacente são resumidos separadamente e, finalmente, o relacionamento entre eles é resolvido. Para esclarecer o princípio de implementação do tipo de dados redis, é muito importante garantir a eficiência de execução dos códigos que envolvem a interação redis no desenvolvimento diário.
meta de aprendizado
- Compreender as estruturas de dados de diferentes objetos e seus cenários de uso
- Entenda o tempo e a eficiência das conversões entre diferentes estruturas de dados
- Entenda que estruturas de dados diferentes funcionam de maneira diferente
- Entenda o mecanismo de reciclagem de dados do redis e seu possível impacto
estrutura de dados subjacente
Cadeias Dinâmicas - SDS
característica
- A essência é um array dinâmico de caracteres
- Caracteres nulos
'\0'
não são contados em bytes usados nem em bytes não usados - Adote pré-alocação de espaço + estratégia de liberação preguiçosa
- Quando o tamanho modificado for menor que 1 MB, o espaço pré-alocado é igual ao espaço usado; quando for maior que 1 MB, o espaço pré-alocado é 1 MB
- Liberação preguiçosa significa não liberar ativamente, mas existem APIs sds que fornecem liberação de memória
- Segurança binária.
'\0'
Marque o final da string não com , mas com len - Na implementação, além de existir de forma independente, o SDS também será aninhado em outros tipos em objetos Redis para realizar objetos Redis específicos (por exemplo, dict usa sds para armazenar valores de elementos específicos e implementar mapa de hash)
- Estouro de memória: uso de espaço não alocado
- Vazamento de memória: o espaço alocado não é mais usado, mas não é liberado
- A solicitação de espaço de memória é uma operação lenta: há alternância entre o modo de usuário e o modo kernel e o agendamento de recursos externos (troca de pilha e troca; aplicativo de memória, etc. A velocidade de agendamento de memória é muito mais lenta do que a velocidade de computação da CPU)
- Pode ser para melhorar ainda mais a utilização da memória redis. A partir do redis 3.2, a estrutura sdshdr original é dividida em sdshdr5; sdshdr8; sdshdr16; sdshdr32; sdshdr64 para armazenar strings de diferentes comprimentos. Entre eles, o sdshdr5 diz em seu código-fonte que nunca será usado
eficácia
- A aquisição do comprimento da string é O(1) —— leia diretamente a variável membro da estrutura sds
- A limpeza de strings (sdsclear) é O(1) - realizada diretamente
len = 0
, sem redefinir valores de elementos individuais - Liberar o espaço de memória (sdsfree) é O(n)—n é o espaço de memória alocado. Observe que é diferente de sdsclear
Lista duplamente encadeada - list&listNode
característica
- Polimorfismo : listNode usa
void *
valores armazenados. Diferentes tipos dedup
;match
;free
métodoslistSet<Xxx>Method
definidos - A estrutura da lista contém comprimento e ponteiros de cabeça e cauda
eficácia
- As operações relacionadas ao predecessor e sucessor são O(1) - obter predecessor/sucessor, inserir nó, excluir determinado nó
- As operações que precisam encontrar um único nó são todas O(n) — obter nós por valor, obter nós por índice, excluir nós, etc.
Pular lista - zskipList&zskipListNode
característica
- A estrutura contém diretamente os ponteiros de cabeça e cauda, comprimento e número máximo de camadas
- Cada nó contém 1 ponteiro de volta
- Cada nó gera ponteiros de avanço de alcance [1, 32] com base na lei de potência
- Os membros do nó são classificados por pontuação
- Cada objeto membro do nó é único
eficácia
Home——dick&dictt&dictEntry
característica
- Algoritmo de hash usando murmursh2/3 . Depois de calcular o valor de hash,
&sizemask
determine a localização do par chave-valor. - Resolver conflitos usando lista encadeada individualmente
- Quando o fator de carga for maior que 1 ou 0,5 (gatilho RDB), ele se expandirá e repetirá, e quando for menor que 0,1, reduzirá o espaço e repetirá
- O dict armazena dois ponteiros de tabela de hash dicttht[0] e dicttht[1] em uma matriz, que são usados para executar rehash em escala de cinza
- Durante o processo de rehash, o dictht[0] de dados antigo apenas diminui, mas não aumenta , e o processo de migração específico é dividido em cada processo CRUD. Contar o progresso atual do rehash por r ehashidx
- Durante o rehash, a operação RUD acessará duas tabelas sucessivamente
eficácia
- Excluir um par chave-valor específico (Excluir) é O(1), liberar todos os pares chave-valor é O(n)
- As operações CRUD são O(1)
- O retorno aleatório de pares chave-valor também é O(1)
- redis/src/dict.h
- redis/src/dict.c
- Família de funções MurmurHash e conjunto de testes usados por redis: aappleby/smhasher
Lista comprimida - zipList
característica
- é um espaço de armazenamento contínuo
- armazenar o deslocamento do nó final
- Os primeiros 4 bytes registram o número total de bytes ocupados por toda a ziplist
- Marque o final do zipList com 0xFF
para cada nó
- Os primeiros 1 ou 5 bytes registram o comprimento do byte do predecessor
- Quando o precursor tem menos de 254 bytes, o primeiro byte do nó atual armazena o comprimento do precursor. Caso contrário, o primeiro byte é 0xFE e o comprimento do precursor é registrado nos 4 bytes seguintes
eficácia
- As operações relacionadas ao predecessor/sucessor são O(1)
- calcule a posição do predecessor/sucessor com base no deslocamento - Obtendo o número total de bytes ocupados por zipList é O(1)
—— armazenado nos primeiros 4 bytes de zipList - O melhor caso para obter o número total de nós em zipList é O(1), e o pior caso é O(n)
—— o número de nós é maior que 65535 - Melhor caso O(n) para pesquisa - número inteiro, pior caso O(n^2) - matriz de bytes
- O melhor caso para inserir, criar, excluir é O(n), o pior caso é O(n^2)
- pode acionar atualizações encadeadas
Conjunto inteiro - intset
característica
- Armazene diretamente em uma tabela linear, possivelmente int16[], int32[], int64[]
- Quando um novo inteiro exceder o intervalo representado pelo tipo atual, uma operação de atualização será acionada. Novos elementos estarão apenas na cabeça (muito pequena) ou na cauda (muito grande)
- Não há operação de downgrade (observe que é diferente da implementação do dict, quando o fator de carga do dict for menor que 0,1, a contração e o rehash serão acionados)
eficácia
- Adicionar ou remover novos elementos é O(n)
- O processo de busca de uma matriz inteira pode ser pesquisado ao meio, O(log n)
- Obter o número de bytes e o número de elementos é O(1), que é diferente de zipList
objeto de superfície
O artigo anterior apresentou a principal estrutura de dados do Redis, mas na implementação específica. Para String, List, Set, ZSet e HashMap são na verdade objetos Redis. Redis define a estrutura RedisObject, que contém um void *ptr
usado para apontar para uma estrutura de dados específica. Sob diferentes condições, o mesmo objeto também pode ser armazenado e gerenciado com diferentes estruturas de dados (ou seja, as variáveis de membro ptr do mesmo RedisObject apontam para diferentes estruturas de dados). A seguir, uma breve visão geral dos diferentes RedisObjects.
Definição de RedisObject:
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS;
int refcount;
void *ptr;
} robj;
Corda
doença | método de armazenamento |
---|---|
<= 32Bytes | embStr |
> 32Bytes | SDS |
Inteiro, pode ser representado por longo | int(longo) |
- Números de ponto flutuante também são armazenados com embStr ou SDS
INCRBYFLOAT
As operações no tipo int acionarão a conversão de tipo
O chamado embStr é usar um espaço de armazenamento contínuo para armazenar estruturas redisObject e SDS ao mesmo tempo. A principal diferença com o SDS é: o SDS precisa executar duas solicitações de memória, enquanto o embStr precisa apenas de um .
Lista
doença | método de armazenamento |
---|---|
O comprimento de todos os elementos < list-max-ziplist-value(Byte) && o número de elementos < list-max-ziplist-entries(Byte) |
zipList |
outro | listaligada |
Definir
doença | método de armazenamento |
---|---|
Todos os elementos podem ser representados por long int && número de elementos < set-max-intset-entries(Byte) |
intset |
outro | ditado |
ZSet
doença | método de armazenamento |
---|---|
Comprimento de todos os elementos < zset-max-ziplist-value && número de elementos < zset-max-ziplist-entries (Byte) |
zipList |
outro | skipList + dict |
Quando houver uma grande quantidade de dados, use duas estruturas diferentes skipList e dict para armazenar os mesmos dados. É benéfico manter a eficiência da busca em O(1), enquanto ordena operações relacionadas (ZRANGE, ZRANK) mantenha O(n)
HashMap
doença | método de armazenamento |
---|---|
Comprimento de todos os elementos < zset-max-ziplist-value && número de elementos < zset-max-ziplist-entries (Byte) |
zipList |
outro | ditado |
- Em zipList, a chave também é um elemento de zipList, e o elemento onde a chave está localizada e o elemento onde o valor está localizado estão sempre próximos
- Tanto a chave quanto o valor são armazenados como StringObject. Para obter as características de armazenamento de StringObject, consulte a descrição do objeto String neste artigo.
polimorfismo
O polimorfismo do Redis é implementado com base na verificação de tipo e na verificação do método de codificação. Permite que o mesmo comando execute operações correspondentes em diferentes objetos Redis e métodos de codificação
- O Redis usa a verificação de tipo para determinar se um comando pode ser executado para uma chave especificada.
Por exemplo,DEL
pode ser executado para todos os tipos, masGET
apenas para String encoding
O Redis avalia como executar uma operação específica em um objeto Redis verificando o método de codificação (como um objeto HashMap, cuja estrutura contémencoding
variáveis de membro, indicandozipList
sedict
recuperação de memória
O GC do Redis é basicamente o mesmo que a ideia central do GC do PHP:
- Adicionar variáveis
refcount
de membro - O novo objeto tem uma contagem de referência de 1
- Adicionado +1 para beber, não mais -1 para ser usado
- Solte imediatamente quando 0
A função de reciclagem refere-se ao método de redis/src/object.c
decrRefCount
Mas, além disso, o Redis também gerencia os objetos do Redis por meio do LRU. Você pode ver que a estrutura RedisObject definida acima é unsigned lru:LRU_BITS;
usada para registrar o último horário de acesso. Quando a memória usada pelo Redis atinge maxmemory. O lru menor (quanto mais tempo não for usado) será lançado primeiro.
Isso nos lembra que, para evitar a perda de dados, além de garantir o funcionamento normal do nível de hardware do servidor Redis, as seguintes medidas devem ser tomadas:
- Certifique-se de que o espaço de armazenamento não esteja cheio - fora do horário de pico, é mais apropriado manter o uso de memória do Redis abaixo de 30%.
- Para os dados que precisam ser persistidos, a operação de persistência deve ser executada no tempo e deve haver mecanismos de log, monitoramento e alarme correspondentes para evitar a perda de dados não persistentes
- Balanceamento de carga - pode ser tratado com Redis-Cluster ou outro middleware