Como garantimos o crescimento do CityMobile

imagem

Meu nome é Ivan, sou o chefe de desenvolvimento de servidores do Citimobil. Hoje vou falar sobre o que é esse desenvolvimento de servidores, quais problemas encontramos e como planejamos desenvolver.

Início do crescimento


Poucas pessoas sabem que o CityMobile existe há muito tempo, 13 anos. Antigamente era uma pequena empresa que trabalhava apenas em Moscou. Havia muito poucos desenvolvedores e eles sabiam como o sistema funcionava - porque eles mesmos o criaram. O mercado de táxis estava começando a se desenvolver então, as cargas eram um centavo. Nem sequer tivemos a tarefa de fornecer tolerância a falhas e escala.

Em 2018, o Mail.Ru Group investiu no CityMobile e começamos a crescer rapidamente. Não havia tempo para reescrever a plataforma, ou pelo menos uma refatoração significativa; era necessário desenvolver seus recursos funcionais para alcançar nosso principal concorrente e contratar pessoas rapidamente. Quando entrei na empresa, apenas 20 desenvolvedores estavam envolvidos no back-end e quase todos estavam em julgamento. Por isso, decidimos “colher frutas baixas”: fazer mudanças simples que produzam um resultado enorme.

Naquela época, tínhamos um monólito em PHP e três serviços no Go, além de uma base principal no MySQL que o monólito estava acessando (os serviços eram usados ​​como repositórios do Redis e EasticSearch). E gradualmente, com o aumento da carga, solicitações pesadas do sistema começaram a desacelerar a base.

O que poderia ser feito com isso?

Primeiro, demos o passo óbvio: colocar um escravo em produção. Mas se muitos pedidos pesados ​​lhe chegam, ele aguenta? Também era óbvio que, com uma abundância de pedidos de relatórios analíticos, o escravo começaria a ficar para trás. Um forte acúmulo de escravos poderia afetar adversamente o desempenho de todo o CityMobile. Como resultado, colocamos outro escravo para analistas. Seu atraso nunca leva a problemas no produto. Mas mesmo isso nos pareceu insuficiente. Nós escrevemos um replicador de tabela do MySQL no Clickhouse. E hoje, a análise vive por conta própria, usando uma pilha mais projetada para OLAP.

Dessa forma, o sistema funcionou por algum tempo e começaram a aparecer funções mais exigentes no hardware. Havia mais e mais solicitações a cada semana: nem mesmo uma semana se passou sem um novo registro. Além disso, colocamos uma bomba-relógio em nosso sistema. Anteriormente, tínhamos apenas um ponto de falha - a base principal do MySQL, mas com a adição de um escravo, havia dois pontos: o mestre e o escravo. A falha de qualquer uma dessas máquinas levaria a uma falha completa do sistema.

Para se proteger contra isso, começamos a usar um proxy local para executar escravos de verificação de saúde. Isso nos permitiu usar muitos escravos sem alterar o código. Introduzimos verificações automáticas regulares do status de cada escravo e suas métricas gerais:

  • la;
  • atraso escravo;
  • disponibilidade de porta;
  • número de bloqueios, etc.

Se um determinado limite for excedido, o sistema remove o escravo da carga. Mas, ao mesmo tempo, não mais que metade dos escravos pode ser retirada, para que, devido ao aumento da carga nos demais, eles não consigam um tempo de inatividade para si mesmos. Como proxy, usamos o HAProxy e imediatamente incluímos um plano para mudar para o ProxySQL no backlog. A escolha foi um pouco estranha, mas nossos administradores já tinham uma boa experiência trabalhando com HAProxy, e o problema era agudo e exigia uma solução inicial. Assim, criamos um sistema à prova de falhas para os escravos, que foi dimensionado com bastante facilidade. Por toda a sua simplicidade, ela nunca nos decepcionou.

Crescimento adicional


À medida que os negócios se desenvolviam, encontramos outro gargalo em nosso sistema. Com as mudanças nas condições externas - por exemplo, as chuvas começaram em uma grande região - o número de pedidos de táxi cresceu rapidamente. Em tais situações, os motoristas não tiveram tempo de reagir com rapidez suficiente e houve uma escassez de carros. Enquanto os pedidos foram distribuídos, eles criaram uma carga nos escravos do MySQL em um loop.

Encontramos uma solução bem-sucedida - Tarantool. Como era difícil reescrever o sistema, resolvemos o problema de maneira diferente: usando a ferramenta de replicação mysql-tarantoolfez replicação de algumas tabelas do MySQL para Tarantool. Todos os pedidos de leitura que surgiram durante a escassez de carros, começamos a transmitir em Tarantool e, desde então, não estamos mais preocupados com tempestades e furacões! E resolvemos o problema com o ponto de falha ainda mais fácil: instalamos imediatamente várias réplicas às quais acessamos a verificação de saúde através do HAProxy. Cada instância do Tarantool é replicada por um replicador separado. Como um bônus agradável, também resolvemos o problema de escravos atrasados ​​nesta seção de código: a replicação do MySQL para o Tarantool funciona muito mais rapidamente do que do MySQL para o MySQL.

No entanto, nossa base principal ainda era um ponto de falha e não era escalável nas operações de gravação. Começamos a resolver esse problema dessa maneira.

Primeiro, naquela época, já tínhamos começado a criar ativamente novos serviços (por exemplo, antifraude, sobre os quais meus colegas já escreveram ). Além disso, os serviços imediatamente exigiram escalabilidade de armazenamento. Para o Redis, começamos a usar apenas o Redis-cluster e para o Tarantool - Vshard. Onde usamos o MySQL, começamos a usar o Vitess para uma nova lógica . Esses bancos de dados são imediatamente compartilháveis, portanto, quase não há problemas com a gravação e, se surgirem repentinamente, será fácil resolvê-los adicionando servidores. Agora, usamos o Vitess apenas para serviços não críticos e estudamos as armadilhas, mas, no futuro, ele estará em todos os bancos de dados MySQL.

Em segundo lugar, como era difícil e demorado implementar o Vitess para a lógica existente, seguimos de uma maneira mais simples, embora menos universal: começamos a distribuir a base principal por diferentes servidores, tabela por tabela. Tivemos muita sorte: verificou-se que a carga principal no registro é criada por tabelas que não são críticas para a funcionalidade principal. E quando fazemos essas tabelas, não criamos pontos adicionais de falha nos negócios. O principal inimigo para nós era a forte conexão das tabelas no código usando JOINs (havia JOINs e 50-60 tabelas cada). Nós os cortamos sem piedade.

Agora é hora de relembrar dois padrões muito importantes para projetar sistemas de alta carga:

  • Graceful degradation. , - . , , , , .. , .
  • Circuit breaker. , . , , , . ? ( - graceful degradation). , FPM- , . - ( ) , . , - , ( ).

Então, começamos a escalar no mínimo, mas ainda havia pontos de falha.

Decidimos então recorrer à replicação semi-síncrona (e a implementamos com êxito). Qual é a sua característica? Se durante a replicação assíncrona normal, o limpador no datacenter derramar um balde de água no servidor, as últimas transações não terão tempo para replicar para os escravos e serão perdidas. E devemos ter certeza de que, neste caso, não teremos problemas sérios depois que um dos escravos se tornar um novo mestre. Como resultado, decidimos não perder transações e, para isso, usamos replicação semi-síncrona. Agora os escravos podem atrasar, mas mesmo se o servidor de banco de dados mestre for destruído, as informações sobre todas as transações serão armazenadas em pelo menos um escravo.

Este foi o primeiro passo para o sucesso. O segundo passo foi usar o utilitário orquestrador. Também monitoramos constantemente todo o MySQL no sistema. Se a base principal falhar, a automação fará do mestre o escravo mais recente (e, considerando a replicação semi-síncrona, ela conterá todas as transações) e alternará toda a carga de gravação para ele. Agora, podemos reviver a história de uma faxineira e um balde de água.

Qual é o próximo?

Quando cheguei ao CityMobile, tínhamos três serviços e um monólito. Hoje existem mais de 20. E a principal coisa que impede nosso crescimento é que ainda temos uma única base principal. Heroicamente lutamos contra isso e dividimos em bases separadas.

Como nos desenvolvemos mais?


Expanda o conjunto de microsserviços. Isso resolverá muitos dos problemas que enfrentamos hoje. Por exemplo, a equipe não tem mais uma única pessoa que conhece o dispositivo de todo o sistema. E como, devido ao rápido crescimento, nem sempre temos documentação atualizada e é muito difícil mantê-la, é difícil para iniciantes se aprofundar no curso dos assuntos. E se o sistema consistir em vários serviços, a documentação de cada um deles será incomparavelmente mais fácil. E a quantidade de código para um estudo único é bastante reduzida.

Vamos que vamos.Eu realmente amo esse idioma, mas sempre acreditei que não era prático reescrever o código de trabalho de um idioma para outro. Porém, recentemente, a experiência mostrou que as bibliotecas PHP, mesmo as mais comuns e padrão, não são da mais alta qualidade. Ou seja, corrigimos muitas bibliotecas. Digamos que a equipe do SRE corrigiu a biblioteca padrão para interagir com o RabbitMQ: verificou-se que uma função tão básica como o tempo limite não funcionou. E quanto mais profunda a equipe do SRE e eu entendemos esses problemas, mais fica claro que poucas pessoas pensam em tempos limite no PHP, poucas pessoas se preocupam com o teste de bibliotecas, poucas pessoas pensam em bloqueios. Por que isso está se tornando um problema para nós? Porque as soluções Go são muito mais fáceis de manter.

O que mais me impressiona com o Go? Curiosamente, escrever sobre isso é muito simples. Além disso, o Go facilita a criação de uma variedade de soluções de plataforma. Essa linguagem possui um conjunto muito poderoso de ferramentas padrão. Se, por algum motivo, nosso back-end começar a ficar lento de repente, basta ir a um URL específico e você poderá ver todas as estatísticas - o diagrama de alocação de memória, para entender onde o processo está ocioso. E, no PHP, é mais difícil identificar problemas de desempenho.Além

disso, o Go possui ótimos linters - programas que encontram automaticamente os erros mais comuns para você. A maioria deles é descrita no artigo "50 tons de Go", e os linters os detectam perfeitamente.

Continue cortando as bases. Mudaremos para Vitess em todos os serviços.

Tradução de um monólito PHP em redis-cluster.Nos nossos serviços, o redis-cluster provou ser excelente. Infelizmente, implementá-lo em PHP é mais difícil. O monólito usa comandos que não são suportados pelo redis-cluster (o que é bom, esses comandos trazem mais problemas do que benefícios).

Vamos investigar os problemas do RabbitMQ. Acredita-se que o RabbitMQ não seja o software mais confiável. Vamos estudar esta questão, encontrar e resolver problemas. Talvez pensemos em mudar para Kafka ou Tarantool.

All Articles