FAQ sobre arquitetura e trabalho do VKontakte

A história da criação do VKontakte está na Wikipedia, foi contada pelo próprio Pavel. Parece que todo mundo já a conhece. Sobre os internos, arquitetura e estrutura do site no HighLoad++ Pavel me disse em 2010. Muitos servidores vazaram desde então, então vamos atualizar as informações: vamos dissecar, retirar o interior, pesar e olhar o dispositivo VK do ponto de vista técnico.

FAQ sobre arquitetura e trabalho do VKontakte

Alexei Akulovich (AterCattus) desenvolvedor back-end da equipe VKontakte. A transcrição deste relatório é uma resposta colectiva a perguntas frequentes sobre o funcionamento da plataforma, infra-estruturas, servidores e interacção entre eles, mas não sobre desenvolvimento, nomeadamente sobre ferro. Separadamente, sobre bancos de dados e o que o VK possui, sobre coleta de logs e monitoramento de todo o projeto como um todo. Detalhes sob o corte.



Há mais de quatro anos tenho lidado com todos os tipos de tarefas relacionadas ao backend.

  • Carregar, armazenar, processar e distribuir mídia: vídeo, transmissão ao vivo, áudio, fotos, documentos.
  • Infraestrutura, plataforma, monitoramento de desenvolvedores, logs, caches regionais, CDN, protocolo RPC proprietário.
  • Integração com serviços externos: notificações push, análise de links externos, feed RSS.
  • Ajudar os colegas com diversas questões, cujas respostas exigem mergulhar em códigos desconhecidos.

Durante esse tempo, participei de muitos componentes do site. Quero compartilhar essa experiência.

Arquitetura geral

Tudo, como sempre, começa com um servidor ou grupo de servidores que aceitam solicitações.

Servidor frontal

O servidor frontal aceita solicitações via HTTPS, RTMP e WSS.

HTTPS - são solicitações para as versões web principal e móvel do site: vk.com e m.vk.com, e outros clientes oficiais e não oficiais de nossa API: clientes móveis, mensageiros. Temos uma recepção RTMP-tráfego para transmissões ao vivo com servidores frontais separados e WSS- conexões para API de streaming.

Para HTTPS e WSS em servidores vale a pena nginx. Para transmissões RTMP, recentemente mudamos para nossa própria solução dar, mas está além do escopo do relatório. Para tolerância a falhas, esses servidores anunciam endereços IP comuns e atuam em grupos para que, caso haja algum problema em um dos servidores, as solicitações dos usuários não sejam perdidas. Para HTTPS e WSS, esses mesmos servidores criptografam o tráfego para assumir parte da carga da CPU.

Não falaremos mais sobre WSS e RTMP, mas apenas sobre solicitações HTTPS padrão, que geralmente estão associadas a um projeto web.

Backend

Atrás da frente geralmente existem servidores back-end. Eles processam solicitações que o servidor frontal recebe dos clientes.

Ele servidores kPHP, no qual o daemon HTTP está em execução, porque o HTTPS já está descriptografado. kPHP é um servidor que roda em modelos de pré-garfo: inicia um processo mestre, vários processos filhos, passa soquetes de escuta para eles e eles processam suas solicitações. Nesse caso, os processos não são reiniciados entre cada solicitação do usuário, mas simplesmente redefinem seu estado para o estado original de valor zero - solicitação após solicitação, em vez de reiniciar.

Distribuição de carga

Todos os nossos backends não são um enorme conjunto de máquinas que podem processar qualquer solicitação. Nós eles divididos em grupos separados: geral, móvel, API, vídeo, teste... O problema em um grupo separado de máquinas não afetará todos os outros. Em caso de problemas com vídeo, o usuário que ouve música nem saberá dos problemas. O back-end para o qual enviar a solicitação é decidido pelo nginx no front de acordo com a configuração.

Coleta de métricas e rebalanceamento

Para entender quantos carros precisamos ter em cada grupo, não confie no QPS. Os backends são diferentes, têm solicitações diferentes, cada solicitação tem uma complexidade diferente de cálculo de QPS. É por isso que nós operamos com o conceito de carga no servidor como um todo - na CPU e no desempenho.

Temos milhares desses servidores. Cada servidor físico executa um grupo kPHP para reciclar todos os núcleos (porque o kPHP é de thread único).

servidor de conteúdo

CS ou Content Server é um armazenamento. CS é um servidor que armazena arquivos e também processa arquivos carregados e todos os tipos de tarefas síncronas em segundo plano que o frontend web principal atribui a ele.

Temos dezenas de milhares de servidores físicos que armazenam arquivos. Os usuários adoram fazer upload de arquivos e nós adoramos armazená-los e compartilhá-los. Alguns desses servidores são fechados por servidores pu/pp especiais.

pu/pp

Se você abriu a aba de rede no VK, você viu pu/pp.

FAQ sobre arquitetura e trabalho do VKontakte

O que é pu/pp? Se fecharmos um servidor após o outro, existem duas opções para fazer upload e download de um arquivo para o servidor que foi fechado: diretamente através http://cs100500.userapi.com/path ou via servidor intermediário - http://pu.vk.com/c100500/path.

Pu é o nome histórico para upload de fotos e pp é proxy de foto. Ou seja, um servidor é para upload de fotos e outro é para upload. Agora não apenas as fotos foram carregadas, mas o nome foi preservado.

Esses servidores encerrar sessões HTTPSpara remover a carga do processador do armazenamento. Além disso, como os arquivos dos usuários são processados ​​nesses servidores, quanto menos informações confidenciais forem armazenadas nessas máquinas, melhor. Por exemplo, chaves de criptografia HTTPS.

Como as máquinas são fechadas por nossas outras máquinas, podemos nos dar ao luxo de não fornecer IPs externos “brancos” e dê "cinza". Dessa forma, economizamos no pool de IP e garantimos a proteção das máquinas contra acesso externo - simplesmente não há IP para entrar nele.

Resiliência sobre IPs compartilhados. Em termos de tolerância a falhas, o esquema funciona da mesma forma – vários servidores físicos possuem um IP físico comum, e o hardware à frente deles escolhe para onde enviar a solicitação. Falarei sobre outras opções mais tarde.

O ponto controverso é que neste caso o cliente mantém menos conexões. Se houver o mesmo IP para várias máquinas - com o mesmo host: pu.vk.com ou pp.vk.com, o navegador do cliente tem um limite no número de solicitações simultâneas para um host. Mas na época do HTTP/2 onipresente, acredito que isso não seja mais tão relevante.

A desvantagem óbvia do regime é que tem de bombear todo o tráfego, que vai para o armazenamento, através de outro servidor. Como bombeamos tráfego por meio de máquinas, ainda não podemos bombear tráfego pesado, por exemplo, vídeo, usando o mesmo esquema. Nós o transmitimos diretamente - uma conexão direta separada para armazenamentos separados especificamente para vídeo. Transmitimos conteúdo mais leve através de um proxy.

Não faz muito tempo, obtivemos uma versão melhorada do proxy. Agora vou lhe contar como eles diferem dos comuns e por que isso é necessário.

Espreguiçadeiras

Em setembro de 2017 a Oracle que já havia comprado a Sun demitiu um grande número de funcionários da Sun. Podemos dizer que neste momento a empresa deixou de existir. Ao escolher um nome para o novo sistema, nossos administradores decidiram homenagear a memória desta empresa e batizaram o novo sistema de Sun. Entre nós simplesmente a chamamos de “sóis”.

FAQ sobre arquitetura e trabalho do VKontakte

pp teve alguns problemas. Um IP por grupo – cache ineficaz. Vários servidores físicos compartilham um endereço IP comum e não há como controlar para qual servidor a solicitação irá. Portanto, se usuários diferentes vierem buscar o mesmo arquivo, se houver cache nesses servidores, o arquivo vai parar no cache de cada servidor. Este é um esquema muito ineficiente, mas nada poderia ser feito.

Consequentemente - não podemos fragmentar conteúdo, porque não podemos selecionar um servidor específico para este grupo - eles têm um IP comum. Também por algumas razões internas temos não foi possível instalar tais servidores em regiões. Eles ficaram apenas em São Petersburgo.

Com os sóis, mudamos o sistema de seleção. Agora temos roteamento anycast: roteamento dinâmico, anycast, daemon de autoverificação. Cada servidor possui seu próprio IP individual, mas uma sub-rede comum. Tudo está configurado de forma que em caso de falha de um servidor, o tráfego é distribuído automaticamente pelos demais servidores do mesmo grupo. Agora é possível selecionar um servidor específico, sem cache redundante, e a confiabilidade não foi afetada.

Suporte de peso. Agora podemos instalar máquinas de diferentes potências conforme necessário, e também, em caso de problemas temporários, alterar os pesos dos “sóis” de trabalho para reduzir a carga sobre eles, para que “descansem” e voltem a trabalhar.

Fragmentação por ID de conteúdo. Uma coisa engraçada sobre o sharding: geralmente fragmentamos o conteúdo para que diferentes usuários acessem o mesmo arquivo através do mesmo “sol” para que tenham um cache comum.

Lançamos recentemente o aplicativo “Clover”. Trata-se de um quiz online em transmissão ao vivo, onde o apresentador faz perguntas e os usuários respondem em tempo real, escolhendo opções. O aplicativo possui um chat onde os usuários podem conversar. Pode se conectar simultaneamente à transmissão mais de 100 mil pessoas. Todos escrevem mensagens que são enviadas a todos os participantes, e um avatar acompanha a mensagem. Se 100 mil pessoas vierem atrás de um avatar em um “sol”, às vezes ele pode rolar atrás de uma nuvem.

Para resistir a explosões de solicitações do mesmo arquivo, é para um determinado tipo de conteúdo que ativamos um esquema estúpido que espalha os arquivos por todos os “sóis” disponíveis na região.

Sol por dentro

Proxy reverso no nginx, cache em RAM ou em discos rápidos Optane/NVMe. Exemplo: http://sun4-2.userapi.com/c100500/path — um link para o “sol”, que está localizado na quarta região, o segundo grupo de servidores. Ele fecha o arquivo de caminho, que está fisicamente no servidor 100500.

Esconderijo

Adicionamos mais um nó ao nosso esquema arquitetônico – o ambiente de cache.

FAQ sobre arquitetura e trabalho do VKontakte

Abaixo está o diagrama de layout caches regionais, existem cerca de 20 deles. Estes são os locais onde estão localizados caches e “sóis”, que podem armazenar tráfego por si próprios.

FAQ sobre arquitetura e trabalho do VKontakte

Este é o cache de conteúdo multimídia; nenhum dado do usuário é armazenado aqui - apenas músicas, vídeos, fotos.

Para determinar a região do usuário, nós coletamos prefixos de rede BGP anunciados nas regiões. No caso de fallback, também teremos que analisar o banco de dados geoip caso não consigamos encontrar o IP por prefixos. Determinamos a região pelo IP do usuário. No código, podemos observar uma ou mais regiões do usuário – aqueles pontos dos quais ele está mais próximo geograficamente.

Como isso funciona?

Contamos a popularidade dos arquivos por região. Há um número de cache regional onde o usuário está localizado e um identificador de arquivo - pegamos esse par e aumentamos a classificação a cada download.

Ao mesmo tempo, demônios - serviços em regiões - de vez em quando chegam à API e dizem: “Eu sou tal e tal cache, dê-me uma lista dos arquivos mais populares da minha região que ainda não estão em mim. ” A API entrega vários arquivos classificados por classificação, o daemon os baixa, os leva para as regiões e entrega os arquivos de lá. Esta é a diferença fundamental entre pu/pp e Sun dos caches: eles fornecem o arquivo por si mesmos imediatamente, mesmo que esse arquivo não esteja no cache, e o cache primeiro baixa o arquivo para si mesmo e depois começa a devolvê-lo.

Neste caso obtemos conteúdo mais próximo dos usuários e distribuir a carga da rede. Por exemplo, apenas do cache de Moscou distribuímos mais de 1 Tbit/s durante os horários de pico.

Mas existem problemas - servidores de cache não são de borracha. Para conteúdo super popular, às vezes não há rede suficiente para um servidor separado. Nossos servidores de cache têm 40-50 Gbit/s, mas há conteúdo que obstrui completamente esse canal. Estamos avançando na implementação do armazenamento de mais de uma cópia de arquivos populares na região. Espero que a implementemos até ao final do ano.

Vimos a arquitetura geral.

  • Servidores frontais que aceitam solicitações.
  • Back-ends que processam solicitações.
  • Armazenamentos que são fechados por dois tipos de proxies.
  • Caches regionais.

O que está faltando neste diagrama? Claro, os bancos de dados nos quais armazenamos dados.

Bancos de dados ou mecanismos

Não os chamamos de bancos de dados, mas de motores - Motores, porque praticamente não temos bancos de dados no sentido geralmente aceito.

FAQ sobre arquitetura e trabalho do VKontakte

Esta é uma medida necessária.. Isso aconteceu porque em 2008-2009, quando o VK teve um crescimento explosivo de popularidade, o projeto funcionava inteiramente em MySQL e Memcache e havia problemas. O MySQL adorava travar e corromper arquivos, após o que não se recuperava, e o desempenho do Memcache diminuiu gradualmente e teve que ser reiniciado.

Acontece que o projeto cada vez mais popular tinha armazenamento persistente, que corrompe os dados, e cache, que fica lento. Nessas condições, é difícil desenvolver um projeto crescente. Decidiu-se tentar reescrever os aspectos críticos em que o projeto se concentrava em nossas próprias bicicletas.

A solução foi bem-sucedida. Havia uma oportunidade para fazer isso, mas também uma extrema necessidade, porque naquela época não existiam outras formas de escalar. Não existiam muitos bancos de dados, o NoSQL ainda não existia, existiam apenas MySQL, Memcache, PostrgreSQL - e é isso.

Operação universal. O desenvolvimento foi liderado pela nossa equipe de desenvolvedores C e tudo foi feito de forma consistente. Independentemente do mecanismo, todos eles tinham aproximadamente o mesmo formato de arquivo gravado em disco, os mesmos parâmetros de inicialização, processavam sinais da mesma maneira e se comportavam aproximadamente da mesma forma em caso de situações e problemas extremos. Com o crescimento dos motores, é conveniente para os administradores operarem o sistema - não há nenhum zoológico que precise ser mantido, e eles precisam reaprender a operar cada novo banco de dados de terceiros, o que possibilitou aumentar de forma rápida e conveniente seu número.

Tipos de motores

A equipe escreveu alguns motores. Aqui estão apenas alguns deles: amigo, dicas, imagem, ipdb, cartas, listas, logs, memcached, meowdb, notícias, nostradamus, foto, listas de reprodução, pmemcached, sandbox, pesquisa, armazenamento, curtidas, tarefas,…

Para cada tarefa que requer uma estrutura de dados específica ou processa solicitações atípicas, a equipe C escreve um novo mecanismo. Por que não.

Temos um motor separado memcached, que é parecido com um normal, mas com um monte de guloseimas e que não desacelera. Não é ClickHouse, mas também funciona. Disponível separadamente pmemcached - É memcached persistente, que também pode armazenar dados em disco, além disso, que cabe na RAM, para não perder dados ao reiniciar. Existem vários mecanismos para tarefas individuais: filas, listas, conjuntos - tudo o que nosso projeto exige.

Clusters

Do ponto de vista do código, não há necessidade de pensar em mecanismos ou bancos de dados como processos, entidades ou instâncias. O código funciona especificamente com clusters, com grupos de motores - um tipo por cluster. Digamos que haja um cluster memcached – é apenas um grupo de máquinas.

O código não precisa saber a localização física, o tamanho ou o número de servidores. Ele vai para o cluster usando um determinado identificador.

Para que isso funcione, você precisa adicionar mais uma entidade localizada entre o código e os mecanismos - procuração.

Proxy RPC

Procurador barramento de conexão, no qual quase todo o site é executado. Ao mesmo tempo temos nenhuma descoberta de serviço — em vez disso, há uma configuração para este proxy, que conhece a localização de todos os clusters e todos os fragmentos deste cluster. Isso é o que os administradores fazem.

Os programadores não se importam com quanto, onde e quanto custa - eles simplesmente vão para o cluster. Isso nos permite muito. Ao receber uma solicitação, o proxy redireciona a solicitação, sabendo onde - ele mesmo determina isso.

FAQ sobre arquitetura e trabalho do VKontakte

Neste caso, o proxy é um ponto de proteção contra falhas no serviço. Se algum mecanismo ficar lento ou travar, o proxy entende isso e responde de acordo com o lado do cliente. Isso permite remover o tempo limite - o código não espera a resposta do mecanismo, mas entende que não está funcionando e precisa se comportar de maneira diferente. O código deve estar preparado para o fato de que os bancos de dados nem sempre funcionam.

Implementações específicas

Às vezes, ainda queremos ter algum tipo de solução fora do padrão como mecanismo. Ao mesmo tempo, foi decidido não usar nosso proxy rpc pronto, criado especificamente para nossos motores, mas sim criar um proxy separado para a tarefa.

Para MySQL, que ainda temos aqui e ali, usamos db-proxy, e para ClickHouse - Casa de Gatinho.

Geralmente funciona assim. Existe um determinado servidor que roda kPHP, Go, Python - em geral, qualquer código que possa usar nosso protocolo RPC. O código é executado localmente em um proxy RPC - cada servidor onde o código está localizado executa seu próprio proxy local. Mediante solicitação, o proxy sabe para onde ir.

FAQ sobre arquitetura e trabalho do VKontakte

Se um motor quiser ir para outro, mesmo que seja vizinho, ele passa por um proxy, pois o vizinho pode estar em outro data center. O mecanismo não deve depender do conhecimento da localização de outra coisa senão ele mesmo - esta é a nossa solução padrão. Mas é claro que há exceções :)

Um exemplo de esquema TL segundo o qual todos os motores operam.

memcache.not_found                                = memcache.Value;
memcache.strvalue	value:string flags:int = memcache.Value;
memcache.addOrIncr key:string flags:int delay:int value:long = memcache.Value;

tasks.task
    fields_mask:#
    flags:int
    tag:%(Vector int)
    data:string
    id:fields_mask.0?long
    retries:fields_mask.1?int
    scheduled_time:fields_mask.2?int
    deadline:fields_mask.3?int
    = tasks.Task;
 
tasks.addTask type_name:string queue_id:%(Vector int) task:%tasks.Task = Long;

Este é um protocolo binário, cujo análogo mais próximo é protobuf. O esquema pré-descreve campos opcionais, tipos complexos - extensões de escalares integrados e consultas. Tudo funciona de acordo com este protocolo.

RPC sobre TL sobre TCP/UDP… UDP?

Temos um protocolo RPC para executar solicitações de mecanismo que é executado no esquema TL. Tudo isso funciona em uma conexão TCP/UDP. O TCP é compreensível, mas por que precisamos do UDP com frequência?

UDP ajuda evite o problema de um grande número de conexões entre servidores. Se cada servidor possui um proxy RPC e, em geral, pode ir para qualquer mecanismo, então existem dezenas de milhares de conexões TCP por servidor. Há uma carga, mas é inútil. No caso do UDP este problema não existe.

Nenhum handshake TCP redundante. Este é um problema típico: quando um novo mecanismo ou um novo servidor é iniciado, muitas conexões TCP são estabelecidas ao mesmo tempo. Para solicitações pequenas e leves, por exemplo, carga útil UDP, toda a comunicação entre o código e o mecanismo é dois pacotes UDP: um voa em uma direção, o segundo na outra. Uma viagem de ida e volta - e o código recebeu uma resposta do mecanismo sem aperto de mão.

Sim, tudo simplesmente funciona com uma porcentagem muito pequena de perda de pacotes. O protocolo tem suporte para retransmissões e timeouts, mas se perdermos muito, conseguiremos quase TCP, o que não é lucrativo. Não conduzimos o UDP através dos oceanos.

Temos milhares desses servidores e o esquema é o mesmo: um pacote de motores é instalado em cada servidor físico. Eles são, em sua maioria, de thread único para serem executados o mais rápido possível sem bloqueio e são fragmentados como soluções de thread único. Ao mesmo tempo, não temos nada mais confiável do que esses mecanismos, e muita atenção é dada ao armazenamento persistente de dados.

Armazenamento de dados persistente

Mecanismos escrevem binlogs. Um log binário é um arquivo ao final do qual é adicionado um evento para uma mudança de estado ou dados. Em diferentes soluções é chamado de forma diferente: log binário, WAL, AOF, Mas o princípio é o mesmo.

Para evitar que o mecanismo releia todo o binlog por muitos anos ao reiniciar, os mecanismos escrevem instantâneos - estado atual. Se necessário, eles lêem primeiro e depois terminam de ler o binlog. Todos os binlogs são escritos no mesmo formato binário - de acordo com o esquema TL, para que os administradores possam administrá-los igualmente com suas ferramentas. Não existe essa necessidade de instantâneos. Existe um cabeçalho geral que indica de quem é o snapshot int, magia do motor, e qual corpo não é importante para ninguém. Este é um problema com o mecanismo que gravou o instantâneo.

Descreverei rapidamente o princípio de operação. Existe um servidor no qual o mecanismo é executado. Ele abre um novo binlog vazio para escrever e escreve um evento para alterá-lo.

FAQ sobre arquitetura e trabalho do VKontakte

Em algum momento, ele decide tirar uma foto sozinho ou recebe um sinal. O servidor cria um novo arquivo, grava todo o seu estado nele, anexa o tamanho atual do binlog - deslocamento - ao final do arquivo e continua gravando. Um novo binlog não é criado.

FAQ sobre arquitetura e trabalho do VKontakte

Em algum momento, quando o mecanismo for reiniciado, haverá um log binário e um instantâneo no disco. O mecanismo lê o instantâneo inteiro e aumenta seu estado em um determinado ponto.

FAQ sobre arquitetura e trabalho do VKontakte

Lê a posição que estava no momento em que o instantâneo foi criado e o tamanho do binlog.

FAQ sobre arquitetura e trabalho do VKontakte

Lê o final do log binário para obter o estado atual e continua escrevendo eventos adicionais. Este é um esquema simples; todos os nossos motores funcionam de acordo com ele.

Replicação de dados

Como resultado, a replicação de dados em nosso baseado em declaração - não escrevemos no binlog nenhuma alteração de página, mas principalmente pedidos de mudança. Muito parecido com o que vem pela rede, apenas ligeiramente modificado.

O mesmo esquema é usado não apenas para replicação, mas também para criar backups. Temos um mecanismo - um mestre de escrita que grava no binlog. Em qualquer outro lugar onde os administradores o configurarem, esse log binário é copiado e pronto - temos um backup.

FAQ sobre arquitetura e trabalho do VKontakte

Se necessário lendo réplicaPara reduzir a carga de leitura da CPU, simplesmente é iniciado o mecanismo de leitura, que lê o final do binlog e executa esses comandos localmente.

O atraso aqui é muito pequeno e é possível saber o quanto a réplica está atrasada em relação ao mestre.

Fragmentação de dados no proxy RPC

Como funciona a fragmentação? Como o proxy entende para qual fragmento de cluster enviar? O código não diz: “Envie 15 fragmentos!” - não, isso é feito pelo proxy.

O esquema mais simples é firstint — o primeiro número da solicitação.

get(photo100_500) => 100 % N.

Este é um exemplo de protocolo de texto simples do memcached, mas, é claro, as consultas podem ser complexas e estruturadas. O exemplo pega o primeiro número da consulta e o restante quando dividido pelo tamanho do cluster.

Isso é útil quando queremos ter a localidade dos dados de uma única entidade. Digamos que 100 seja um ID de usuário ou grupo e queremos que todos os dados de uma entidade estejam em um fragmento para consultas complexas.

Se não nos importamos como as solicitações são distribuídas pelo cluster, existe outra opção - hash de todo o fragmento.

hash(photo100_500) => 3539886280 % N

Também obtemos o hash, o restante da divisão e o número do fragmento.

Ambas as opções só funcionam se estivermos preparados para o fato de que, ao aumentarmos o tamanho do cluster, iremos dividi-lo ou aumentá-lo várias vezes. Por exemplo, tínhamos 16 fragmentos, não temos o suficiente, queremos mais - podemos obter 32 com segurança sem tempo de inatividade. Se quisermos aumentar não múltiplos, haverá tempo de inatividade, pois não conseguiremos dividir tudo com precisão e sem perdas. Essas opções são úteis, mas nem sempre.

Se precisarmos adicionar ou remover um número arbitrário de servidores, usamos Hashing consistente no ringue à la Ketama. Mas, ao mesmo tempo, perdemos completamente a localidade dos dados; temos que mesclar a solicitação ao cluster para que cada parte retorne sua própria pequena resposta e, em seguida, mesclar as respostas ao proxy.

Existem solicitações superespecíficas. É assim: o proxy RPC recebe a solicitação, determina para qual cluster ir e determina o fragmento. Depois, há mestres de gravação ou, se o cluster tiver suporte para réplica, ele envia para uma réplica sob demanda. O proxy faz tudo isso.

FAQ sobre arquitetura e trabalho do VKontakte

Logs

Escrevemos logs de várias maneiras. O mais óbvio e simples é gravar logs no memcache.

ring-buffer: prefix.idx = line

Existe um prefixo chave - o nome do log, uma linha, e existe o tamanho desse log - o número de linhas. Pegamos um número aleatório de 0 ao número de linhas menos 1. A chave no memcache é um prefixo concatenado com esse número aleatório. Salvamos a linha de log e a hora atual no valor.

Quando for necessário ler logs, realizamos Obtenção múltipla todas as chaves, ordenadas por hora, e assim obter um log de produção em tempo real. O esquema é usado quando você precisa depurar algo em produção em tempo real, sem quebrar nada, sem parar ou permitir tráfego para outras máquinas, mas esse log não dura muito.

Para armazenamento confiável de logs, temos um mecanismo mecanismo de logs. É exatamente por isso que foi criado e é amplamente utilizado em um grande número de clusters. O maior cluster que conheço armazena 600 TB de logs compactados.

O motor é muito antigo, há clusters que já têm de 6 a 7 anos. Há problemas que estamos tentando resolver, por exemplo, começamos a usar ativamente o ClickHouse para armazenar logs.

Coletando registros no ClickHouse

Este diagrama mostra como entramos em nossos motores.

FAQ sobre arquitetura e trabalho do VKontakte

Existe um código que vai localmente via RPC para o proxy RPC e ele entende para onde ir para o mecanismo. Se quisermos escrever logs no ClickHouse, precisamos alterar duas partes deste esquema:

  • substituir algum motor pelo ClickHouse;
  • substitua o proxy RPC, que não pode acessar o ClickHouse, por alguma solução que possa, e via RPC.

O mecanismo é simples - nós o substituímos por um servidor ou cluster de servidores com ClickHouse.

E para ir para ClickHouse, fizemos Casa dos gatinhos. Se formos diretamente do KittenHouse para o ClickHouse, não vai dar certo. Mesmo sem solicitações, ele resulta de conexões HTTP de um grande número de máquinas. Para que o esquema funcione, em um servidor com ClickHouse proxy reverso local é gerado, que é escrito de forma que possa suportar os volumes necessários de conexões. Ele também pode armazenar dados em buffer dentro de si de forma relativamente confiável.

FAQ sobre arquitetura e trabalho do VKontakte

Às vezes não queremos implementar o esquema RPC em soluções não padronizadas, por exemplo, no nginx. Portanto, KittenHouse tem a capacidade de receber logs via UDP.

FAQ sobre arquitetura e trabalho do VKontakte

Se o remetente e o destinatário dos logs trabalharem na mesma máquina, a probabilidade de perder um pacote UDP no host local será bastante baixa. Como compromisso entre a necessidade de implementar RPC em uma solução de terceiros e a confiabilidade, simplesmente utilizamos o envio UDP. Voltaremos a este esquema mais tarde.

Monitoramento

Temos dois tipos de logs: aqueles coletados pelos administradores em seus servidores e aqueles escritos pelos desenvolvedores a partir do código. Eles correspondem a dois tipos de métricas: sistema e produto.

Métricas do sistema

Funciona em todos os nossos servidores Dados de rede, que coleta estatísticas e as envia para Carbono Grafite. Portanto, o ClickHouse é usado como sistema de armazenamento, e não o Whisper, por exemplo. Se necessário, você pode ler diretamente no ClickHouse ou usar grafana para métricas, gráficos e relatórios. Como desenvolvedores, temos acesso suficiente ao Netdata e ao Grafana.

Métricas do produto

Por conveniência, escrevemos muitas coisas. Por exemplo, há um conjunto de funções comuns que permitem gravar valores de Counts e UniqueCounts em estatísticas, que são enviadas para algum lugar mais adiante.

statlogsCountEvent   ( ‘stat_name’,            $key1, $key2, …)
statlogsUniqueCount ( ‘stat_name’, $uid,    $key1, $key2, …)
statlogsValuetEvent  ( ‘stat_name’, $value, $key1, $key2, …)

$stats = statlogsStatData($params)

Posteriormente, podemos usar filtros de classificação e agrupamento e fazer tudo o que quisermos com estatísticas - construir gráficos, configurar Watchdogs.

Nós escrevemos muito muitas métricas o número de eventos é de 600 bilhões a 1 trilhão por dia. No entanto, queremos mantê-los pelo menos alguns anospara entender tendências em métricas. Juntar tudo isso é um grande problema que ainda não resolvemos. Vou contar como tem funcionado nos últimos anos.

Temos funções que escrevem essas métricas para o memcache localpara reduzir o número de entradas. Uma vez em um curto período de tempo lançado localmente daemon de estatísticas coleta todos os registros. Em seguida, o demônio mescla as métricas em duas camadas de servidores coletores de registros, que agrega estatísticas de várias de nossas máquinas para que a camada por trás delas não morra.

FAQ sobre arquitetura e trabalho do VKontakte

Se necessário, podemos escrever diretamente para coletores de logs.

FAQ sobre arquitetura e trabalho do VKontakte

Mas escrever do código diretamente para os coletores, ignorando o stas-daemom, é uma solução pouco escalonável porque aumenta a carga no coletor. A solução só é adequada se, por algum motivo, não conseguirmos ativar o daemon de estatísticas do memcache na máquina ou se ela travou e fomos diretamente.

Em seguida, os coletores de logs mesclam as estatísticas em miauDB - este é o nosso banco de dados, que também pode armazenar métricas.

FAQ sobre arquitetura e trabalho do VKontakte

Então podemos fazer seleções binárias “near-SQL” a partir do código.

FAQ sobre arquitetura e trabalho do VKontakte

Experiência

No verão de 2018, fizemos um hackathon interno e surgiu a ideia de tentar substituir a parte vermelha do diagrama por algo que pudesse armazenar métricas no ClickHouse. Temos registros no ClickHouse - por que não tentar?

FAQ sobre arquitetura e trabalho do VKontakte

Tínhamos um esquema que gravava logs por meio da KittenHouse.

FAQ sobre arquitetura e trabalho do VKontakte

Nós decidimos adicione outra “*Casa” ao diagrama, que receberá exatamente as métricas no formato conforme nosso código as grava via UDP. Então esta *House os transforma em inserções, como toras, que a KittenHouse entende. Ele pode entregar perfeitamente esses logs ao ClickHouse, que deverá ser capaz de lê-los.

FAQ sobre arquitetura e trabalho do VKontakte

O esquema com banco de dados memcache, stats-daemon e logs-collectors foi substituído por este.

FAQ sobre arquitetura e trabalho do VKontakte

O esquema com banco de dados memcache, stats-daemon e logs-collectors foi substituído por este.

  • Há um despacho do código aqui, que é escrito localmente no StatsHouse.
  • StatsHouse grava métricas UDP, já convertidas em inserções SQL, no KittenHouse em lotes.
  • KittenHouse os envia para ClickHouse.
  • Se quisermos lê-los, nós os lemos ignorando o StatsHouse - diretamente do ClickHouse usando SQL normal.

Ainda é experiência, mas gostamos do resultado. Se resolvermos os problemas do esquema, talvez possamos mudar completamente para ele. Pessoalmente, espero que sim.

Condução não economiza ferro. Menos servidores são necessários, daemons de estatísticas locais e coletores de logs não são necessários, mas ClickHouse requer um servidor maior do que aqueles no esquema atual. São necessários menos servidores, mas eles devem ser mais caros e mais poderosos.

Implantar

Primeiro, vamos dar uma olhada na implantação do PHP. Estamos desenvolvendo em git: usar GitLab и TeamCity para implantação. Os ramos de desenvolvimento são mesclados no ramo mestre, do mestre para teste eles são mesclados no teste e do teste na produção.

Antes da implantação, a ramificação de produção atual e a anterior são obtidas, e os arquivos diff são considerados nelas - alterações: criadas, excluídas, alteradas. Essa alteração é registrada no log binário de um mecanismo copyfast especial, que pode replicar rapidamente as alterações em toda a nossa frota de servidores. O que é usado aqui não é copiar diretamente, mas replicação de fofoca, quando um servidor envia alterações para seus vizinhos mais próximos, para seus vizinhos e assim por diante. Isso permite atualizar o código em dezenas e unidades de segundos em toda a frota. Quando a mudança atinge a réplica local, ela aplica esses patches aos seus sistema de arquivos local. A reversão também é realizada de acordo com o mesmo esquema.

Também implantamos muito o kPHP e ele também tem seu próprio desenvolvimento em git de acordo com o diagrama acima. Desde isso Binário do servidor HTTP, então não poderemos produzir diff - o binário de lançamento pesa centenas de MB. Portanto, há outra opção aqui - a versão foi escrita para copyfast do binlog. A cada construção ele aumenta e durante a reversão também aumenta. Versão replicado para servidores. Os copyfasts locais veem que uma nova versão entrou no binlog e, pela mesma replicação de fofoca, eles pegam a versão mais recente do binário para si, sem cansar nosso servidor mestre, mas espalhando cuidadosamente a carga pela rede. O que se segue relançamento elegante para a nova versão.

Para nossos motores, que também são essencialmente binários, o esquema é muito semelhante:

  • ramo mestre git;
  • binário em . Deb;
  • a versão é gravada no binlog copyfast;
  • replicado para servidores;
  • o servidor extrai um novo .dep;
  • dpkg -eu;
  • relançamento elegante para nova versão.

A diferença é que nosso binário é empacotado em arquivos . Deb, e ao bombear eles dpkg -eu são colocados no sistema. Por que o kPHP é implantado como binário e os motores são implantados como dpkg? Aconteceu assim. Funciona - não toque nele.

Links úteis:

Alexey Akulovich é um daqueles que, como parte do Comitê do Programa, ajuda PHP Rússia no dia 17 de maio se tornará o maior evento para desenvolvedores PHP dos últimos tempos. Olha que PC legal que temos, o que caixas de som (dois deles estão desenvolvendo o núcleo do PHP!) - parece algo que você não pode perder se escrever PHP.

Fonte: habr.com

Adicionar um comentário