
Aqui vou falar sobre o dispositivo de uma das muitas ferramentas que ajudam no desenvolvimento de vários serviços para o projeto Odnoklassniki. Dentro da empresa, chamamos de "Hot Code Replace" (HCR), e essa ferramenta foi projetada para corrigir erros críticos e descomplicados na execução de serviços de produção sem interrompê-los. Esse é um recurso extremamente importante, pois permite evitar o processo bastante chato e demorado de criar uma nova versão corrigida de um serviço de lixo eletrônico, evitar a pausa bastante longa na acessibilidade de cada host e evitar a descarga de caches.
Em geral, economiza muito tempo e reduz o intervalo desde o momento em que o erro é detectado até a correção de horas para minutos. Na maioria das vezes, conforme planejado, pequenos erros no código são corrigidos; por exemplo, o programador esqueceu de verificar se há nulos e, para alguns usuários, certas ações no site levam a um erro. Ou seja, quando a correção é realizada alterando várias linhas dentro do método. E por causa de pequenas mudanças, você não precisa mais distrair seus colegas e aguardar horas disponíveis para produção.
Por exemplo:

Você pode corrigi-lo facilmente:

Obviamente, você pode fazer muito mais alterações, ao mesmo tempo adicionar novas classes, fazer alterações rapidamente, conforme solicitado pelo gerente, sem esperar pela próxima atualização. Mas isso já é, se Monsieur sabe muito sobre perversões.
Além disso, é possível colocar "remendos" um no outro e até o infinito.
Mas essa ferramenta não é onipotente e se baseia na funcionalidade padrão que a classe Java oferece:
java.lang.instrument.Instrumentation e seu método void redefineClasses (ClassDefinition ... definições) .
Instrumentation.redefineClasses substitui as classes carregadas anteriormente pelo novo código de bytes. Você pode sobrecarregar várias classes com dependências diferentes ao mesmo tempo. A sobrecarga não altera as instâncias de classe existentes, não muda a herança e não toca nos campos de instância ou classe. Você só pode alterar o corpo do método, o conjunto de constantes e atributos. Você pode adicionar novas classes ou subclasses. Assinaturas de método, campos de instância e campos de classe não podem ser alterados. Se você tentar fazer alterações incompatíveis, redefineClasses, em princípio, não funcionará e gerará um erro. Deve-se lembrar que quando as classes estão sobrecarregadas, a execução da seção sobrecarregada do código não é interrompida, o novo bytecode será usado na próxima vez que o mesmo método for chamado. E, portanto, se você tentar corrigir o código de um método que possui um ciclo infinitamente longo, a substituição real ocorrerá somente após o término desse ciclo.
Se for simplesmente: você pode alterar o código apenas dentro dos métodos e do ponto.
E aqui está um exemplo de um loop while, que até o método ser concluído, não será corrigido.

A principal dificuldade foi criar uma ferramenta que funcione no ecossistema Odnoklassniki, uma ferramenta que se encaixa em todos os processos de trabalho estabelecidos. Que irá interagir de forma consistente e transparente com todos os serviços em centenas de hosts, além de ser flexível e fácil de usar. Essa ferramenta deve lidar com dezenas de experimentos, trabalhos e atualizações que ocorrem continuamente na produção.
Como é o processo de instalação de um patch do ponto de vista do desenvolvedor / administrador, tentando corrigir um erro na produção, mas para que isso possa ser feito usando algum procedimento padrão e confiável em dezenas de servidores. Omitimos o processo de localização e correção de erros no código.
1. Um brunch separado é criado no GIT para correções de código. O uso da versão é muito importante, não apenas por conveniência, mas também para possíveis investigações subsequentes.
2. O TeamCity inicia o processo de criação do patch. Primeiro, uma montagem do projeto é criada a partir do brunch especificado e, em seguida, a nova montagem é comparada com a instalada na produção. Para fazer isso, escrevi um plug-in para a ferramenta de construção, que extrai todos os arquivos dos arquivos, compara discrepâncias e seleciona apenas os arquivos que foram alterados ou adicionados. Nesse caso, a versão do compilador Java nos dois assemblies deve ser a mesma, porque outra versão do compilador criará arquivos diferentes e quase todos os arquivos do projeto serão incluídos no patch. É muito importante criar apenas um pequeno arquivo, no qual somente os arquivos necessários serão obtidos, porque Isso irá acelerar significativamente o processo de entrega do patch para dezenas de servidores. O processo de construção é adequado não apenas para o patch do código do projeto, mas também é possível substituir a biblioteca corrigida no projeto. Ao comparar o conteúdo de dois assemblies, serão encontradas diferenças nas bibliotecas (arquivos jar).
3. No caso de uma compilação bem-sucedida, o patch é enviado para um repositório especial e, na janela de resultados, uma chave (ou hash) é emitida, necessária para identificar exclusivamente o patch e garantir que esse código chegue à produção.

Bem, e novamente - você pode corrigir um número ilimitado de vezes e as compilações com o mesmo número de versão serão diferentes por um hash.
4. Em seguida, todas as atividades são transferidas para o serviço de configuração, onde na interface do usuário comum você pode especificar para qual serviço, em quais hosts e quais versões de aplicativos você precisa corrigir.

Essa abundância de parâmetros fornece o nível necessário de flexibilidade das configurações, o que é muito importante em um grande zoológico de muitos servidores. Digamos que em alguma parte dos servidores o número da versão do aplicativo seja diferente e você não precise corrigir esse código. Ou, para verificação, o Hot Code Rreplace é iniciado primeiro em um servidor ou em um grupo de servidores e depois distribuído por todas as instâncias do aplicativo.
5. Por meio de uma alteração na configuração, os serviços selecionados recebem informações sobre o que o patch precisa ser instalado, sua versão e hash de verificação. A idéia é que todos os serviços recebam o comando "instalar o patch" e, em seguida, atuem de forma independente. Eles comparam independentemente sua própria versão e somente se a versão corresponder e o hash do patch estiver ausente ou diferente, eles baixam independentemente o conjunto do patch do repositório. O processo de download em si ocorre via HTTP e você pode alterar rapidamente o endereço do repositório, o número de tentativas de download e o período de espera entre as novas tentativas.
6. Cada aplicativo verifica localmente o hash do conjunto e o descompacta. Ao mesmo tempo, cada arquivo é verificado quanto à sua presença na matriz dentre os retornados por Instrumentation.getAllLoadedClasses (), todas as novas classes e arquivos são gravados em um novo - um caminho de classe temporário, e esse caminho de classe é adicionado via Instrumentation.appendToSystemClassLoaderSearch (), e as classes existentes são lidas na memória e passe pelo método redefineClasses.
7. Todo o processo: a chegada de um sinal sobre a necessidade de corrigir o aplicativo, o download, a verificação, a descompactação e o aplicativo são registrados em detalhes, tanto no registro geral do aplicativo quanto no próprio, para que você possa monitorar de forma rápida e sem gestos desnecessários.
8. Depois que o patch é aplicado com sucesso, o processo termina alterando a versão do aplicativo para o patch, adicionando uma linha especialmente composta, incluindo o hash do patch. No caso de, para alguns hosts, a versão não ter sido alterada para a esperada, vamos para o registro Substituir código quente para esse host e ver o que aconteceu lá. Se houver problemas de comunicação, você poderá repetir com segurança o comando patch e o host desejado tentará novamente.
Quais possíveis problemas podem impedir o aplicativo de aplicar patches? Existem alguns deles, e entre eles a funcionalidade da classe Instrumentação que eu colocaria em último lugar. Até agora, o código torto que não atende às condições estritas do redefineClasses sempre foi atualizado pela JVM, sem consequências para o aplicativo. Ao aplicar o método redefineClasses, a JVM para completamente o aplicativo, mas esse processo leva uma fração de segundo. Porque não é nada assustador.
O momento mais arriscado é a entrega do patch ao servidor, que foi decidido por novos treinamentos. Mas se as retransmissões não ajudarem, você poderá repetir o comando para chamar o patch e cada um dos hosts tentará repetir o processo, mas instale o patch apenas se necessário, ou seja, o patch não foi instalado anteriormente ou se a chave de hash foi alterada.
Outro problema em potencial é quando a correção corrige um erro e adiciona um novo. Para minimizar esse risco, primeiro carregamos o patch em um número limitado de servidores, examinamos os logs, gráficos e monitoramos o resultado. E somente então lançamos as correções para outros hosts.
O que fazer com o reinício de um aplicativo ou servidor? Isso já está incorporado à lógica de todos os aplicativos de colegas de classe: um dos primeiros em qualquer aplicativo é o módulo HCR. E se, durante a inicialização, forem observadas informações sobre a necessidade de corrigir o aplicativo, o patch será aplicado primeiro.
E agora um pouco sobre o que consiste o Hot Code Replace.
- Nosso JavaAgent. JavaAgent, se alguém se esqueceu , este é um arquivo * .jar separado, especialmente formado, que é captado pela JVM quando o aplicativo começa a usar um parâmetro adicional, por exemplo: -javaagent: /path/to/lib/my-agent.jar Graças aos recursos adicionais do Javaagent- e é possível usar a mágica de substituição de código. É no agente que a classe java.lang.instrument.Instrumentation está disponível. Mas eu não o obstruí (o agente) com código extra, porque A atualização do agente é uma tarefa não trivial, mas simplesmente moveu a instância da classe Instrumentation para o campo estático da classe de utilitário. Assim, todas as manipulações podem ser iniciadas de qualquer lugar no aplicativo.
- Serviço de configuração - é responsável pela configuração de qualquer um dos nossos aplicativos e, portanto, é inicializado em cada aplicativo primeiro. É aí que a principal funcionalidade do Hot Code Replace fica oculta. Na inicialização do aplicativo ou ao alterar a configuração do HCR para um aplicativo específico, a compatibilidade da versão é verificada e todas as manipulações acima são executadas.
- TeamCity e scripts de compilação - para criar "patches" convenientemente e salvar apenas classes e recursos modificados ou adicionados a eles.
Quais são as vantagens que temos dessa ferramenta? A primeira é a velocidade de correção de erros críticos no produto. Pelos registros, vejo que os colegas começaram gradualmente a usar o HCR com mais e mais frequência, em vez de esperar por liberações. Em seguida é a velocidade da aplicação. O aplicativo não precisa ser parado, a JVM congela apenas por uma fração de segundo e todos os seus objetos permanecem em seus lugares e continuam a funcionar.
E nossos desenvolvedores se curaram de maneira livre e feliz e corrigiram seus erros de forma imediata e independente diretamente na produção, sem levar em consideração o número de servidores e a carga.