Uma análise detalhada do mecanismo de reequilíbrio kafka

prefácio

1. Visão geral do rebalanceamento de filas

Se você sabe alguma coisa sobre RocketMQ ou middleware de mensagens, o consumidor precisa pelo menos realizar carregamento de fila (partição) ao consumir mensagens, ou seja, como vários consumidores em um grupo de consumidores se inscrevem no tópico na fila. ser rebalanceado automaticamente quando os consumidores são adicionados ou diminuídos, e a fila é aumentada ou diminuída, de forma que o aplicativo não esteja ciente, determina diretamente a escalabilidade do programa. A ilustração é a seguinte:

Esta seção irá reunir o mecanismo de rebalanceamento dos consumidores Kafka . .

2. Processo básico do consumidor Kafka

Antes de apresentar o mecanismo de reequilíbrio do lado do consumidor kafka, vamos primeiro examinar brevemente o processo de recebimento de mensagens pelos consumidores. De todo o processo, podemos ver o momento de disparo do reequilíbrio e seu importante papel em todo o processo de consumo. breve processo é mostrado na figura a seguir:


Os principais pontos-chave são os seguintes:

  • Determine se o objeto KafkaConsumer está em um ambiente multithread. Nota: Este objeto não é multi-thread safe, não pode haver mais de um thread segurando o objeto.
  • Inicialização do grupo de consumidores, incluindo carregamento de fila (rebalanceamento)
  • puxar mensagem
  • Processamento do interceptor de consumo de mensagens

O núcleo do método de pesquisa nada mais é do que dois: reequilíbrio e consumo puxado. Este artigo se concentrará em analisar o mecanismo de reequilíbrio dos consumidores Kafka.

3. Mecanismo de carregamento da fila do consumidor (rebalanceamento)

Através da análise do código-fonte do método updateAssignmentMetadataIfNeeded, o método central finalmente chamado é o método poll do ConsumerCoordinator. O fluxograma central é o seguinte:

Os pontos-chave do processo central do coordenador do consumidor:

  • Coordenador de consumo em busca de coordenador de grupo
  • Carga da fila (rebalancear)
  • site de submissão

Este artigo se aprofundará no mecanismo de reequilíbrio de Kafka.

Antes de se aprofundar no mecanismo de reequilíbrio kafka, pense brevemente nas seguintes perguntas:

  • O rebalanceamento bloqueará o consumo de mensagens?
  • Quais mudanças no protocolo de grupo de ingresso de Kafka podem reduzir efetivamente o reequilíbrio

Manutenção do pensamento arquitetônico: Para a segunda pergunta, como uma pessoa que interpreta Kafka do nível do código-fonte, pensar profundamente sobre seus princípios internos é uma qualidade necessária para os arquitetos.

3.1 Coordenador do Consumidor

No Kafka, cada consumidor do lado do cliente corresponde a um coordenador de consumidor (ConsumerCoordinator), e cada corretor do lado do servidor inicia um coordenador de grupo.

Em seguida, o processo será rastreado em nível de origem.De acordo com o mecanismo de trabalho de extração de código-fonte, esta parte corresponde ao método assuranceCoordinatorReady no fluxograma acima.

Os pontos principais deste método são os seguintes:

  • Primeiro, julgue se o consumidor atual encontrou o coordenador do grupo no lado do corretor e retorne true se o detectou.
  • Se não houver atualmente nenhum coordenador de grupo ciente, procure o coordenador de grupo do grupo de consumidores do servidor (broker).
  • O processo de localização do coordenador do grupo é um processo síncrono e, se ocorrer uma exceção, uma nova tentativa é acionada, mas um mecanismo de intervalo de repetição é introduzido.
  • Se não expirar e o coordenador do grupo não for adquirido, tente novamente (faça enquanto).

O ponto principal é o método lookupCoordinator. O núcleo deste método é selecionar um broker com a menor carga, construir uma solicitação e consultar o broker para o coordenador do grupo de consumidores. O código é o seguinte:

Consultar a solicitação de o coordenador do grupo. Os parâmetros principais são:

  • ApiKeys apiKey

A API de requisição é análoga ao RequestCode do RocketMQ, de acordo com este valor, é fácil encontrar o código de processamento correspondente do servidor, aqui é ApiKeys.FIND_COORDINATOR.

  • Coordenador de stringKey

Chave do coordenador, cancele o nome do grupo.

Pergunta pensando: spoiler antecipadamente: cada broker no servidor Kafka criará um coordenador de grupo (GroupCoordinator), cada coordenador de grupo pode coordenar vários grupos de consumidores, mas um grupo de consumidores será atribuído apenas a um coordenador de grupo, qual é o mecanismo de carregamento aqui ? Como muitos corretores no lado do servidor competem pelo controle do grupo de consumidores?

  • coordenadorType O tipo de coordenador, o padrão é GROUP, que representa um grupo de consumidores comum.
  • Versão minVersion curta.

Para solicitações de clientes, a entrada unificada do servidor é KafkaApis.scala, e sua entrada de processamento pode ser encontrada rapidamente de acordo com ApiKeys, conforme mostrado na figura:

A lógica de processamento específica está no handleFindCoordinatorRequest de KafkaApis, conforme mostrado na figura a seguir : O

servidor aloca o grupo de consumidores O algoritmo central do coordenador é realmente muito simples: de acordo com o nome do grupo de consumidores, pegue o hashcode e, em seguida, pegue o módulo com o número de partições no tópico interno do kafka (__consumer_offsets) e em seguida, retorne o broker físico onde a partição está localizada como coordenador de agrupamento do grupo de consumidores, ou seja Não há nenhum mecanismo de eleição complexo dentro, o que pode explicar melhor o motivo pelo qual o cliente pode selecionar o broker com a menor carga para consulta ao enviar um pedido.

Após o cliente receber o resultado da resposta, ele atualiza a propriedade (Coordenador do nó) do ConsumerCoordinator, então chamar o método CoordinatorUnknown() novamente retornará false, completando assim a busca pelo coordenador do consumidor.

3.2 Análise de Processos de Consumidores Ingressando em Grupos de Consumidores

Após o consumidor obter o coordenador, de acordo com o processo de processamento do coordenador mencionado acima, o consumidor precisa ingressar no grupo de consumidores. Entrar no grupo de consumidores também é um pré-requisito para participar do mecanismo de carregamento da fila. Em seguida, partiremos do código-fonte Analisar o processo de adição de um grupo de consumidores a um grupo de consumidores, correspondente ao método EnsureActiveGroup de AbstractCoordinator acima.

Os principais pontos-chave deste método:

  • Antes de ingressar em um grupo de consumidores, você deve garantir que o consumidor esteja ciente do coordenador do grupo.
  • Iniciar o thread de heartbeat.Quando o consumidor estiver em MemberState.STABLE após ingressar no grupo de consumidores, ele precisa reportar o heartbeat ao coordenador regularmente, indicando que está ativo, caso contrário será removido do grupo de consumidores.
  • Junte-se a um grupo de consumidores.

O segmento de pulsação será apresentado em detalhes posteriormente. Primeiro, acompanhe o processo principal de ingressar em um grupo de consumidores. O método de implementação específico é joinGroupIfneeded. Em seguida, o método será explicado passo a passo.
![](https://img-blog.csdnimg.cn/img_convert/5818099a47b126056adc203a06f7b6b7.png

Passo 1: Antes de ingressar em um grupo de consumidores, você deve obter o coordenador do grupo correspondente, pois todas as solicitações subsequentes precisam ser enviadas ao coordenador do grupo.

![](https://img-blog.csdnimg.cn/img_convert/e39bfb92866a23a5abfa0ca59a54a1be.png

Passo 2: Chame sua função de retorno de chamada antes que cada rebalanceamento seja executado. Podemos observar a implementação de ConsumerCoordinatory. O código é mostrado na figura a seguir:


O coordenador do consumidor normalmente faz o seguinte antes de rebalancear (ingressar em um novo grupo):

  • Se o envio automático do site estiver ativado, faça um envio do site.
  • Execute ouvintes de eventos relacionados ao rebalanceamento.

AbstractCoordinator#joinGroupIfneeded

Há dois lugares que vale a pena prestar atenção aqui:

  • Envie uma solicitação de ingresso para o coordenador do grupo de consumidores, mas ingressar no grupo de consumidores não é um fim, mas um meio. O objetivo final é equilibrar a carga da fila.
  • Chame o método onJoinComplete para notificar o coordenador do consumidor sobre o resultado final do carregamento da fila, que podemos conhecer a partir de seus parâmetros:
  • String GenerationId
  • String memberId ID do membro
  • String protocolo nome do protocolo, aqui é consumidor.
  • ByteBuffer memberAssignment

O resultado do carregamento da fila inclui as informações da fila alocadas ao consumidor atual. O resultado após a sequência é mostrado na figura:

![](https://img-blog.csdnimg.cn/img_convert/785e75f36c601362d22ca2007a87cee8.png

Portanto, o mecanismo de carregamento da fila está contido na solicitação de construção.A seguir, analisaremos detalhadamente o processo de interação entre o cliente e o servidor.

3.2.1 Crie uma solicitação para ingressar em um grupo de consumidores

Consulte sendJoinGroupRequest do AbstractCoordinator para obter o código para construir e ingressar em um grupo de consumidores. O código é o seguinte: Para

iniciar uma solicitação de ingresso em grupo, o corpo da solicitação contém principalmente as seguintes informações:

  • nome do grupo de consumidores
  • tempo limite da sessão, tempo limite da sessão, o padrão é 10s
  • memberId é o id do membro do grupo de consumidores. Na primeira vez que for nulo, o servidor subsequente atribuirá um id exclusivo ao consumidor, que é composto de id do cliente + uuid.
  • protocolType tipo de protocolo, o consumidor se junta ao grupo de consumidores e é fixado como consumidor
  • Todos os algoritmos de carregamento de filas suportados pelo consumidor

Após receber a resposta do servidor, JoinGroupResponseHandler será chamado para devolvê-la, o que será descrito em detalhes posteriormente.

3.2.2 Lógica de Resposta do Servidor

Entrada de processamento do lado do servidor: método handleJoinGroupRequest de KafkaApis, que é delegado ao GroupCoordinator.

Através desta entrada, você pode ver basicamente os pontos-chave para o servidor processar a solicitação de junção:

  • Extraia o memberId do cliente da solicitação do cliente. Se estiver vazio, significa que é a primeira vez que você participa do grupo de consumidores e o memberId não foi atribuído.
  • Se as informações do grupo de consumidores não existirem no coordenador, significa que é a primeira vez para ingressar, criar um e executar doUnknownJoinGroup (a lógica de ingressar no grupo de consumidores pela primeira vez)
  • Se a informação do grupo de consumidores já existir no coordenador, julgue se o número máximo de consumidores foi atingido (o padrão não é limitado), se exceder, uma exceção será lançada; e então o processamento lógico correspondente será realizado de acordo se o consumidor aderir pela primeira vez.

O coordenador do grupo manterá os metadados do grupo (GroupMetadata) para cada grupo de consumidores roteado, que é armazenado em HashMap<String, GroupMetadata>. Cada informação de nuvem do grupo de consumidores armazena todos os consumidores atuais, e os consumidores tomam a iniciativa Junte-se, o coordenador do grupo pode ativamente abater consumidores.

Em seguida, vamos lidar com a situação, e vamos dar uma olhada no primeiro join (doUnknownJoinGroup) e rejoin (doJoinGroup) em detalhes.

3.2.2.1 Entrando em um grupo de consumidores pela primeira vez

O código para ingressar no grupo de consumidores pela primeira vez é o seguinte: Os

pontos principais são os seguintes:

  • Primeiro, vamos dar uma olhada no significado dos parâmetros deste método:
  • Grupo GroupMetadata: As metainformações do grupo de consumidores, que não são persistentes e armazenadas na memória, são as informações do consumidor atual de um grupo de consumidores.
  • boolean requireKnownMemberId: Se é necessário saber o ID do cliente, se a versão da solicitação do cliente for 4, o memberId da outra parte precisa ser claramente conhecido ao ingressar no grupo de consumidores.
  • String clientId: ID do cliente, a regra de geração memberId do grupo de mensagens é clientId + uuid
  • String clientHost: o endereço IP do consumidor
  • int rebalanceTimeoutMs: Tempo limite de rebalanceamento, obtido do parâmetro do consumidor max.poll.interval.ms, o padrão é 5 minutos.
  • int sessionTimeoutMs: tempo limite da sessão, o padrão é 10s
  • String protocolType: tipo de protocolo, o padrão é consumidor
  • Listar protocolos: Os algoritmos de carregamento de fila suportados pelo cliente.
  • A verificação de status é realizada no cliente e a verificação é a seguinte:
  • Se o status do consumidor estiver morto, retorne UNKNOWN_MEMBER_ID
  • Se o protocolo do algoritmo de carregamento do grupo de consumidores atual não suportar o novo protocolo de carregamento da fila do cliente, UNKNOWN_MEMBER_ID será lançado e um protocolo de carregamento de fila inconsistente será solicitado.
  • A versão 4 da solicitação de ingresso do Kafka usa um ID de membro do cliente claro ao ingressar em um grupo de consumidores. O grupo de consumidores adiciona o memberId criado ao membro pendente do grupo e retorna MEMBER_ID_REQUIRED ao cliente para orientar o cliente a ingressar novamente. O cliente usará O memberId gerado pelo servidor é reiniciado para ingressar no grupo de consumidores.
  • Chame o método addMemberAndRebalance para ingressar no grupo de consumidores e acionar o rebalanceamento.

Em seguida, continue a explorar a lógica específica de adicionar um grupo de consumidores e acionar o rebalanceamento.Para a implementação específica, consulte addMemberAndRebalance of GroupCoordinator.

Os pontos centrais são os seguintes:

  • O coordenador do grupo cria um objeto MemberMetadata para cada consumidor.
  • Se o estado do grupo de consumidores for PreparandoRebalance (este estado indica que está aguardando a entrada do grupo de consumidores) e o newMemberAdded do grupo estiver definido como verdadeiro, significa que um novo membro ingressou e o rebalanceamento subsequente precisa ser ser acionado. E adicionar o grupo de consumidores ao grupo, aqui vai acionar uma seleção de grupo de consumidores, a lógica de seleção principal: o primeiro consumidor a entrar no grupo de consumidores se torna o líder no grupo de consumidores, qual a responsabilidade do líder?

  • Crie um objeto DelayedHeartbeat para cada consumidor para detectar o tempo limite da sessão. Se o coordenador do grupo detectar um tempo limite da sessão, ele removerá o consumidor do grupo e acionará novamente o rebalanceamento. Para evitar que o consumidor seja removido pelo grupo coordenador do grupo, você precisa enviar pacotes de pulsação em intervalos.
  • Se o rebalanceamento é necessário de acordo com o estado atual do grupo de consumidores.

Em seguida, continue a rastrear o método mayPrepareRebalance em profundidade. Sua implementação é mostrada na figura a seguir:

De acordo com as regras de condução da máquina de estado, ela é julgada se ela pode entrar em PrepareRebalance. A lógica de julgamento é julgar se o estado atual pode entrar neste estado de acordo com o drive da máquina de estado.A implementação específica é armazenar um conjunto de estado precursor que pode entrar no estado atual para cada estado.

Se estiver em conformidade com o processo orientado por estado, o grupo de consumidores entrará em PrepareRebalance e sua implementação específica é mostrada na figura a seguir:

  • Se o status do grupo de consumidores atual for CompletingRebalance, a ação de alocação de fila precisará ser redefinida e o grupo de consumidores se juntará novamente ao grupo de consumidores, ou seja, a solicitação JOIN_GROUP será reenviada. Habilidades específicas de implementação:
  • Esvazie as informações da fila para as quais todos os consumidores foram alocados de acordo com o algoritmo de alocação
  • Retorne um resultado de alocação vazio para o consumidor e o código de erro é REBALANCE_IN_PROGRESS. O cliente voltará ao grupo de consumidores quando receber esse erro.
  • Se não houver um consumidor no momento, crie um InitialDelayedJoin, caso contrário crie um DelayedJoin. Vale a pena notar que há um parâmetro aqui: group.initial.rebalance.delay.ms, que é usado para definir o tempo de atraso do grupo de consumidores para entre no PreparandoRebalance para realmente executar sua lógica de negócios. , cujo principal objetivo é aguardar a entrada de mais consumidores.
  • O estado do grupo de consumidores do driver é PreparandoRebalance.
  • Tente executar o método tryComplete de initialDelayedJoin ou DelayedJoin. Caso não seja concluído, crie um watch, aguarde a conclusão da execução e, finalmente, execute os métodos relacionados do coordenador do grupo. A descrição é a seguinte:

    Em seguida, observe o Método tryCompleteJoin do coordenador do grupo, que é implementado da seguinte forma Conforme mostrado na figura:
  • A condição para concluir o estado de PreparaçãoRebalance é que todos os grupos de consumidores conhecidos sejam adicionados com êxito ao grupo de consumidores. Depois que o método retornar true, o método onCompleteJoin será executado.

Em seguida, vamos dar uma olhada na implementação do método onCompleteJoin do GroupCoordinator.

Os pontos principais do núcleo são os seguintes:

  • O estado do grupo de consumidores acionadores é convertido em CompletingRebalance, que entrará no segundo estágio de rebalanceamento (carga da fila)
  • Construa um objeto JoinResponse correspondente para cada membro, com três pontos-chave
  • generationId é incrementado em um para cada mudança de estado do grupo de consumidores
  • Algoritmo de carregamento de fila de subprotocolo suportado por todos os consumidores no grupo de consumidores atual
  • leaderId O líder do grupo de consumidores, o primeiro consumidor a ingressar em um grupo de consumidores é o líder

Em seguida, o coordenador do consumidor realizará o rebalanceamento do segundo estágio de acordo com o resultado da resposta retornado pelo servidor, ou seja, entrará no algoritmo de carregamento da fila.

O processo do servidor para o cliente ingressar no grupo de consumidores pela primeira vez é apresentado aqui. Antes de focar na resposta do cliente ao reequilíbrio, vejamos como o coordenador do grupo trata a associação de consumidores com a lógica de processamento de memberId conhecida.

3.2.2.2 A lógica de processamento de adicionar um memberId conhecido a um grupo de consumidores

O código de processamento principal para a coordenação do grupo processar a solicitação de junção com o memberid conhecido está no doJoinGroup do GroupCoordinator, ou seja, a solicitação de reingresso.

Etapa 1: Primeiro, execute a verificação de erros relacionada:

  • Se o status do grupo de consumidores for Dead, o erro unknown_member_id será retornado.
  • Se o algoritmo de carregamento de fila compatível com o consumidor atual não for compatível com o grupo de consumidores
  • Se o memberid atual estiver em pendantMember, o consumidor para esta reintegração aceitará e acionará um rebalanceamento.

Vale a pena notar que depois que a versão Kafka JOIN_REQUEST for 4, o memberId será gerado no lado do servidor e adicionado ao pendantMember, e o memberId será retornado ao cliente imediatamente para orientar o cliente a se juntar novamente.

  • Se o membro não existir no grupo de consumidores, um erro será retornado, indicando que o grupo de consumidores removeu o consumidor.
    Passo 2: Realize diferentes ações de acordo com o status do grupo de consumidores. Se o estado atual for PreparandoRebalance, atualize as metainformações do membro e acione o rebalanceamento conforme necessário.

No estado PreparandoRebalance, o grupo de consumidores está aguardando a adesão dos consumidores no grupo de consumidores.

Passo 3: Se o estado for CompletingRebalance, se a requisição do grupo de junção for recebida, mas sua meta informação não tiver mudado (algoritmo de carregamento da fila), basta retornar a informação mais recente para o consumidor; se o estado mudar, ele voltará para No primeira etapa do reequilíbrio, o grupo de consumidores se reúne.

Se o grupo de consumidores estiver no estado CompletingRebalance, ele não deseja receber mais solicitações de Join Group, porque o grupo de consumidores no estado CompletingRebalance está aguardando o líder do consumidor alocar uma fila.

Passo 4: Se o grupo de consumidores estiver no estado Estável, se o membro for o líder e o protocolo suportado mudar, o rebalanceamento é realizado, caso contrário, apenas as metainformações precisam ser enviadas ao cliente.

3.2.3 O cliente processa o pacote de resposta Join Group do coordenador do grupo

A resposta do cliente para Join_Group é processada em: JoinGroupResponseHandler, e sua implementação principal é a seguinte:

Ponto chave: O algoritmo de carregamento da fila é alocado pelo nó Líder e o resultado da alocação é enviado ao coordenador do grupo enviando uma solicitação SYNC_GROUP , e então o coordenador do grupo o obtém do nó Líder. Após o algoritmo de alocação, ele é devolvido a todos os consumidores para iniciar o consumo.

3.3 Batimentos cardíacos e partida

O consumidor entra no grupo de consumidores interagindo com o coordenador do grupo por meio do coordenador do consumidor, mas como sair? Por exemplo, quando o consumidor está em baixo, como o coordenador percebe isso?

Acontece que no Kafka, o coordenador do consumidor irá introduzir um mecanismo de heartbeat, ou seja, enviar um pacote de heartbeat ao coordenador do grupo regularmente. o tempo de expiração é definido pelo parâmetro session.timeout.ms , o padrão é 10s.

De acordo com o processo de poll do ConsumerCoordinator, após o coordenador do consumidor conhecer o coordenador do grupo do grupo de consumidores, ele iniciará a thread de pulsação. O código é o seguinte

: .

Os pontos principais do thread de pulsação são os seguintes:

  • Se a última pulsação exceder o tempo da sessão, o link com o GroupCoordinator será desconectado e definido como CoordinatorUnknow como true, e o coordenador do grupo precisará ser pesquisado novamente.
  • Se o tempo de envio de pulsação exceder pollTimeout desde o último tempo de envio de pulsação, o cliente enviará LEAVE_GROUP, sairá do grupo de consumidores e entrará novamente na operação de ingressar no grupo de consumidores quando o próximo método de sondagem for chamado, o que acionará o rebalanceamento novamente.
  • Se o tempo entre duas pulsações exceder o intervalo entre o envio de uma única pulsação, uma mensagem será enviada.

Lembrete: Embora o pacote heartbeat seja geralmente uma tarefa cronometrada, o mecanismo de heartbeat do Kafka tem uma abordagem diferente. Ele usa a espera e a notificação do Object. O thread de heartbeat e o thread de pull de mensagem auxiliam um ao outro. ser julgado se deve enviar um batimento cardíaco.

Em relação à saída do grupo de consumidores, a lógica de processamento do lado do servidor é relativamente simples, portanto não será apresentada aqui.

4. Resumo do mecanismo de reequilíbrio

O rebalanceamento de Kafka na verdade inclui dois estágios muito importantes: o estágio de ingresso do grupo de consumidores (PreparingRebalance) e o carregamento da fila (CompletingRebalance).

PreparandoRebalance: Nesta etapa, os consumidores ingressam no grupo de consumo um após o outro. O primeiro consumidor do grupo é eleito como líder. Quando todos os consumidores com memberId conhecido no grupo se juntam, o estado é direcionado para CompletingRebalance.

CompletingRebalance: Após a conclusão do estado de PreparaçãoRebalance, se o consumidor for eleito como líder, o líder usará o algoritmo de carregamento de fila suportado pelo grupo de consumidores para distribuir a fila e, em seguida, relatar o resultado ao coordenador do grupo; se a função do consumidor é não líder, o algoritmo de partição da fila de sincronização será enviado ao coordenador do grupo e o coordenador do grupo distribuirá os resultados da atribuição do nó líder aos consumidores.

Se o grupo de consumidores estiver rebalanceando, ele suspenderá o consumo de mensagens. O rebalanceamento frequente afetará muito a velocidade do consumo de mensagens da fila.

Parâmetros do lado do consumidor relacionados ao rebalanceamento:

  • max.poll.interval.ms

O intervalo máximo entre duas chamadas de método de sondagem, em milissegundos, e o padrão é 5 minutos. Caso o consumidor não inicie uma operação de poll dentro desse intervalo, o consumidor será eliminado, acionando o rebalanceamento e atribuindo a fila alocada pelo consumidor a outros consumidores.

  • sessão.tempo limite.ms

O tempo limite de pulsação entre o consumidor e o broker, o padrão é 10s. Se o broker não receber uma solicitação de heartbeat dentro do tempo especificado, o broker removerá o consumidor e acionará o rebalanceamento.

  • batimentos cardíacos.intervalo.ms

Intervalo de pulsação, o consumidor enviará pulsações para o broker nesta frequência, o padrão é 3s, principalmente para garantir que a sessão não irá falhar.

finalmente

Um conjunto completo de manuais de coleta de entrevistas Java: ajuste de desempenho + arquitetura de microsserviços + programação simultânea + estrutura de código aberto + distribuído" e outras sete colunas de entrevistas, incluindo Tomcat, JVM, MySQL, SpringCloud, SpringBoot, Dubbo, Concurrency, Spring, SpringMVC, MyBatis , Zookeeper, Ngnix, Kafka, MQ, Redis, MongoDB, memcached e muito mais.

Amigos necessitados podem seguir a conta oficial [Programa Yuan Xiaowan] para obtê-lo.

Acho que você gosta

Origin blog.csdn.net/m0_48795607/article/details/119679978
Recomendado
Clasificación