Meituan é muito detalhado: tempo de inatividade gracioso do microsserviço Springcloud, como alcançá-lo?

diga na frente

Muitas vezes me perguntam sobre o desligamento normal dos aplicativos Spring Boot e Spring Cloud. Esse também é um ponto que deve ser dominado no processo real do aplicativo.

Na comunidade de leitores (50+) de Nien, um arquiteto veterano de 40 anos , alguns amigos obtiveram recentemente qualificações para entrevistas para empresas de Internet de primeira linha, como Meituan, Pinduoduo, Jitu, Youzan e Xiyin. Perguntas muito importantes para entrevistas :

  • Como obter o desligamento normal dos microsserviços Springcloud nativos da nuvem?

Problemas semelhantes encontrados por outros pequenos parceiros incluem:

  • Como implementar o gracioso offline dos microsserviços Springcloud?
  • Como obter o gracioso offline do SpringBoot?
  • Etc etc…

Aqui, Nien lhe dará uma triagem sistemática e sistemática, para que você possa demonstrar totalmente seus fortes "músculos técnicos" e fazer o entrevistador amar "não consigo evitar, babando" .

Inclua também esta pergunta e respostas de referência em nossa versão V77 " Nin Java Interview Collection ", para referência de amigos posteriores, para melhorar a arquitetura, o design e o nível de desenvolvimento de 3 níveis de todos.

Para os arquivos PDF das últimas "Notas de Arquitetura Nin", "Trilogia Nin High Concurrency" e "Coleção de Entrevistas Nin Java", acesse a conta oficial [Technical Freedom Circle] no final do artigo.

Diretório de artigos

O que é Spring Cloud gracioso offline?

Os programas Java são executados na JVM e há muitas situações que podem travar repentinamente, como OOM, saída forçada pelo usuário e outros erros de negócios. . . Uma série de problemas pode fazer com que nosso processo trave.

Neste momento, temos necessidades de melhoria de serviço e atualizações de novas versões.

Se quisermos atualizar um determinado serviço, a premissa é desligar o serviço normalmente.

O que é Spring Cloud gracioso offline?

Então, nosso kill PID comumente usado é uma estratégia offline elegante?

claro que não.

Em primeiro lugar, off-line gracioso é o objetivo, não o meio, é um conceito relativo.

Embora kill PID não seja normal offline, e kill PID e kill -9 PID ambos matam serviços violentamente, em comparação com kill -9 PID, kill PID é normal.

Então, qual é exatamente o gracioso offline do Spring Cloud?

Inclui o seguinte:

  • Processar solicitações inacabadas, observe que novas solicitações não serão aceitas
  • Liberação de recursos agrupados: pool de conexão de banco de dados, pool de conexão HTTP
  • Liberação de thread de processamento: solicitar a liberação de thread de processamento
  • O modo off-line elegante da instância do microsserviço SpringCloud efetua logout ativamente do centro de registro para garantir que outros clientes RPC não façam chamadas RPC erradas

Então, como conseguir offline gracioso do Spring Cloud?

Para introduzir o mecanismo de implementação do gracioso offline do SpringCloud, devemos primeiro começar com o básico da saída elegante da JVM.

Saída elegante da JVM

O elegante mecanismo de saída da JVM é implementado principalmente por meio do Hook.

A JVM possui um mecanismo shutdwonHook, chamado de saída normal em chinês.

O Gancho de saída elegante da VM e algumas operações executadas antes de sair quando SIGTERM (kill -15 ou svc -d) é executado no sistema Linux.

Função de gancho para saída da JVM

O cenário de aplicativo da função de gancho para saída da JVM

Vamos primeiro examinar o cenário do aplicativo da função de gancho para a saída da JVM.

Nosso programa java é executado na JVM e há muitas situações que podem travar repentinamente, como:

  • OOM
  • Forçar logoff do usuário
  • Outros erros comerciais
  • espere

Uma série de problemas pode fazer com que nosso processo JVM seja interrompido.

A função hook da saída da JVM refere-se à execução automática do segmento de código especificado pelo usuário quando o processo da JVM está prestes a sair.

Esta função possui uma ampla variedade de cenários de aplicação, como:

  1. Liberação de recursos : Quando a JVM sai, alguns recursos precisam ser liberados, como fechar a conexão com o banco de dados, liberar o identificador de arquivo, etc. Você pode usar a função de gancho para executar essas operações automaticamente.
  2. Logging : Quando a JVM sai, algumas informações importantes podem ser registradas, como tempo de execução do programa, uso de memória, etc., para posterior análise de problemas.
  3. Persistência de dados : quando a JVM sai, alguns dados importantes podem ser persistidos no disco para que o estado possa ser restaurado na próxima vez que for iniciado.
  4. Saída segura : Quando a JVM sai, algumas operações de limpeza podem ser executadas, como excluir arquivos temporários, fechar conexões de rede, etc., para garantir a saída segura do programa.

Resumindo, a função hook pode executar algumas operações customizadas quando a JVM sai, para melhor gerenciar e controlar a execução do programa.

Dicas do arquiteto Nien, de 40 anos:

O desligamento normal do aplicativo é muito importante, e algumas operações de gravação e corretivas podem ser executadas antes do desligamento.

Pelo menos, podemos saber quando e por que nosso processo JVM foi encerrado de forma anormal.

Uso da função de gancho para saída da JVM

Em um programa java, adicionando uma função de gancho de desligamento, a função de fechar recursos e sair normalmente do programa pode ser realizada quando o programa é encerrado.

Como fazer isso?

É Runtime.addShutDownHook(Thread hook)alcançado principalmente por meio de.

Runtime.addShutdownHook(Thread hook)É um método em Java que registra um thread para executar operações de limpeza quando a JVM é encerrada.

Runtime.addShutdownHook(Thread hook)Cada chamada é para registrar um thread, o código de referência é o seguinte:

// 添加hook thread,重写其run方法
Runtime.getRuntime().addShutdownHook(new Thread(){
    
    
    @Override
    public void run() {
    
    
        System.out.println("this is hook demo...");
        // jvm 退出的钩子逻辑
    }
});

Runtime.addShutdownHook(Thread hook)Pode ser chamado várias vezes para registrar vários threads.

Quando a JVM está prestes a ser encerrada, esses encadeamentos são executados sequencialmente na ordem em que foram registrados para fazer alguma liberação de recurso, criação de log ou outras operações de limpeza.

Esse método pode ser usado no aplicativo para garantir que alguma limpeza necessária seja executada antes que o programa seja encerrado, como fechar a conexão com o banco de dados ou liberar o identificador de arquivo.

Vamos dar uma breve olhada em um Runtime.addShutDownHook(Thread hook)exemplo de uso

// 创建HookTest,我们通过main方法来模拟应用程序
public class HookTest {
    
    
 
    public static void main(String[] args) {
    
    
 
        // 添加hook thread,重写其run方法
        Runtime.getRuntime().addShutdownHook(new Thread(){
    
    
            @Override
            public void run() {
    
    
                System.out.println("this is hook demo...");
                // jvm 退出的钩子逻辑
            }
        });
 
        int i = 0;
        // 这里会报错,jvm回去执行hook thread
        int j = 10/i;
        System.out.println("j" + j);
    }
}

Após a execução, o resultado é o seguinte:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at hook.HookTest.main(HookTest.java:23)
this is hook demo...
 
Process finished with exit code 1

Resumo: Tomamos a iniciativa de escrever um programa de relatório de erros. Depois que o programa relatou um erro, a função de gancho ainda era executada.

Foi verificado que podemos concluir as consequências da saída adicionando uma função de gancho ao Runtime.

Runtime.addShutDownHook(Thread hook) aciona a cena

Como esse método fornecido pelo JDK pode registrar uma função de gancho para desligamento da JVM, em que circunstâncias essa função será chamada?

Acima mostramos que será chamado quando o programa estiver anormal.Existem outros cenários?

  • programa sai normalmente
  • Use System.exit()
  • Interrupções acionadas pelo terminal usando Ctrl+C
  • desligamento do sistema
  • Falha de memória insuficiente
  • Use Kill pid para matar o processo (usar kill -9 não será chamado)
  • …etc

Runtime.addShutDownHook(Thread hook)Cena de gatilho, veja a figura abaixo para detalhes

O arquiteto Nien, de 40 anos, lembrou: O desligamento forçado não pode ser acionado por Runtime.addShutDownHook(Thread hook),

O terceiro ramo na figura acima é claramente mostrado

Como um aplicativo SpringBoot sai normalmente

Como o Spring adiciona funções de gancho?

Aplicativo Spring/SpringBoot, como adicionar manualmente a função de gancho?

Spring/SpringBoot fornece um método:

// 通过这种方式来添加钩子函数
ApplicationContext.registerShutdownHook();

ApplicationContext.registerShutdownHook()O método é um método na estrutura Spring, que é usado para registrar um gancho de desligamento da JVM (gancho de desligamento).

Quando a JVM é encerrada, o contêiner Spring pode encerrar e liberar recursos normalmente, evitando possíveis vazamentos de recursos ou outros problemas.

ApplicationContext.registerShutdownHook()O método deve ser chamado em um mainmétodo para garantir que o contêiner Spring seja encerrado corretamente quando a JVM for encerrada.

A seguir está a introdução oficial do Spring:

registerShutdownHook() análise do código-fonte

O código-fonte do gancho de saída de registro de mola por meio da JVM é o seguinte:

// 通过源码可以看到,
@Override
public void registerShutdownHook() {
    
    
    if (this.shutdownHook == null) {
    
    
        // No shutdown hook registered yet.
        this.shutdownHook = new Thread() {
    
    
            @Override
            public void run() {
    
    
                synchronized (startupShutdownMonitor) {
    
    
                    doClose();
                }
            }
        };
        // 也是通过这种方式来添加
        Runtime.getRuntime().addShutdownHook(this.shutdownHook);
    }
}
 
// 重点是这个doClose()方法
 
protected void doClose() {
    
    
    // Check whether an actual close attempt is necessary...
    if (this.active.get() && this.closed.compareAndSet(false, true)) {
    
    
        if (logger.isInfoEnabled()) {
    
    
            logger.info("Closing " + this);
        }
 
        LiveBeansView.unregisterApplicationContext(this);
 
        try {
    
    
            // Publish shutdown event.
            publishEvent(new ContextClosedEvent(this));
        }
        catch (Throwable ex) {
    
    
            logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
        }
 
        // Stop all Lifecycle beans, to avoid delays during individual destruction.
        if (this.lifecycleProcessor != null) {
    
    
            try {
    
    
                this.lifecycleProcessor.onClose();
            }
            catch (Throwable ex) {
    
    
                logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
            }
        }
 
        // Destroy all cached singletons in the context's BeanFactory.
        destroyBeans();
 
        // Close the state of this context itself.
        closeBeanFactory();
 
        // Let subclasses do some final clean-up if they wish...
        onClose();
 
        // Switch to inactive.
        this.active.set(false);
    }
}

Conforme mostrado no código-fonte em spring registerShutdownHook, é para registrar uma shutdownHookfunção de gancho jvm. Esta função de gancho será executada antes que o jvm seja encerrado.

Por meio do código-fonte, você pode ver: doClose()o método será executado bean的destroy()e o método será executado SmartLifeCyclePodemos stop()realizar o desligamento do objeto e o gerenciamento do ciclo de vida reescrevendo esses métodos para obter um desligamento suave e um desligamento normal.

Por que o spring chama automaticamente destroy() e outros métodos quando o container é destruído? Essa é a execução do destroyBeans()método . Portanto, uma preocupação especial aqui é destroyBeans()o método .

destroyBeans()É um método no framework Spring, que é chamado quando o contêiner Spring é fechado e é usado para destruir todos os beans singleton.

Quando o contêiner Spring é fechado, destroy()o método e destroyBeans()o método é usado para acionar esse processo.

No destroy()método , podemos liberar recursos, fechar conexões, etc. para garantir que o aplicativo seja encerrado corretamente.

Dicas do arquiteto Nien, de 40 anos:

destroy()O método só será chamado no bean singleton,

Para beans de protótipo, o contêiner Spring não chamará seus destroy()métodos .

Como usar o método destroy()

Dois caminhos:

  • Método 1: Implemente DisposableBeana interface e reescreva destroy()o método nela
  • Método 2: Use @PreDestroyanotações para especificar o método de destruição

destroy()Métodos são métodos chamados quando o contêiner Spring é destruído para liberar recursos ou executar operações de limpeza.

Método 1: Implemente DisposableBeana interface e reescreva destroy()o método nela

Aqui está destroy()um exemplo de como o método é usado:

public class MyBean implements DisposableBean {
    
    
    
    private Resource resource;
    
    public void setResource(Resource resource) {
    
    
        this.resource = resource;
    }
    
    // 实现 DisposableBean 接口中的 destroy() 方法
    @Override
    public void destroy() throws Exception {
    
    
        // 释放资源
        if (this.resource != null) {
    
    
            this.resource.release();
        }
    }
}

Neste exemplo, a interfaceMyBean é implementada e seu método é substituído.DisposableBeandestroy()

No destroy()método , liberamos resourceo recurso.

Quando o contêiner do Spring é destruído MyBean, destroy()o método de é chamado automaticamente, liberando recursos.

Método 2: Use @PreDestroyanotações para especificar o método de destruição

Além de implementar DisposableBeana interface , você também pode usar @PreDestroyanotações para especificar o método de destruição. Por exemplo:

public class MyBean {
    
    
    
    private Resource resource;
    
    public void setResource(Resource resource) {
    
    
        this.resource = resource;
    }
    
    // 使用 @PreDestroy 注解指定销毁方法
    @PreDestroy
    public void releaseResource() {
    
    
        if (this.resource != null) {
    
    
            this.resource.release();
        }
    }
}

Neste exemplo, MyBeanem vez de implementar DisposableBeana interface , @PreDestroyuma anotação é usada para especificar o método destroy releaseResource(). Este método é chamado automaticamente quando o contêiner Spring é destruído.

Como o Springboot registra automaticamente a função de gancho

Na verdade, registerShutdownHook()o método hook, na nova versão do Springboot, não precisa ser chamado manualmente, ele foi executado automaticamente.

Olhando para um aplicativo simples, Nien o guiará pelo código-fonte passo a passo:

Siga o link de chamada de run, continue olhando para dentro e você verá refreshContexta execução do método

A chave está neste refreshContextmétodo

registerShutdownHook()Continue olhando para dentro e encontre um segredo: o método é chamado automaticamente

Bem, finalmente, de volta ao código-fonte de registerShutdownHook(), que foi apresentado em detalhes acima

Amigos cuidadosos, vocês podem descobrir que existe uma condição aí, se a condição não for atendida, basicamente pule-a.

Não com pressa.

O arquiteto Nien, de 40 anos, defende a prática, vamos executar passo a passo e descobrir um segredo: a condição padrão é verdadeira

Maneira offline elegante de instância de microsserviço SpringCloud

Em um cenário de microsserviço distribuído, as instâncias de microsserviço SpringCloud são gerenciadas por meio de um registro como Eureka/nacos.

  • Modo off-line elegante da instância de microsserviço Eureka
  • Modo off-line elegante da instância de microsserviço Nacos

Modo off-line elegante da instância de microsserviço Eureka

No entanto, se o componente de descoberta de serviço usar o Eureka, haverá um atraso de até 90 segundos por padrão antes que outros aplicativos percebam que o serviço está offline.

isso significa:

Dentro de 90 segundos após a instância ficar offline, outros serviços ainda podem chamar a instância offline.

Quando o aplicativo Spring Boot sai, como excluir ativamente a instância no Eureka?

Você pode usar o gancho de desligamento do aplicativo Spring Boot, combinado com a API do cliente do Eureka, para atingir o objetivo de off-line elegante da instância do microsserviço.

As duas principais APIs de cliente do Eureka são as seguintes:

  • Ao executar eurekaAutoServiceRegistration.start()o método, o serviço atual registra o serviço no registro Eureka;
  • Quando o método for executado eurekaAutoServiceRegistration.stop(), o serviço atual cancelará o registro no centro de registro Eureka e o centro de registro excluirá o serviço da lista de registro após receber a solicitação.

Com a ajuda do gancho Shutdown do aplicativo Spring Boot, o alvo do off-line gracioso da instância do microsserviço, o código-fonte é o seguinte:

O resultado da execução é o seguinte:

Modo off-line gracioso da instância do microsserviço Nacos

O arquiteto de 40 anos fez uma pesquisa em seu círculo técnico gratuito (Future Super Architect Community Future Super Architect Community), e a maioria dos projetos online agora usa o Nacos como centro de registro.

Então, o Nacos oferece suporte online e offline de instâncias de microsserviços?

Em teoria, o Nacos não apenas oferece suporte on-line e off-line graciosos, mas também pode ser operado por meio do console, API ou SDK.

Ao iniciar o serviço no console, você pode escolher o "método online", sendo que existem duas formas: "online rápido" e "liberação cinza". Obviamente, isso requer a cooperação do pessoal de operação e manutenção. Entre eles, o "lançamento rápido" iniciará diretamente a instância do serviço, enquanto o "lançamento em escala de cinza" primeiro liberará a instância do serviço para o ambiente em escala de cinza e, em seguida, liberará gradualmente para o ambiente de produção após aguardar um período de tempo.

Aqui não nos concentramos no trabalho do pessoal de operação e manutenção, nos concentramos no trabalho de desenvolvedores e arquitetos.

Na API ou SDK, os seguintes métodos podem ser usados ​​para operações online e offline:

  • Instância de serviço online: chame registerInstancea interface , especifique o nome do serviço, IP, porta e outras informações.
  • Instância de serviço offline: chame deRegisterInstancea interface e especifique o ID da instância.

Com a ajuda do gancho Shutdown do aplicativo Spring Boot e da API nacos do Eureka, o objetivo do off-line elegante da instância do microsserviço pode ser alcançado.

O método de implementação é semelhante ao Eureka anterior, apenas a diferença está na chamada da API.

Não há expansão aqui. Se você estiver interessado, pode entrar no círculo técnico gratuito de Nien (Future Super Architect Community Future Super Architect Community) para se comunicar.

Como sair normalmente de uma instância de microsserviço SpringCloud em um cenário nativo de nuvem

De um modo geral, nossos aplicativos online são implantados no Kubernetes.

Para obter detalhes sobre a arquitetura de microsserviços nativa da nuvem K8S+ SpringCloud, consulte o e-book em PDF de Nien "K8S Study Bible"

Aí vem a pergunta, no cenário nativo da nuvem, como sair normalmente da instância do microsserviço SpringCloud?

Ou usar a função de gancho do desligamento da JVM?

Em primeiro lugar, Nien o levará a examinar o problema da função de gancho que a JVM desliga.

Revise em quais circunstâncias a função de gancho do desligamento da JVM será chamada

O SpringShutDownHook usado anteriormente ainda é a função de gancho que chama o desligamento da JVM.

Como esse método fornecido pelo JDK pode registrar uma função de gancho para desligamento da JVM, em que circunstâncias essa função será chamada?

  • programa sai normalmente
  • Use System.exit()
  • Interrupções acionadas pelo terminal usando Ctrl+C
  • desligamento do sistema
  • Falha de memória insuficiente
  • Use Kill pid para matar o processo (usar kill -9 não será chamado)
  • …etc

No cenário nativo de nuvem, os problemas existentes do gancho de desligamento da JVM

De um modo geral, nossos aplicativos online são implantados no Kubernetes.

Para obter detalhes sobre a arquitetura de microsserviços nativa da nuvem K8S+ SpringCloud, consulte o e-book em PDF de Nien "K8S Study Bible"

Como o Kubernetes interrompe os pods de maneira elegante?

Ao eliminar um pod, o status do pod é encerrado e o processo de encerramento é iniciado.

  • Primeiro, o Service removerá o Pod do Endpoint, para que o balanceamento de carga do Service não envie mais tráfego para o Pod.
  • Então, primeiro ele irá verificar se existe um gancho preStop, e se estiver definido, execute-o,
  • Por fim, envie um sinal SIGTERM ao pod para permitir que todos os contêineres no pod saiam normalmente.

A saída normal da JVM ocorre na última etapa.

Porém, em situações reais, podemos nos deparar com as seguintes situações, que levam a acidentes na última etapa, como:

  • O código SpringBoot no container tem muita lógica para lidar com a saída normal, mas alguns deles lidam com problemas de lógica, o que impossibilita a saída
  • O SpringBoot no contêiner foi processado normalmente e ficou preso, resultando em esgotamento de recursos, incapaz de lidar com a lógica do código do offline normal de microsserviços ou pode demorar muito para concluir o processamento

Como o Kubernetes lida com tempos limite?

O Kubernetes também tem terminationGracePeriodSecondsum tempo de parada difícil, que é 30 segundos por padrão. Se o processo acima não puder ser concluído em 30 segundos, SIGKILL será enviado para encerrar o pod à força.

Em plataformas compatíveis com POSIX, SIGKILL é o sinal enviado a um processo para fazer com que ele seja encerrado imediatamente.

As constantes simbólicas para SIGKILL são definidas no arquivo de cabeçalho signal.h. Como os números dos sinais podem variar em diferentes plataformas, nomes de sinais simbólicos são usados, no entanto, na maioria dos sistemas principais, SIGKILL é o sinal nº 9

Runtime.addShutDownHook(Thread hook)Cena de gatilho, veja a figura abaixo para detalhes

O arquiteto Nien, de 40 anos, lembrou: O desligamento forçado não pode ser acionado por Runtime.addShutDownHook(Thread hook), a terceira ramificação na imagem acima é claramente exibida

Portanto, o problema da saída normal da JVM é claramente revelado.

Em situações reais, se forem encontradas as seguintes situações, ocorre um acidente na última etapa, como:

  • O código SpringBoot no container tem muita lógica para lidar com a saída normal, mas alguns deles lidam com problemas de lógica, o que impossibilita a saída
  • O SpringBoot no contêiner foi processado normalmente e ficou preso, resultando em esgotamento de recursos, incapaz de lidar com a lógica do código do offline normal de microsserviços ou pode demorar muito para concluir o processamento

A saída normal da JVM é inválida.

Como fazer isso?

As estratégias mais diretas e eficazes são:

Use o pre-hook para interromper o pod normalmente e defina a interface de gancho preStop para executar primeiro a lógica off-line principal

Defina a interface de gancho preStop para executar primeiro a lógica off-line principal

Defina a interface de gancho preStop para executar primeiro a lógica off-line principal, por exemplo, a instância de microsserviço aqui fica off-line

Como fazer isso? Na prática, existem etapas:

  • step1 : No aplicativo SpringBoot, defina a interface WEB e implemente a lógica principal offline
  • step2 : Na configuração da interface de gancho preStop do pod, defina-o como o link de gancho offline do aplicativo SpringBoot

step1 : No aplicativo SpringBoot, defina a interface WEB e implemente a lógica principal offline

Veja o primeiro passo:

step2 : Na configuração da interface de gancho preStop do pod, defina-o para o link de gancho off-line do aplicativo SpringBoot

O método específico é: adicione o item de configuração PreStop ao contêiner e defina-o como o link de gancho offline do aplicativo SpringBoot

/demo-provider/graceful/service/offline

Ao eliminar um pod, o status do pod é encerrado e o processo de encerramento é iniciado.

  • step1 : O Service removerá o Pod do Endpoint, para que o balanceamento de carga do Service não envie mais tráfego para o Pod,
    para que esta instância de microsserviço não receba solicitações do usuário
  • Em seguida, ele primeiro verificará se existe um hook preStop, caso esteja definido, será executado,
    desta forma não receberá requisições rpc de outros microsserviços quando estiver off-line da central de cadastro.
  • Por fim, envie um sinal SIGTERM ao pod para fazer com que todos os contêineres no pod saiam normalmente.

Após a conclusão da execução do hook preStop, na última etapa, o Pod envia um sinal SIGTERM para tornar todos os contêineres no Pod normais. Após o sinal SIGTERM ser enviado para a JVM, a JVM executará a lógica de saída normal, principalmente :

  • Processar solicitações inacabadas, observe que novas solicitações não serão aceitas
  • Liberação de recursos agrupados: pool de conexão de banco de dados, pool de conexão HTTP
  • Lidando com a liberação do thread: solicitações HTTP já conectadas

Para isso, vamos primeiro usá-los no método de gancho de processamento da JVM apresentado anteriormente.

Resumo de off-line não destrutivo off-line de microsserviços

Antes que o serviço de aplicativo fique off-line, notificamos ativamente o centro de registro para colocar a instância off-line por meio da chamada de gancho HTTP

Quer se trate do Dubbo ou da estrutura de serviço distribuído da Cloud, todos eles estão preocupados em como colocar o provedor offline no centro de registro antes que o serviço seja interrompido e, em seguida, parar o provedor de serviços,

Somente assim podemos garantir que chamadas remotas RPC entre microsserviços de negócios não causem vários 503, tempo limite e outros fenômenos.

Anexo: desligamento normal de aplicativos SpringBoot

Além do off-line não destrutivo de microsserviços, como um aplicativo SpringBoot, também há um requisito para desligamento normal de serviços únicos:

  • Processar solicitações inacabadas, observe que novas solicitações não serão aceitas
  • Liberação de recursos agrupados: pool de conexão de banco de dados, pool de conexão HTTP
  • Lidando com a liberação do thread: solicitações HTTP já conectadas

Conforme mencionado anteriormente, vamos colocá-lo primeiro no método de gancho de processamento da JVM.

O desligamento normal do aplicativo SpringBoot, na verdade, refere-se ao desligamento normal do servidor WEB incorporado.

Atualmente, o Spring Boot foi desenvolvido para 2.3.4.RELEASE.Com a chegada da versão 2.3, o elegante mecanismo de desligamento também foi aprimorado.

O que é o comportamento normal de desligamento do contêiner da Web

O comportamento de desligamento normal do contêiner da Web significa que, quando o contêiner é fechado, o processamento da solicitação de processamento atual é concluído ou aguarda um período de tempo, para que a solicitação de processamento seja concluída antes de fechar o contêiner, em vez de encerrar diretamente à força o pedido de processamento. Isso evita a interrupção do processamento das solicitações, melhorando a disponibilidade e a estabilidade do sistema.

De um modo geral, o comportamento de desligamento normal de um contêiner da Web precisa atender às seguintes condições:

  1. Aguarde a conclusão do processamento da solicitação e nenhuma nova solicitação será aceita.
  2. Se o tempo de espera exceder um determinado limite, o contêiner pode ser forçado a desligar.
  3. Antes do container ser fechado, uma resposta precisa ser dada ao cliente, informando que o container está sendo fechado e nenhuma nova requisição será aceita

O objetivo do desligamento normal:

Se não houver desligamento normal e o servidor for desligado diretamente (kill -9) neste momento, o negócio atualmente em execução no contêiner falhará diretamente e dados sujos serão gerados em alguns cenários especiais.

O comportamento específico do desligamento normal:

Quando o servidor executar o desligamento (kill -2), um pouco de tempo será reservado para que a execução da thread de negócios dentro do contêiner seja concluída.

Depois de adicionar a configuração elegante de desligamento, o contêiner não permite a entrada de novas solicitações neste momento.

A versão atual do desligamento normal do Spring Boot suporta Jetty, Reactor Netty, Tomcat e Undertow, bem como aplicativos da Web reativos e baseados em Servlet, todos com suporte para desligamento normal.

O método de processamento da nova solicitação está relacionado ao servidor da Web. Reactor Netty e Tomcat interromperão a solicitação de acesso e o método de processamento do Undertow é retornar 503.

Os comportamentos específicos são mostrados na tabela abaixo:

nome do contêiner da web declaração de comportamento
tomcat 9.0.33+ Pare de receber solicitações e a nova solicitação do cliente aguardará um tempo limite.
Reator Netty Pare de receber solicitações e a nova solicitação do cliente aguardará um tempo limite.
Ressaca Pare de receber solicitações e o cliente retornará 503 diretamente para novas solicitações.

Diferentes contêineres da Web podem ter maneiras diferentes de implementar o desligamento normal, mas geralmente fornecem opções de configuração relevantes ou interfaces de API para realizar essa função.

Além disso, semelhante ao servidor WEB incorporado no SpringBoot, outros servidores WEB integrados não SpringBoot também podem ser configurados.

Aqui está uma configuração de desligamento elegante para Nginx e Apache:

  • O Nginx pode definir o tempo de espera por meio de worker_shutdown_timeoutopções
  • O Apache pode usar graceful-stopo comando para obter um desligamento normal.

Uso e Configuração do Graceful Shutdown

A configuração da nova versão é muito simples, server.shutdown=gracefule está pronta (observe que a elegante configuração de desligamento precisa ser combinada com Tomcat 9.0.33 (inclusive) e superior)

server:
  port: 6080
  shutdown: graceful #开启优雅停机
spring:
  lifecycle:
    timeout-per-shutdown-phase: 20s #设置缓冲时间 默认30s

Depois de definir os parâmetros do buffer timeout-per-shutdown-phase, se o thread não puder concluir a execução dentro do tempo especificado, ele será forçado a parar.

Vamos dar uma olhada na diferença entre parar logs normalmente e não adicioná-los ao parar:

//未加优雅停机配置
Disconnected from the target VM, address: '127.0.0.1:49754', transport: 'socket'
Process finished with exit code 130 (interrupted by signal 2: SIGINT)

Depois de adicionar a configuração de desligamento normal,

A espera de solicitações ativas para cpmplete pode ser claramente encontrada no log . Nesse momento, o contêiner será interrompido após a execução do ShutdownHook.

Princípio de saída graciosa

Como mencionado anteriormente, a saída elegante do SpringBoot pode finalmente adicionar ganchos no programa Java, e o método gancho será executado quando o programa for encerrado, para realizar funções como fechamento de recursos, saída suave e saída normal.

Durante o processo de inicialização do SpringBoot, um JVM Shutdown Hook será registrado por padrão.Quando o aplicativo for fechado, o gancho será acionado para chamar o método doClose() para fechar o contêiner.

Esta parte do código, apresentada anteriormente, está especificamente org.springframework.boot.SpringApplication#refreshContextno método

Registre o Bean que implementa smartLifecycle

Ao criar um servidor web, um smartLifecyclebean implementado será criado para suportar o desligamento normal do servidor.

// 注册webServerGracefulShutdown用来实现server优雅关闭
this.getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer));
}

Você pode ver que a classe WebServerGracefulShutdownLifecycle implementa SmartLifecyclea interface e reescreve o método stop.

 // 优雅关闭server
this.webServer.shutDownGracefully((result) -> callback.run());

@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
    
    
    if (this.gracefulShutdown == null) {
    
    
        // 如果没有开启优雅停机,会立即关闭tomcat服务器
        callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
        return;
    }
    // 优雅关闭服务器
    this.gracefulShutdown.shutDownGracefully(callback);
}

Até agora, o elegante código de saída realiza o registro de retorno de chamada de saída por meio do método stop do smartLifecycle Bean.

O fluxo e o tempo da execução do callback do smartLifecycle

Quando o método stop do smartLifecycle Bean é executado?

Conforme mencionado acima, depois que o método hook da JVM for chamado, o método doColse() será executado.

E este método doColse(), antes de fechar o container, irá chamar o método lifecycle através do lifecycleProcessor.

protected void doClose() {
    
    
   if (this.active.get() && this.closed.compareAndSet(false, true)) {
    
    
      LiveBeansView.unregisterApplicationContext(this);
      // 发布 ContextClosedEvent 事件
      publishEvent(new ContextClosedEvent(this));
      // 回调所有实现Lifecycle 接口的Bean的stop方法
      if (this.lifecycleProcessor != null) {
    
    
            this.lifecycleProcessor.onClose();
      }
      // 销毁bean, 关闭容器
      destroyBeans();
      closeBeanFactory();
      onClose();
      if (this.earlyApplicationListeners != null) {
    
    
         this.applicationListeners.clear();
         this.applicationListeners.addAll(this.earlyApplicationListeners);
      }
      // Switch to inactive.
      this.active.set(false);
   }
}

Feche a entrada do Lifecycle Bean:

org.springframework.context.support.DefaultLifecycleProcessor

O código específico é o seguinte:

public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactoryAware {
    
    
   
   @Override
   public void onClose() {
    
    
      stopBeans();
      this.running = false;
   }

   private void stopBeans() {
    
    
      //获取所有的 Lifecycle bean
      Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
      //按Phase值对bean分组, 如果没有实现 Phased 接口则认为 Phase 是 0
      Map<Integer, LifecycleGroup> phases = new HashMap<>();
      lifecycleBeans.forEach((beanName, bean) -> {
    
    
         int shutdownPhase = getPhase(bean);
         LifecycleGroup group = phases.get(shutdownPhase);
         if (group == null) {
    
    
            group = new LifecycleGroup(shutdownPhase, this.timeoutPerShutdownPhase, lifecycleBeans, false);
            phases.put(shutdownPhase, group);
         }
         group.add(beanName, bean);
      });
      
      if (!phases.isEmpty()) {
    
    
         List<Integer> keys = new ArrayList<>(phases.keySet());
         //按照 Phase 值倒序
         keys.sort(Collections.reverseOrder());
         // Phase值越大优先级越高,先执行
         for (Integer key : keys) {
    
    
            phases.get(key).stop();
         }
      }
   }

DefaultLifecycleProcessorO fluxo de execução do método stop:

  • Obtenha todos os beans no contêiner que implementam a interface Lifecycle.
    Como a interface smartLifecycle herda Lifecycle, ela é obtida aqui.
  • Em seguida, classifique o grupo List contendo todos os beans na ordem inversa do valor da fase, e aquele com o maior valor é classificado primeiro. (A interface Phased não é implementada e o padrão Phase é 0)
  • Chame o método stop do bean em cada grupo por vez (quanto maior a Fase, o método stop é executado primeiro)

A implementação do método stop é finalmente executada aqui.

Conclua o comportamento de saída normal do tomcat, execute a solicitação recebida antes e realize a saída normal.

Resumo do processo de execução do desligamento normal do SpringBoot:

  • O SpringBoot registra o método de retorno de chamada doclose() por meio do Shutdown Hook, que aciona a execução quando o aplicativo é fechado.
  • Quando o SpringBoot cria um servidor web, ele registra um bean que implementa a interface smartLifecycel para desligar o tomcat normalmente
  • doClose() executará todos os métodos stop dos beans da interface Lifecycle antes de destruir o bean e fechar o container, e será agrupado por valor de Fase, e aquele com a fase maior será executado primeiro.
  • WebServerGracefulShutdownLifecycle, Phase=Inter.MAX_VALUE, está na sequência de execução de prioridade mais alta, portanto, ele acionará o desligamento normal do tomcat primeiro e o método de desligamento do tomcat será executado de forma assíncrona. A linha principal continuará a chamar e executar o método de desligamento de outros beans neste grupo e em seguida, aguarde até que todos os beans sejam fechados.Se o tempo de espera for excedido, ele executará o desligamento do próximo conjunto de beans de ciclo de vida.

Como acionar o desligamento normal do aplicativo SpringBoot

Por meio da análise do código-fonte, todos também descobriram que o desligamento normal dos aplicativos SpringBoot é o método de gancho para registrar a saída normal da JVM

Como o método hook para a saída normal da JVM é acionado?

Os métodos de gatilho comuns são:

  • Método 1: matar PID
  • Método 2: endpoint de desligamento

Método 1: matar PID

Uso: kill ID do processo java

O formato do comando kill é kill -Signal pid, onde pid é o número do processo, e signal é o sinal enviado ao processo. Sob os parâmetros padrão, kill envia um sinal SIGTERM (15) para o processo, informando ao processo que você precisa para ser desligado, por favor, pare de correr e saia sozinho.

A diferença entre matar, matar -9 e matar -3

kill passará o sinal representado por 15 como SIGTERM por padrão, que informa ao processo que você precisa ser encerrado, por favor, pare de executar e saia sozinho, o processo pode limpar o cache para terminar sozinho ou recusar-se a terminar.

kill -9O sinal representativo é SIGKILL, o que significa que o processo foi encerrado e precisa sair imediatamente, forçando a morte do processo.Este sinal não pode ser capturado ou ignorado.

kill -3Você pode imprimir as informações da pilha de cada thread do processo. kill -3 pidO caminho para salvar o arquivo final é: /proc/${pid}/cwd, e o nome do arquivo é: antBuilderOutput.log

A lista de outros sinais Kill é a seguinte:

Sinal valor ação padrão Significado (motivo da sinalização)
INSCREVA-SE 1 Prazo Desligamento do terminal ou morte do processo
ASSINAR 2 Prazo Sinal de interrupção do teclado
SEGUE 3 Essencial Sinal ausente do teclado
SELO 4 Essencial ordem ilegal
SIGABRT 6 Essencial Sinal de exceção de abortar
SIGFPE 8 Essencial exceção de ponto flutuante
SIGKILL 9 Prazo matar
SIGSEGV 11 Essencial Falha de segmentação ilegal (referência de memória inválida)
SIGPIPE 13 Prazo Tubo quebrado: gravando dados em um tubo sem processo de leitura
SIGALRM 14 Prazo Sinal de expiração do temporizador do alarme
PRAZO ALVO 15 Prazo terminação
SIGUSR1 30,10,16 Prazo Sinal definido pelo usuário 1
SIGUSR2 31,12,17 Prazo Sinal definido pelo usuário 2
SIGCHLD 20,17,18 acender processo filho parado ou encerrado
SIGCONT 19,18,25 Cont. Se parado, continue a execução
SIGSTOP 17,19,23 Parar Não é um sinal de parada de um terminal
SIGTSTP 18,20,24 Parar sinal de parada do terminal
ASSINAR 21,21,26 Parar terminal de leitura de processo em segundo plano
SIGTTOU 22,22,27 Parar O processo em segundo plano grava no terminal
SIGBUS 10,7,10 Essencial Erro de barramento (erro de acesso à memória)
ASSINAR ENQUETE Prazo Ocorre um evento pesquisável (Sys V), sinônimo de SIGIO
SIGPROF 27,27,29 Prazo Mapa de distribuição estatística com timer quando expira
SIGSYS 12,-,12 Essencial Chamada de sistema ilegal (SVr4)
SIG TRAP 5 Essencial Armadilhas de rastreamento/ponto de interrupção
SIGURG 16,23,21 acender sinal de emergência do soquete (4.2BSD)
SIGVTALRM 26,26,28 Prazo Temporizador virtual expira (4.2BSD)
SIGXCPU 24,24,30 Essencial Limite de tempo da CPU excedido (4.2BSD)
SIGXFSZ 25,25,31 Essencial limite de comprimento de arquivo excedido (4.2BSD)
SIGIOT 6 Essencial IOT self-trap, sinônimo de SIGABRT
SIGEMT 7,-,7 Prazo
SIGSTKFLT -,16,- Prazo Erro de pilha do coprocessador (não usado)
SIGIO 23,29,22 Prazo As operações de E/S podem ser executadas no descritor
SIGCLD -,-,18 acender Sinônimo de SIGCHLD
CINGAPURA 29,30,19 Prazo Falha de energia (Sistema V)
SIGINFO 29,-,- Sinônimo de SIGPWR
SIGLOST -,-,- Prazo bloqueio de arquivo perdido
SIGWINCH 28,28,20 acender Alteração do tamanho da janela (4.3BSD, Sun)
RELIGIÃO -,31,- Prazo Sinal não utilizado (será SIGSYS)

Método 2: endpoint de desligamento

O Spring Boot fornece o endpoint /shutdown, que pode ser usado para obter um desligamento normal.

Como usar: Adicione a seguinte configuração ao application.yml do aplicativo que você deseja colocar offline para habilitar e expor o endpoint /shutdown:

management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: shutdown

Envie uma solicitação POST para o endpoint /shutdown

curl -X http://ip:port/actuator/shutdown

A essência desse método é a mesma do método 1 e também é implementada com a ajuda do gancho Shutdown do aplicativo Spring Boot.

Análise do código-fonte do endpoint de desligamento

Todos os atuadores usam o método de extensão SPI,

Primeiro olhe para AutoConfiguration, você pode ver que o ponto chave é ShutdownEndpoint

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnAvailableEndpoint(
    endpoint = ShutdownEndpoint.class
)
public class ShutdownEndpointAutoConfiguration {
    
    
    public ShutdownEndpointAutoConfiguration() {
    
    
    }

    @Bean(
        destroyMethod = ""
    )
    @ConditionalOnMissingBean
    public ShutdownEndpoint shutdownEndpoint() {
    
    
        return new ShutdownEndpoint();
    }
}

ShutdownEndpoint,的核心代码如下:

@Endpoint(
    id = "shutdown",
    enableByDefault = false
)
public class ShutdownEndpoint implements ApplicationContextAware {
    
    
     
    @WriteOperation
    public Map<String, String> shutdown() {
    
    
        if (this.context == null) {
    
    
            return NO_CONTEXT_MESSAGE;
        } else {
    
    
            boolean var6 = false;

            Map var1;
            try {
    
    
                var6 = true;
                var1 = SHUTDOWN_MESSAGE;
                var6 = false;
            } finally {
    
    
                if (var6) {
    
    
                    Thread thread = new Thread(this::performShutdown);
                    thread.setContextClassLoader(this.getClass().getClassLoader());
                    thread.start();
                }
            }

            Thread thread = new Thread(this::performShutdown);
            thread.setContextClassLoader(this.getClass().getClassLoader());
            thread.start();
            return var1;
        }
    }
  
      private void performShutdown() {
    
    
        try {
    
    
            Thread.sleep(500L);
        } catch (InterruptedException var2) {
    
    
            Thread.currentThread().interrupt();
        }

        this.context.close();  //这里才是核心
    }
}

在调用了 this.context.close() ,其实就是AbstractApplicationContextclose() 方法 (重点是其中的doClose()

/**
 * Close this application context, destroying all beans in its bean factory.
 * <p>Delegates to {@code doClose()} for the actual closing procedure.
 * Also removes a JVM shutdown hook, if registered, as it's not needed anymore.
 * @see #doClose()
 * @see #registerShutdownHook()
 */
@Override
public void close() {
    
    
    synchronized (this.startupShutdownMonitor) {
    
    
        doClose(); //重点:销毁bean 并执行jvm shutdown hook
        // If we registered a JVM shutdown hook, we don't need it anymore now:
        // We've already explicitly closed the context.
        if (this.shutdownHook != null) {
    
    
            try {
    
    
                Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
            }
            catch (IllegalStateException ex) {
    
    
                // ignore - VM is already shutting down
            }
        }
    }
}

doClose() 方法,又回到了前面的 Spring 的核心关闭方法。

doClose()在销毁 bean, 关闭容器之前会执行所有实现 Lifecycel 接口 bean 的 stop方法,并且会按 Phase 值分组, phase 大的优先执行。

SpringCloud + SpringBoot优雅退出总结

Spring Boot、Spring Cloud应用的优雅停机,平时经常会被问到,这也是实际应用过程中,必须要掌握的点,

这里简单总结下以前我们一般在实现的时候要把握的几个要点:

  1. 关闭命令方面,一定要杜绝 kill -9 操作
  2. 多线程采用线程池实现,并且线程池要优雅关闭,从而保证每个异步线程都可以随Spring的生命周期完成正常关闭操作
  3. 有服务注册与发现机制下的时候,优先通过自定义K8S的POD的preStop钩子接口,来保障实例的主动下线。

说在最后

SpringCloud + SpringBoot 面试题,是非常常见的面试题。

以上的5大方案,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

最终,让面试官爱到 “不能自已、口水直流”。 offer, 也就来了。

学习过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩沟通。

技术自由的实现路径 PDF:

实现你的 架构自由:

吃透8图1模板,人人可以做架构

10Wqps评论中台,如何架构?B站是这么做的!!!

阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了

峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?

100亿级订单怎么调度,来一个大厂的极品方案

2个大厂 100亿级 超大流量 红包 架构方案

… 更多架构文章,正在添加中

尼恩 架构笔记、面试题 的PDF文件更新,▼请到下面【技术自由圈】公号取 ▼

Acho que você gosta

Origin blog.csdn.net/crazymakercircle/article/details/131117316
Recomendado
Clasificación