Pergunta obrigatória para candidatos ouro, prata e prata: O Redis é realmente single-thread?



foto

Pensamentos desencadeados pela pergunta da entrevista "O Redis é single-threaded?"
Autor: Li Le
Fonte: IT Reading Ranking

Muitas pessoas se depararam com essa pergunta de entrevista: O Redis é de thread único ou multithread? Esta questão é simples e complexa. Diz-se que é simples porque a maioria das pessoas sabe que o Redis é de thread único e é considerado complexo porque a resposta é, na verdade, imprecisa.

O Redis não é de thread único? Iniciamos uma instância do Redis e a verificamos. O método de instalação e implantação do Redis é o seguinte:

// 下载
wget https://download.redis.io/redis-stable.tar.gz
tar -xzvf redis-stable.tar.gz
// 编译安装
cd redis-stable
make
// 验证是否安装成功
./src/redis-server -v
Redis server v=7.2.4

Em seguida, inicie a instância do Redis e use o comando ps para visualizar todos os threads, conforme mostrado abaixo:

// 启动Redis实例
./src/redis-server ./redis.conf

// 查看实例进程ID
ps aux | grep redis
root     385806  0.0  0.0 245472 11200 pts/2    Sl+  17:32   0:00 ./src/redis-server 127.0.0.1:6379

// 查看所有线程
ps -L -p 385806
   PID    LWP TTY          TIME CMD
385806 385806 pts/2    00:00:00 redis-server
385806 385809 pts/2    00:00:00 bio_close_file
385806 385810 pts/2    00:00:00 bio_aof
385806 385811 pts/2    00:00:00 bio_lazy_free
385806 385812 pts/2    00:00:00 jemalloc_bg_thd
385806 385813 pts/2    00:00:00 jemalloc_bg_thd

Na verdade, existem 6 tópicos! Não é dito que o Redis é single-threaded? Por que existem tantos tópicos?

Você pode não entender o significado desses seis threads, mas este exemplo pelo menos mostra que o Redis não é de thread único.

01 Multithreading em Redis

A seguir, apresentamos as funções dos 6 threads acima, um por um:

1) servidor redis:

O thread principal é usado para receber e processar solicitações de clientes.

2)jemalloc_bg_thd

jemalloc é uma nova geração de alocador de memória, usado pela camada inferior do Redis para gerenciar a memória.

3)bio_xxx:

Aqueles que começam com o prefixo bio são todos threads assíncronos, usados ​​para executar algumas tarefas demoradas de forma assíncrona. Entre eles, o thread bio_close_file é usado para excluir arquivos de forma assíncrona, o thread bio_aof é usado para liberar arquivos AOF de forma assíncrona para o disco e o thread bio_lazy_free é usado para excluir dados de forma assíncrona (exclusão lenta).

Deve-se observar que o thread principal distribui tarefas para threads assíncronos por meio da fila, e esta operação requer bloqueio. O relacionamento entre o thread principal e o thread assíncrono é mostrado na figura abaixo:

foto

Thread principal e thread assíncrona Thread principal e thread assíncronaThread principal e thread assíncrono
Aqui tomamos a exclusão lenta como exemplo para explicar por que o thread assíncrono deve ser usado. Redis é um banco de dados na memória que oferece suporte a vários tipos de dados, incluindo strings, listas, tabelas hash, conjuntos, etc. Pense bem, qual é o processo de exclusão de dados do tipo lista (DEL)? A primeira etapa é excluir o par chave-valor do dicionário do banco de dados e a segunda etapa é percorrer e excluir todos os elementos da lista (liberando memória). Pense no que aconteceria se o número de elementos da lista fosse muito grande? Esta etapa consumirá muito tempo. Este método de exclusão é chamado de exclusão síncrona e o processo é mostrado na figura abaixo:

foto

Fluxograma de exclusão síncrona Fluxograma de exclusão síncronaFluxograma de exclusão síncrona
Em resposta aos problemas acima, o Redis propôs a exclusão lenta (exclusão assíncrona).Quando o thread principal recebe o comando de exclusão (UNLINK), ele primeiro exclui o par chave-valor do dicionário do banco de dados e depois distribui a exclusão tarefa para o thread assíncrono.bio_lazy_free, a segunda etapa da lógica demorada é executada pelo thread assíncrono. O processo neste momento é mostrado abaixo:

foto

Fluxograma de exclusão lenta Fluxograma de exclusão lentaFluxograma de exclusão lenta

02 E/S multithreading

O Redis é multithread? Então, por que sempre dizemos que o Redis é single-threaded? Isso ocorre porque a leitura de solicitações de comando do cliente, a execução de comandos e o retorno de resultados ao cliente são concluídos no thread principal. Caso contrário, se vários threads operarem o banco de dados na memória ao mesmo tempo, como resolver o problema de simultaneidade? Se um bloqueio é bloqueado antes de cada operação, qual é a diferença entre ele e um único thread?

Claro, esse processo também mudou na versão Redis 6.0. Os funcionários do Redis apontaram que o Redis é um banco de dados de valores-chave baseado em memória. O processo de execução de comandos é muito rápido. Ele lê a solicitação de comando do cliente e retorna os resultados para o cliente (ou seja, E/S de rede) geralmente se torna o gargalo de desempenho do Redis.

Portanto, na versão Redis 6.0, o autor adicionou a capacidade de E/S multithread, ou seja, vários threads de E/S podem ser abertos, as solicitações de comando do cliente podem ser lidas em paralelo e os resultados podem ser retornados ao cliente em paralelo. A capacidade multithreading de E/S pelo menos dobra o desempenho do Redis.

Para ativar o recurso de E/S multithread, você precisa primeiro modificar o arquivo de configuração redis.conf:

io-threads-do-reads yes
io-threads 4

Os significados dessas duas configurações são os seguintes:

  • io-threads-do-reads: Para habilitar o recurso de E/S multithread, o padrão é "não";

  • io-threads: O número de threads de E/S, o padrão é 1, ou seja, apenas o thread principal é usado para realizar E/S de rede, e o número máximo de threads é 128; esta configuração deve ser definida de acordo com o número de núcleos de CPU. O autor recomenda definir 2 ~ 3 para threads de E/S de CPU de 4 núcleos, CPU de 8 núcleos define 6 threads de E/S.

Depois de ativar o recurso de E/S multithread, reinicie a instância do Redis e visualize todos os threads. Os resultados são os seguintes:

ps -L -p 104648
   PID    LWP TTY          TIME CMD
104648 104648 pts/1    00:00:00 redis-server
104648 104654 pts/1    00:00:00 io_thd_1
104648 104655 pts/1    00:00:00 io_thd_2
104648 104656 pts/1    00:00:00 io_thd_3
……

Como definimos io-threads como 4, 4 threads serão criados para realizar operações de E/S (incluindo o thread principal).Os resultados acima estão de acordo com as expectativas.

Claro, apenas a fase de E/S usa multithreading, e o processamento de solicitações de comando ainda é de thread único. Afinal, existem problemas de simultaneidade em operações multithread de dados de memória.

Finalmente, após a habilitação do multithreading de E/S, o fluxo de execução do comando é mostrado abaixo:

foto

Fluxograma multithread de E/S Fluxograma multithread de E/SFluxograma multithreading de E / S

03 Multiprocesso no Redis

O Redis tem vários processos? Sim. Em alguns cenários, o Redis também criará vários subprocessos para executar algumas tarefas. Tomando a persistência como exemplo, o Redis oferece suporte a dois tipos de persistência:

  • AOF (Append Only File): Pode ser considerado um arquivo de log de comandos. O Redis anexará cada comando de gravação ao arquivo AOF.

  • RDB (Banco de Dados Redis): armazena dados na memória Redis na forma de snapshots. O comando SAVE é usado para acionar manualmente a persistência RDB. Pense nisso se a quantidade de dados no Redis for muito grande, a operação de persistência deve demorar muito e o Redis processa solicitações de comando em um único thread, então quando o tempo de execução do comando SAVE for muito longo, isso inevitavelmente afetará a execução de outros comandos.

O comando SAVE pode bloquear outras solicitações, por isso o Redis introduziu o comando BGSAVE, que criará um subprocesso para realizar operações de persistência, de forma que não afete o processo principal na execução de outras solicitações.

Podemos executar manualmente o comando BGSAVE para verificar. Primeiro, use o GDB para rastrear o processo Redis, adicionar pontos de interrupção e permitir que o processo filho seja bloqueado na lógica de persistência. Do seguinte modo:

// 查询Redis进程ID
ps aux | grep redis
root     448144  0.1  0.0 270060 11520 pts/1    tl+  17:00   0:00 ./src/redis-server 127.0.0.1:6379

// GDB跟踪进程
gdb -p 448144

// 跟踪创建的子进程(默认GDB只跟踪主进程,需手动设置)
(gdb) set follow-fork-mode child
// 函数rdbSaveDb用于持久化数据快照
(gdb) b rdbSaveDb
Breakpoint 1 at 0x541a10: file rdb.c, line 1300.
(gdb) c

Após definir o ponto de interrupção, use o cliente Redis para enviar o comando BGSAVE. Os resultados são os seguintes:

// 请求立即返回
127.0.0.1:6379> bgsave
Background saving started

// GDB输出以下信息
[New process 452541]
Breakpoint 1, rdbSaveDb (...) at rdb.c:1300

Como você pode ver, o GDB está rastreando o processo filho e o ID do processo é 452541. Você também pode visualizar todos os processos através do comando ps do Linux. Os resultados são os seguintes:

ps aux | grep redis
root     448144  0.0  0.0 270060 11520 pts/1    Sl+  17:00   0:00 ./src/redis-server 127.0.0.1:6379
root     452541  0.0  0.0 270064 11412 pts/1    t+   17:19   0:00 redis-rdb-bgsave 127.0.0.1:6379

Você pode ver que o nome do processo filho é redis-rdb-bgsave, o que significa que esse processo persiste em instantâneos de todos os dados em arquivos RDB.

Finalmente, considere duas questões.

  • Pergunta 1: Por que usar subprocesso em vez de subthread?

Como o RDB armazena instantâneos de dados de forma persistente, se subthreads forem usados, o thread principal e os subthreads compartilharão dados de memória. O thread principal também modificará os dados de memória enquanto persiste, o que pode levar à inconsistência de dados. Os dados de memória do processo principal e do processo filho estão completamente isolados e esse problema não existe.

  • Pergunta 2: Suponha que 10 GB de dados sejam armazenados na memória Redis. Depois de criar um processo filho para realizar operações de persistência, o processo filho também precisa de 10 GB de memória neste momento? Copiar 10 GB de dados de memória consumirá muito tempo, certo? Além disso, se o sistema tiver apenas 15 GB de memória, o comando BGSAVE ainda poderá ser executado?

Existe um conceito aqui chamado cópia na gravação. Depois de usar a chamada do sistema fork para criar um processo filho, os dados de memória do processo principal e do processo filho são temporariamente compartilhados, mas quando o processo principal precisa modificar os dados da memória, o o sistema fará automaticamente uma cópia deste bloco de memória para obter o isolamento dos dados da memória.

O fluxo de execução do comando BGSAVE é mostrado na figura abaixo:

foto

Processo de execução BGSAVE Processo de execução BGSAVEProcesso de execução BGS A V E

04 Conclusão

O modelo de processo/modelo de threading do Redis ainda é relativamente complexo. Aqui apresentamos apenas brevemente multi-threading e multiprocessamento em alguns cenários. Multi-threading e multiprocessamento em outros cenários ainda precisam ser estudados pelos próprios leitores.

Sobre o autor
Li Le: especialista em desenvolvimento de Golang na TAL e mestre pela Universidade de Ciência e Tecnologia Eletrônica de Xi'an. Ele já trabalhou para Didi. Ele está disposto a se aprofundar em tecnologia e código-fonte. Ele é coautor de "Usando Redis com eficiência: aprenda armazenamento de dados e clusters de alta disponibilidade em um livro" e "Redis5" Design e análise de código-fonte "" Design subjacente do Nginx e análise de código-fonte ".

▼Leitura estendida

Insira a descrição da imagem aqui

"Usando o Redis com eficiência: aprenda sobre armazenamento de dados e clusters de alta disponibilidade em um livro" "Usando o Redis com eficiência: aprenda sobre armazenamento de dados e clusters de alta disponibilidade em um livro""Usando Redis com eficiência : aprenda armazenamento de dados e clusters de alta disponibilidade em um só livro "

Palavras recomendadas: Aprofunde-se na estrutura de dados do Redis e na implementação subjacente e supere os problemas de armazenamento de dados do Redis e gerenciamento de cluster.

Acho que você gosta

Origin blog.csdn.net/qq_32682301/article/details/136363807
Recomendado
Clasificación