Veja a arquitetura? E eu não vejo, mas ela é

No desenvolvimento de hh.ru hoje cerca de 150 pessoas. Temos muitas equipes interessantes e cada uma delas contribui significativamente. Mas neste artigo vou contar apenas sobre um deles.


Porque sou o líder da equipe dela, e há várias razões para isso:

  • freqüentemente os candidatos não entendem o que estamos fazendo;
  • às vezes até os funcionários da empresa não sabem disso, porque nossa equipe não possui gerente de produto, área funcional de negócios própria e lista de serviços por nós suportados;
  • nossos méritos na maioria das vezes permanecem na sombra;
  • no final, “se você quiser descobrir, tente explicar para alguém” :)

Portanto, tentarei entender com exemplos compreensíveis em que nosso trabalho realmente consiste.

Vamos começar com o mais geral, ou seja, com prioridades, existem dois deles:

  • suporte do sistema para a confiabilidade e resiliência de nossa plataforma. Aqui, vale a pena prestar atenção à palavra “sistema” - significa que não corrigiremos defeitos de desempenho específicos, mas desenvolveremos regras e padrões gerais, corrigi-los em estruturas, verificações automáticas etc. para que funcione para todos.
  • foco de desenvolvimento na lógica de negócios. Ou seja, menos um desenvolvedor pensa em oferecer suporte a confiabilidade, arquitetura etc. - tudo do melhor. É claro que livrar completamente os colegas de tais pensamentos é bastante prejudicial, mas manter um equilíbrio razoável faz sentido.

A partir dessas prioridades, seguem as principais orientações de nosso trabalho:

0. Suporte e desenvolvimento de arquitetura


O hh.ru está a 5-6k rps dos usuários, chegando ao pico de 10k, que crescem em uma ordem de magnitude, atingindo back-ends. São mais de 1.500 instâncias, gerando cerca de 150 serviços em 3 DC. Então, sim, antes de tudo, esses são os esquemas bastante ramificados com quadrados, bancos e flechas: quem vai aonde, onde deveria ser. Obviamente, não desenhamos esquemas - cobrimos as necessidades com automação, registro e monitoramento, mas assustamos nossos alunos , por exemplo, com o seguinte:



somos realmente responsáveis ​​por encontrar e eliminar gargalos e soluções inflexíveis na arquitetura e desenvolvê-lo de acordo com as necessidades.

Eu vou dar um exemplo:

O hh.ru está trabalhando longe do primeiro ano e, uma vez que parecia uma boa idéia ter uma máquina separada para executar tarefas em segundo plano em um cronograma - você pode alocar mais recursos para ela e não haverá provas. Mas o que temos no final:

  • ponto de falha para todas as tarefas
  • configuração única reproduzida apenas em prod
  • tarefas cuja lógica é projetada para um lançamento dedicado em uma máquina em negrito separada e não é dimensionada horizontalmente

Quando entendemos isso, garantimos que tínhamos todos os meios para transferir as tarefas da coroa para instâncias gerais e iniciamos uma grande tarefa na categoria de dívida técnica - agora que chega a hora de pagar as dívidas, os colegas estão gradualmente eliminando esse problema.

1. Padronização de muletas


Primeiro, essas são nossas estruturas e ferramentas para o rápido desenvolvimento de serviços: porcas e parafusos e frontik . De qualquer forma, o jclient e muitas outras bibliotecas abertas no nosso github surgiram da ideia de que faz sentido agregar a experiência de operar várias tecnologias. Isso nos permite cultivar as limitações, padrões de design e comportamento que trabalhamos em batalha, e consideramos que eles são os mais adequados, compreensíveis e confiáveis.

Além desses exemplos óbvios de padronização, há aqueles em que faz sentido generalizar soluções específicas.

Por exemplo, em algum momento, começamos a ter periodicamente a necessidade de enviar mensagens para rabbitmq (pelo menos uma vez). As tarefas eram resolvidas repetidamente por filas auto-escritas na base, e o dba dizia repetidamente o quanto as filas na base eram fortemente amadas, especialmente as carregadas. No final, ficou óbvio que era necessária uma solução padrão aqui, que seria aceitável para o dba, garantisse a entrega confiável e conveniente para o desenvolvimento - foi assim que escrevemos nossa biblioteca para integrar pgq e rabbitmq. Agora há uma alta probabilidade de que usaremos o pgq também em conjunto com o kafka.

1.0 Insetos


Os erros também são globais. Por exemplo, em algum momento, descobrimos que nossa estrutura python está registrada no consul em todo trabalhador de processo, e até faz isso antes que o aplicativo esteja pronto para aceitar solicitações. Após a correção na estrutura, as alterações atingirão gradualmente todos os serviços à medida que forem atualizados.

Falei sobre outro bug geral relacionado às configurações da jvm no estágio de demonstração jpoint 2019 .

E o que, por exemplo, fazer com um bug reproduzido uma vez por semana em uma das instâncias, é tratado com uma reinicialização, mas nem a carga nem os sintéticos o reproduzem?
, java- . nuts-and-bolts:

"qtp1778300121-22" #22 prio=5 os_prio=0 cpu=797.67ms elapsed=11737.06s tid=0x00007f5890139000 nid=0x26 waiting for monitor entry [0x00007f58922c7000]
java.lang.Thread.State: BLOCKED (on object monitor)
at ch.qos.logback.core.AppenderBase.doAppend(AppenderBase.java:63)
- waiting to lock <0x00000000e86acad0> (a ru.hh.nab.logging.HhSyslogAppender)
at ru.hh.nab.logging.HhMultiAppender.doAppend(HhMultiAppender.java:47)
at ru.hh.nab.logging.HhMultiAppender.doAppend(HhMultiAppender.java:21)
at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51)


:

"qtp1778300121-22" #22 prio=5 os_prio=0 cpu=5718.81ms elapsed=7767.14s tid=0x00007f1537dba000 nid=0x24 waiting for monitor entry [0x00007f153d2b9000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(java.base@11.0.4/ConcurrentHashMap.java:1723)
- waiting to lock <0x00000000e976a668> (a java.util.concurrent.ConcurrentHashMap$Node)
at org.springframework.beans.factory.BeanFactoryUtils.transformedBeanName(BeanFactoryUtils.java:86)


jackson:

"qtp1778300121-23" #23 prio=5 os_prio=0 cpu=494.19ms elapsed=7234.32s tid=0x00007f6c01218800 nid=0x25 waiting for monitor entry [0x00007f6c07cfa000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.ProviderBase._endpointForWriting(ProviderBase.java:711)
- waiting to lock <0x00000000e9f94c38> (a org.glassfish.jersey.jackson.internal.jackson.jaxrs.util.LRUMap)
at org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.ProviderBase.writeTo(ProviderBase.java:588)


Code Cache:



java . java, , - . , .

1.1 Soluções gerais


Às vezes, é possível encontrar soluções padrão antes que isso se torne um problema sério. Como exemplos, podemos citar a tarefa de processamento de log que nosso Vlad Senin falou no mesmo jpoint 2019 , ou a tarefa de gerenciamento de tempo limite em nosso cliente http.

Seu significado é que é útil determinar um tempo limite razoável, não no lado do cliente, mas no lado do servidor. Para o servidor, temos dados sobre a rapidez com que ele responde aos seus pontos finais. Agora, nosso cliente suporta um tempo limite para o serviço. Mas é óbvio que nem todos os terminais de serviço respondem da mesma maneira - alguns mais longos, outros mais rápidos. Eu gostaria de poder usar tempos limite diferentes. Caso contrário, surge uma situação semelhante a esta:



Até agora, essas situações aparecem apenas sob teste de estresse, mas quero resolvê-las antes que isso se torne um problema.

1.2 Questões em aberto


Mas nem todos os problemas são explicados por alguns lugares importantes e processos manuais complicados. Além disso, darei alguns exemplos de questões que também se enquadram no campo de nossas prioridades, mas ao mesmo tempo são muito menos determinísticas. Portanto, descreverei apenas os dados iniciais e podemos discutir as soluções, se desejado, nos comentários.

Então, o primeiro exemplo: agora fica claro que há um problema de integrar nossos serviços entre si. A integração de, digamos, um identificador de site em uma API pode levar mais tempo que seu desenvolvimento inicial.

Outro exemplo, provavelmente familiar para muitos, de um problema semelhante é serrar um monólito. Todo mundo entende que um monólito, coberto por um grande número de legados, complica o desenvolvimento e a operação. Mas quem sabe quanto? Vale a pena sacrificar outras tarefas da dívida técnica em favor de serrar, cada uma das quais individualmente carrega um valor extremamente pequeno?

A escala desses problemas e similares é tal que, para resolvê-los, às vezes é preciso ir muito além da estrutura técnica e mergulhar em áreas completamente novas do processo de trabalho. Isso é assustador, por um lado, mas, por outro lado, oferece uma liberdade incrível na escolha de decisões.

2. Como trabalhamos


A história sobre as direções de nosso trabalho será incompleta sem uma descrição de como trabalhamos com tudo isso.

Para começar, o que me atraiu a trabalhar em “Arquitetura” e o que nos motiva a todos: realmente trabalhamos pela qualidade.

E antes que as pedras voem para mim, tentarei explicar o que quero dizer. Acredito que nenhum desenvolvedor deliberadamente pontua na qualidade. O ponto está na dívida técnica: se estamos falando de uma seção da lógica de negócios que não está planejada para ser reutilizada, provavelmente o montante da dívida de uma solução não tão ideal crescerá lentamente ao longo do tempo, se for o caso.

Isso permite que você esfrie um pouco seu perfeccionismo - inicie uma tarefa para dívidas e chegue à próxima iteração. Mas se estivermos falando sobre uma estrutura ou uma ferramenta de preparação da configuração global usada em centenas de aplicativos e consolida certos padrões de design ou de nomenclatura, a taxa de crescimento da dívida resultante de sua decisão malsucedida pode impedir qualquer ganho. É claro que existem situações em que até a melhor solução revela fraquezas à medida que é usada, mas isso não acontece com frequência ...

No final, gostaria de falar sobre os obstáculos que ainda surgem em nosso caminho. Sem isso, uma história sobre o nosso trabalho seria desonesta. Assim.

2.0 Tarefas de avaliação de dificuldade


Como eu disse acima, não podemos avaliar o efeito benéfico para todas as tarefas. Quanto diminuirá o tempo de liberação da tarefa quando uma solução “in a box” sair para alguma função? Qual das duas seções problemáticas do código deve ser refatorada primeiro? Para desenvolver um sistema adequado para avaliar tarefas, nos reunimos algumas vezes por semana durante vários meses, mas esse é um tópico para um post separado.

2.1 Inconsciente coletivo


Coordenar algo para 150 pessoas não é uma tarefa fácil. Nossa estrutura organizacional muito descentralizada geralmente se manifesta com seus melhores lados, mas para a "arquitetura" às vezes é um sério obstáculo. Existem muito poucos acordos com os quais é possível chegar a um acordo, menos ainda aqueles cuja conformidade pode ser monitorada.

E todas as alterações devem ser roladas sem problemas. O serviço pode não ser atualizado por meses, mas ainda há um monólito ... Bem, muito triste.

Então nós conversamos


Espero que, depois da minha história, tenha esclarecido um pouco o que "Arquitetura" faz no hh.ru. E se eu consegui despertar seu interesse em nosso trabalho, é geralmente fantástico. Além disso, agora há uma vaga em nossa equipe . Ficaremos muito felizes por novas idéias que nos ajudarão a alcançar nossos olhares ocultos e ociosos, mas vitórias tão importantes.

ps o KDPV original é, ao que parece, uma ilustração desse homem . Espero que ele não seja contra o uso de suas imagens como um KDPV

All Articles