Preguntas frecuentes sobre arquitectura e traballo de VKontakte

A historia da creación de VKontakte está na Wikipedia; foi contada polo propio Pavel. Parece que xa a coñecen todos. Sobre os elementos internos, a arquitectura e a estrutura do sitio en HighLoad++ Pavel díxome en 2010. Moitos servidores filtáronse dende entón, polo que actualizaremos a información: diseccionarémola, sacarémola por dentro, pesarémola e miraremos o dispositivo VK dende o punto de vista técnico.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Alexei Akulovich (AterCattus) desenvolvedor de backend no equipo de VKontakte. A transcrición deste informe é unha resposta colectiva ás preguntas máis frecuentes sobre o funcionamento da plataforma, a infraestrutura, os servidores e a interacción entre eles, pero non sobre o desenvolvemento, é dicir. sobre o ferro. Por separado, sobre as bases de datos e o que ten VK, sobre a recollida de rexistros e o seguimento de todo o proxecto no seu conxunto. Detalles baixo o corte.



Levo máis de catro anos lidiando con todo tipo de tarefas relacionadas co backend.

  • Carga, almacenamento, procesamento, distribución de medios: vídeo, transmisión en directo, audio, fotos, documentos.
  • Infraestrutura, plataforma, monitorización de desenvolvedores, rexistros, cachés rexionais, CDN, protocolo RPC propietario.
  • Integración con servizos externos: notificacións push, análise de ligazóns externas, fonte RSS.
  • Axudar aos compañeiros con varias preguntas, cuxas respostas requiren mergullarse en código descoñecido.

Durante este tempo, tiven unha man en moitos compoñentes do sitio. Quero compartir esta experiencia.

Arquitectura xeral

Todo, como é habitual, comeza cun servidor ou grupo de servidores que aceptan solicitudes.

Servidor frontal

O servidor frontal acepta solicitudes a través de HTTPS, RTMP e WSS.

HTTPS - Son solicitudes para as versións web principal e móbil do sitio: vk.com e m.vk.com, e outros clientes oficiais e non oficiais da nosa API: clientes móbiles, mensaxeiros. Temos recepción RTMP-tráfico para transmisións en directo con servidores frontales separados e WSS- conexións para Streaming API.

Para HTTPS e WSS en servidores paga a pena Nginx. Para as emisións RTMP, cambiamos recentemente á nosa propia solución kive, pero está fóra do alcance do informe. Para tolerancia a fallos, estes servidores anuncian enderezos IP comúns e actúan en grupos para que, se hai algún problema nalgún dos servidores, non se perdan as solicitudes dos usuarios. Para HTTPS e WSS, estes mesmos servidores cifran o tráfico para tomar parte da carga da CPU.

Non falaremos máis de WSS e RTMP, senón só de solicitudes HTTPS estándar, que adoitan estar asociadas a un proxecto web.

motor

Detrás da fronte normalmente hai servidores backend. Procesan as solicitudes que o servidor frontal recibe dos clientes.

El servidores kPHP, no que se está a executar o daemon HTTP, porque HTTPS xa está descifrado. kPHP é un servidor que se executa modelos de prefork: inicia un proceso mestre, unha morea de procesos fillos, páselles sockets de escoita e procesan as súas solicitudes. Neste caso, os procesos non se reinician entre cada solicitude do usuario, senón que simplemente restablecen o seu estado ao estado orixinal de valor cero, solicitude tras solicitude, en lugar de reiniciarse.

Distribución de carga

Todos os nosos backends non son un gran conxunto de máquinas que poidan procesar calquera solicitude. Nós eles divididos en grupos separados: xeral, móbil, api, vídeo, posta en escena... O problema nun grupo separado de máquinas non afectará a todos os demais. En caso de problemas co vídeo, o usuario que escoita música nin sequera saberá os problemas. A que backend enviar a solicitude é decidido por nginx na parte frontal segundo a configuración.

Recollida métrica e reequilibrio

Para entender cantos coches necesitamos ter en cada grupo, nós non confíe en QPS. Os backends son diferentes, teñen solicitudes diferentes, cada solicitude ten unha complexidade diferente de calcular QPS. Por iso nós operamos co concepto de carga no servidor no seu conxunto: na CPU e no rendemento.

Temos miles deste tipo de servidores. Cada servidor físico executa un grupo kPHP para reciclar todos os núcleos (porque kPHP ten un único fío).

Servidor de contidos

CS ou Content Server é un almacenamento. CS é un servidor que almacena ficheiros e tamén procesa ficheiros cargados e todo tipo de tarefas sincrónicas en segundo plano que lle asigna a interface web principal.

Temos decenas de miles de servidores físicos que almacenan ficheiros. Aos usuarios encántalles cargar ficheiros e gústanos gardalos e compartilos. Algúns destes servidores están pechados por servidores especiais pu/pp.

pu/pp

Se abriches a pestana de rede en VK, viu pu/pp.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Que é pu/pp? Se pechamos un servidor tras outro, hai dúas opcións para cargar e descargar un ficheiro no servidor que estaba pechado: directamente través http://cs100500.userapi.com/path ou mediante un servidor intermedio - http://pu.vk.com/c100500/path.

Pu é o nome histórico para a carga de fotos e pp é o proxy de fotos. É dicir, un servidor é para subir fotos e outro para cargar. Agora non só se cargan fotos, senón que o nome conservouse.

Estes servidores finalizar sesións HTTPSpara eliminar a carga do procesador do almacenamento. Ademais, dado que nestes servidores se procesan os ficheiros de usuario, canta menos información sensible se almacene nestas máquinas, mellor. Por exemplo, chaves de cifrado HTTPS.

Dado que as máquinas están pechadas polas nosas outras máquinas, podemos permitirnos o luxo de non darlles IP externas "brancas" e dar "gris". Deste xeito, aforramos no grupo de IP e garantimos protexer as máquinas do acceso externo: simplemente non hai IP para entrar nel.

Resiliencia sobre IPs compartidas. En termos de tolerancia a fallos, o esquema funciona igual: varios servidores físicos teñen unha IP física común e o hardware que teñen diante elixe onde enviar a solicitude. Máis adiante falarei doutras opcións.

O punto controvertido é que neste caso o cliente mantén menos conexións. Se hai a mesma IP para varias máquinas, co mesmo host: pu.vk.com ou pp.vk.com, o navegador do cliente ten un límite no número de solicitudes simultáneas a un servidor. Pero na época do HTTP/2 omnipresente, creo que isto xa non é tan relevante.

A desvantaxe obvia do esquema é que ten que facelo bombear todo o tráfico, que vai ao almacenamento, a través doutro servidor. Dado que bombeamos o tráfico a través de máquinas, aínda non podemos bombear tráfico pesado, por exemplo, o vídeo, usando o mesmo esquema. Transmitimos directamente: unha conexión directa separada para almacenamentos separados específicamente para vídeo. Transmitimos contidos máis lixeiros a través dun proxy.

Non hai moito que obtivemos unha versión mellorada do proxy. Agora vouche dicir en que se diferencian dos comúns e por que é necesario.

Sol

En setembro de 2017, Oracle, que anteriormente comprara Sun, despediu a un gran número de empregados de Sun. Podemos dicir que neste momento a empresa deixou de existir. Ao elixir un nome para o novo sistema, os nosos administradores decidiron render homenaxe á memoria desta empresa e chamaron o novo sistema Sun. Entre nós simplemente chamámoslle "soles".

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

pp tivo algúns problemas. Unha IP por grupo: caché ineficaz. Varios servidores físicos comparten un enderezo IP común e non hai forma de controlar a que servidor irá a solicitude. Polo tanto, se diferentes usuarios veñen para o mesmo ficheiro, entón se hai unha caché nestes servidores, o ficheiro acaba na caché de cada servidor. Este é un esquema moi ineficiente, pero non se puido facer nada.

En consecuencia - non podemos fragmentar o contido, porque non podemos seleccionar un servidor específico para este grupo: teñen unha IP común. Tamén por algúns motivos internos temos non foi posible instalar tales servidores nas rexións. Estaban só en San Petersburgo.

Cos soles, cambiamos o sistema de selección. Agora temos enrutamento anycast: enrutamento dinámico, anycast, daemon de autocomprobación. Cada servidor ten a súa propia IP individual, pero unha subrede común. Todo está configurado de tal xeito que, se un servidor falla, o tráfico distribúese automaticamente polos outros servidores do mesmo grupo. Agora é posible seleccionar un servidor específico, sen caché redundante, e a fiabilidade non se viu afectada.

Soporte de peso. Agora podemos permitirnos o luxo de instalar máquinas de diferente potencia segundo sexa necesario, e tamén, en caso de problemas temporais, cambiar os pesos dos "soles" de traballo para reducir a carga sobre eles, para que "descansen" e comecen a traballar de novo.

Compartición por ID de contido. Unha cousa curiosa sobre o sharding: adoitamos dividir o contido para que distintos usuarios vaian ao mesmo ficheiro a través do mesmo “sol” para que teñan unha caché común.

Lanzamos recentemente a aplicación "Clover". Trátase dun cuestionario en liña nunha emisión en directo, onde o anfitrión fai preguntas e os usuarios responden en tempo real, escollendo opcións. A aplicación ten un chat onde os usuarios poden chatear. Pode conectarse simultaneamente á transmisión máis de 100 mil persoas. Todos escriben mensaxes que se envían a todos os participantes, e xunto coa mensaxe aparece un avatar. Se 100 mil persoas veñen por un avatar nun "sol", ás veces pode rodar detrás dunha nube.

Para soportar ráfagas de solicitudes para o mesmo ficheiro, é para un determinado tipo de contido que activamos un esquema estúpido que difunde ficheiros por todos os "soles" dispoñibles na rexión.

Sol dende dentro

Proxy inverso en nginx, caché en RAM ou en discos Optane/NVMe rápidos. Exemplo: http://sun4-2.userapi.com/c100500/path — unha ligazón ao "sol", que se atopa na cuarta rexión, o segundo grupo de servidores. Pecha o ficheiro de ruta, que se atopa fisicamente no servidor 100500.

Escondite

Engadimos un nodo máis ao noso esquema arquitectónico: o ambiente de caché.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Abaixo está o diagrama de disposición cachés rexionais, hai uns 20 deles. Estes son os lugares onde se atopan cachés e "soles", que poden almacenar o tráfico por si mesmos.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Trátase de almacenar na caché contido multimedia; aquí non se almacenan datos de usuario: só música, vídeos ou fotos.

Para determinar a rexión do usuario, nós recollemos prefixos de rede BGP anunciados nas rexións. No caso do fallback, tamén temos que analizar a base de datos xeoip se non puidemos atopar a IP por prefixos. Determinamos a rexión pola IP do usuario. No código, podemos mirar unha ou máis rexións do usuario: aqueles puntos aos que está máis próximo xeograficamente.

Como funciona isto?

Contamos a popularidade dos ficheiros por rexión. Hai un número de caché rexional onde se atopa o usuario e un identificador de ficheiro: tomamos este par e aumentamos a clasificación con cada descarga.

Ao mesmo tempo, os demos - servizos nas rexións - chegan de cando en vez á API e din: "Son tal ou tal caché, dáme unha lista dos ficheiros máis populares da miña rexión que aínda non están en min. ” A API ofrece unha morea de ficheiros ordenados por clasificación, o daemon descárgaos, lévaos ás rexións e envía os ficheiros desde alí. Esta é a diferenza fundamental entre pu/pp e Sun das cachés: transfiren o ficheiro por si mesmos inmediatamente, aínda que este non estea na caché, e a caché primeiro descarga o ficheiro para si mesmo e despois comeza a transferilo.

Neste caso conseguimos contidos máis próximos aos usuarios e repartindo a carga da rede. Por exemplo, só dende a caché de Moscova distribuímos máis de 1 Tbit/s durante as horas punta.

Pero hai problemas - os servidores de caché non son de goma. Para contido súper popular, ás veces non hai rede suficiente para un servidor separado. Os nosos servidores de caché son de 40 a 50 Gbit/s, pero hai contido que atasca por completo esa canle. Estamos avanzando para implementar o almacenamento de máis dunha copia de ficheiros populares na rexión. Espero que o poñamos en práctica a finais de ano.

Observamos a arquitectura xeral.

  • Servidores frontales que aceptan solicitudes.
  • Backends que procesan as solicitudes.
  • Almacenamentos que están pechados por dous tipos de proxies.
  • Cachés rexionais.

Que falta a este diagrama? Por suposto, as bases de datos nas que almacenamos os datos.

Bases de datos ou motores

Chamámoslles non bases de datos, senón motores - Motores, porque practicamente non temos bases de datos no sentido xeralmente aceptado.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Esta é unha medida necesaria. Isto ocorreu porque en 2008-2009, cando VK tivo un crecemento explosivo en popularidade, o proxecto funcionou completamente en MySQL e Memcache e houbo problemas. A MySQL encantáballe bloquear e corromper os ficheiros, despois de que non se recuperaría, e Memcache foi degradando gradualmente o seu rendemento e tivo que reiniciarse.

Resulta que o proxecto cada vez máis popular tiña un almacenamento persistente, que corrompe os datos, e unha caché, que ralentiza. En tales condicións, é difícil desenvolver un proxecto en crecemento. Decidiuse tentar reescribir as cousas críticas nas que se centraba o proxecto nas nosas propias bicicletas.

A solución foi exitosa. Houbo unha oportunidade para facelo, ademais dunha necesidade extrema, porque daquela non existían outras formas de escalar. Non había unha morea de bases de datos, NoSQL aínda non existía, só había MySQL, Memcache, PostrgreSQL e iso é todo.

Funcionamento universal. O desenvolvemento foi dirixido polo noso equipo de desenvolvedores C e todo se fixo de forma coherente. Independentemente do motor, todos tiñan aproximadamente o mesmo formato de ficheiro escrito no disco, os mesmos parámetros de lanzamento, procesaban os sinais da mesma forma e se comportaban aproximadamente igual en caso de situacións e problemas de borde. Co crecemento dos motores, é conveniente que os administradores operen o sistema: non hai que manter ningún zoolóxico e teñen que volver aprender a operar cada nova base de datos de terceiros, o que permitiu aumentar de forma rápida e cómoda. o seu número.

Tipos de motores

O equipo escribiu bastantes motores. Aquí tes só algúns deles: amigo, suxestións, imaxe, ipdb, cartas, listas, rexistros, memcached, meowdb, noticias, nostradamus, foto, listas de reprodución, pmemcached, sandbox, busca, almacenamento, gústame, tarefas,...

Para cada tarefa que require unha estrutura de datos específica ou procesa solicitudes atípicas, o equipo C escribe un novo motor. Por que non.

Temos un motor separado memcached, que é semellante a un normal, pero cunha chea de golosinas, e que non ralentiza. Non ClickHouse, pero tamén funciona. Dispoñible por separado pmemcached - É memcached persistente, que tamén pode almacenar datos no disco, ademais, do que cabe na memoria RAM, para non perder datos ao reiniciar. Hai varios motores para tarefas individuais: filas, listas, conxuntos, todo o que require o noso proxecto.

Clústeres

Desde a perspectiva do código, non hai que pensar nos motores ou bases de datos como procesos, entidades ou instancias. O código funciona especificamente con clusters, con grupos de motores - un tipo por clúster. Digamos que hai un clúster memcached: é só un grupo de máquinas.

O código non precisa saber a localización física, o tamaño ou o número de servidores. Vai ao clúster usando un determinado identificador.

Para que isto funcione, cómpre engadir unha entidade máis que estea situada entre o código e os motores: proxy.

Proxy RPC

Proxy bus de conexión, no que funciona case todo o sitio. Ao mesmo tempo temos ningún descubrimento de servizo — en cambio, hai unha configuración para este proxy, que coñece a localización de todos os clústeres e todos os fragmentos deste clúster. Isto é o que fan os administradores.

Aos programadores non lles importa en absoluto canto, onde e o que custa: só van ao clúster. Isto permítenos moito. Ao recibir unha solicitude, o proxy redirixe a solicitude, sabendo onde, o determina por si mesmo.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Neste caso, o proxy é un punto de protección contra fallos do servizo. Se algún motor ralentiza ou falla, entón o proxy enténdeo e responde en consecuencia ao lado do cliente. Isto permítelle eliminar o tempo de espera: o código non espera a que o motor responda, pero entende que non funciona e debe comportarse dun xeito diferente. O código debe estar preparado para o feito de que as bases de datos non sempre funcionan.

Implementacións específicas

Ás veces aínda queremos ter algún tipo de solución non estándar como motor. Ao mesmo tempo, decidiuse non usar o noso proxy rpc-ready, creado especificamente para os nosos motores, senón facer un proxy separado para a tarefa.

Para MySQL, que aínda temos aquí e alí, usamos db-proxy, e para ClickHouse - Gatinho.

Xeralmente funciona así. Hai un servidor determinado, executa kPHP, Go, Python; en xeral, calquera código que poida usar o noso protocolo RPC. O código execútase localmente nun proxy RPC: cada servidor onde se atopa o código executa o seu propio proxy local. Cando o solicite, o apoderado entende onde ir.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Se un motor quere ir a outro, aínda que sexa un veciño, pasa por un proxy, porque o veciño pode estar noutro centro de datos. O motor non debe depender de coñecer a localización doutro xeito que non sexa el mesmo: esta é a nosa solución estándar. Pero claro que hai excepcións :)

Un exemplo dun esquema TL segundo o cal funcionan todos os motores.

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 é un protocolo binario, cuxo análogo máis próximo é protobuf. O esquema prescribe campos opcionais, tipos complexos - extensións de escalares incorporados e consultas. Todo funciona segundo este protocolo.

RPC sobre TL sobre TCP/UDP... UDP?

Temos un protocolo RPC para executar solicitudes de motor que se executa enriba do esquema TL. Todo isto funciona a través dunha conexión TCP/UDP. TCP é comprensible, pero por que necesitamos UDP a miúdo?

UDP axuda evitar o problema dunha gran cantidade de conexións entre servidores. Se cada servidor ten un proxy RPC e, en xeral, pode ir a calquera motor, entón hai decenas de miles de conexións TCP por servidor. Hai unha carga, pero é inútil. No caso de UDP este problema non existe.

Non hai un apretón de mans TCP redundante. Este é un problema típico: cando se inicia un novo motor ou un novo servidor, establécense moitas conexións TCP á vez. Para pequenas solicitudes lixeiras, por exemplo, carga útil UDP, toda a comunicación entre o código e o motor é dous paquetes UDP: un voa nunha dirección, o segundo na outra. Unha viaxe de ida e volta - e o código recibiu unha resposta do motor sen un apretón de mans.

Si, todo funciona cunha porcentaxe moi pequena de perda de paquetes. O protocolo ten soporte para retransmisións e tempo de espera, pero se perdemos moito, conseguiremos case TCP, o que non é beneficioso. Non impulsamos UDP a través dos océanos.

Temos miles de servidores deste tipo, e o esquema é o mesmo: un paquete de motores está instalado en cada servidor físico. Son na súa maioría de fío único para executarse o máis rápido posible sen bloquealos, e son fragmentados como solucións de fío único. Ao mesmo tempo, non temos nada máis fiable que estes motores e préstase moita atención ao almacenamento de datos persistente.

Almacenamento de datos persistente

Os motores escriben binlogs. Un binlog é un ficheiro ao final do cal se engade un evento para un cambio de estado ou de datos. En diferentes solucións chámase de forma diferente: rexistro binario, WAL, AOF, pero o principio é o mesmo.

Para evitar que o motor volva ler todo o binlog durante moitos anos ao reiniciar, os motores escriben instantáneas - estado actual. Se é necesario, primeiro len nel e despois rematan de ler desde o binlog. Todos os binlogs están escritos no mesmo formato binario, segundo o esquema TL, para que os administradores poidan administralos por igual coas súas ferramentas. Non hai tal necesidade de instantáneas. Hai unha cabeceira xeral que indica quen é a instantánea int, a maxia do motor e que corpo non é importante para ninguén. Este é un problema co motor que gravou a instantánea.

Describirei rapidamente o principio de funcionamento. Hai un servidor no que funciona o motor. Abre un novo binlog baleiro para escribir e escribe un evento para cambialo.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Nalgún momento, ou decide tomar unha instantánea el mesmo ou recibe un sinal. O servidor crea un ficheiro novo, escribe nel todo o seu estado, engade o tamaño do binlog actual - desplazamento - ao final do ficheiro e continúa escribindo. Non se creou un novo binlog.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Nalgún momento, cando se reinicie o motor, haberá tanto un binlog como unha instantánea no disco. O motor le toda a instantánea e eleva o seu estado nun momento determinado.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Le a posición que estaba no momento en que se creou a instantánea e o tamaño do binlog.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Le o final do binlog para obter o estado actual e continúa escribindo máis eventos. Este é un esquema sinxelo; todos os nosos motores funcionan segundo el.

Replicación de datos

Como resultado, a replicación de datos no noso baseado en declaracións — escribimos no binlog non calquera cambio de páxina, pero é dicir solicitudes de cambio. Moi parecido ao que vén pola rede, só lixeiramente modificado.

O mesmo esquema úsase non só para a replicación, senón tamén para crear copias de seguridade. Temos un motor: un mestre de escritura que escribe no binlog. En calquera outro lugar onde o configuren os administradores, cópiase este binlog e xa está: temos unha copia de seguridade.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Se fai falta réplica de lecturaPara reducir a carga de lectura da CPU, simplemente lánzase o motor de lectura, que le o final do binlog e executa estes comandos localmente.

O atraso aquí é moi pequeno, e é posible descubrir canto queda a réplica por detrás do mestre.

Compartimento de datos no proxy RPC

Como funciona o sharding? Como entende o proxy a que fragmento de clúster enviar? O código non di: "Envía por 15 fragmentos!" - Non, isto faise polo proxy.

O esquema máis sinxelo é firstint - O primeiro número da solicitude.

get(photo100_500) => 100 % N.

Este é un exemplo dun protocolo de texto memcache simple, pero, por suposto, as consultas poden ser complexas e estruturadas. O exemplo toma o primeiro número da consulta e o resto cando se divide polo tamaño do clúster.

Isto é útil cando queremos ter a localidade dos datos dunha única entidade. Digamos que 100 é un ID de usuario ou grupo e queremos que todos os datos dunha entidade estean nun fragmento para consultas complexas.

Se non nos importa como se reparten as solicitudes polo clúster, hai outra opción: cortando todo o fragmento.

hash(photo100_500) => 3539886280 % N

Tamén obtemos o hash, o resto da división e o número de fragmento.

Estas dúas opcións só funcionan se estamos preparados para o feito de que cando aumentemos o tamaño do clúster, dividirémolo ou aumentaremos varias veces. Por exemplo, tivemos 16 fragmentos, non temos o suficiente, queremos máis; podemos conseguir 32 con seguridade sen tempo de inactividade. Se queremos aumentar non múltiplos, haberá tempo de inactividade, porque non poderemos dividir todo con precisión sen perdas. Estas opcións son útiles, pero non sempre.

Se necesitamos engadir ou eliminar un número arbitrario de servidores, usamos Hashing consistente no ring á Ketama. Pero ao mesmo tempo, perdemos completamente a localización dos datos; temos que fusionar a solicitude ao clúster para que cada peza devolva a súa propia pequena resposta e, a continuación, fusionar as respostas ao proxy.

Hai solicitudes superespecíficas. Parece así: o proxy RPC recibe a solicitude, determina a que clúster ir e determina o fragmento. Entón hai mestres de escritura ou, se o clúster ten soporte de réplica, envía a unha réplica baixo demanda. O proxy fai todo isto.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Rexistros

Escribimos rexistros de varias maneiras. O máis obvio e sinxelo é escribir rexistros en memcache.

ring-buffer: prefix.idx = line

Hai un prefixo clave: o nome do rexistro, unha liña e o tamaño deste rexistro: o número de liñas. Tomamos un número aleatorio de 0 ata o número de liñas menos 1. A clave en memcache é un prefixo concatenado con este número aleatorio. Gardamos a liña de rexistro e a hora actual no valor.

Cando é necesario ler rexistros, realizamos Multi Get todas as claves, ordenadas por tempo, e así obter un rexistro de produción en tempo real. O esquema utilízase cando precisa depurar algo en produción en tempo real, sen romper nada, sen deter ou permitir o tráfico a outras máquinas, pero este rexistro non dura moito.

Para almacenar de forma fiable os rexistros temos un motor motor de rexistros. É precisamente por iso que se creou e úsase amplamente nun gran número de clusters. O clúster máis grande que coñezo almacena 600 TB de rexistros empaquetados.

O motor é moi antigo, hai racimos que xa teñen 6-7 anos. Hai problemas con el que estamos tentando resolver, por exemplo, comezamos a usar ClickHouse activamente para almacenar rexistros.

Recopilación de rexistros en ClickHouse

Este diagrama mostra como entramos nos nosos motores.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Hai código que vai localmente a través de RPC ao proxy RPC e comprende onde ir ao motor. Se queremos escribir rexistros en ClickHouse, necesitamos cambiar dúas partes neste esquema:

  • substituír algún motor con ClickHouse;
  • substituír o proxy RPC, que non pode acceder a ClickHouse, por algunha solución que poida, e a través de RPC.

O motor é sinxelo: substitúímolo por un servidor ou un clúster de servidores con ClickHouse.

E para ir a ClickHouse, fixemos Casa dos Gatiños. Se pasamos directamente de KittenHouse a ClickHouse, non vai facer fronte. Mesmo sen solicitudes, súmase a partir de conexións HTTP dun gran número de máquinas. Para que o esquema funcione, nun servidor con ClickHouse suscita un proxy inverso local, que está escrito de forma que poida soportar os volumes de conexións necesarios. Tamén pode almacenar datos dentro de si de forma relativamente fiable.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Ás veces non queremos implementar o esquema RPC en solucións non estándar, por exemplo, en nginx. Polo tanto, KittenHouse ten a capacidade de recibir rexistros a través de UDP.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Se o remitente e o destinatario dos rexistros traballan na mesma máquina, entón a probabilidade de perder un paquete UDP dentro do host local é bastante baixa. Como compromiso entre a necesidade de implementar RPC nunha solución de terceiros e a fiabilidade, simplemente usamos o envío UDP. Volveremos a este esquema máis adiante.

Seguimento

Temos dous tipos de rexistros: os que recollen os administradores nos seus servidores e os escritos polos desenvolvedores a partir de código. Corresponden a dous tipos de métricas: sistema e produto.

Métricas do sistema

Funciona en todos os nosos servidores netdata, que recolle estatísticas e as envía a Carbono Grafito. Polo tanto, ClickHouse úsase como sistema de almacenamento, e non Whisper, por exemplo. Se é necesario, podes ler directamente desde ClickHouse ou usar grafana para métricas, gráficos e informes. Como desenvolvedores, temos acceso suficiente a Netdata e Grafana.

Métricas do produto

Por comodidade, escribimos moitas cousas. Por exemplo, hai un conxunto de funcións ordinarias que che permiten escribir Contas, valores UniqueCounts en estatísticas, que se envían a algún lugar máis lonxe.

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 clasificación e agrupación e facer todo o que queiramos das estatísticas: construír gráficos, configurar Watchdogs.

Escribimos moito moitas métricas o número de eventos é de 600 mil millóns a 1 billón por día. Non obstante, queremos mantelos polo menos un par de anoscomprender tendencias en métricas. Combinalo todo é un gran problema que aínda non solucionamos. Vouvos contar como está a funcionar durante os últimos anos.

Temos funcións que escriben estas métricas ao memcache localpara reducir o número de entradas. Unha vez nun curto período de tempo lanzado localmente stats-daemon recolle todos os rexistros. A continuación, o demo fusiona as métricas en dúas capas de servidores recolectores de troncos, que agrega estatísticas dun grupo das nosas máquinas para que a capa que hai detrás delas non morra.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Se é necesario, podemos escribir directamente nos rexistros-colectores.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Pero escribir desde o código directamente aos colectores, evitando stas-daemom, é unha solución pouco escalable porque aumenta a carga do colector. A solución só é axeitada se por algún motivo non podemos levantar o daemon stats-daemon de memcache na máquina, ou fallou e fomos directamente.

A continuación, os recopiladores de rexistros combinan as estatísticas en meowDB - esta é a nosa base de datos, que tamén pode almacenar métricas.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Despois podemos facer seleccións binarias "preto de SQL" do código.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Experiencia

No verán de 2018, tivemos un hackathon interno e xurdiu a idea de tentar substituír a parte vermella do diagrama por algo que puidese almacenar métricas en ClickHouse. Temos rexistros en ClickHouse - por que non probalo?

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Tiñamos un esquema que escribía rexistros a través de KittenHouse.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

Decidimos engade outra "*Casa" ao diagrama, que recibirá exactamente as métricas no formato como o noso código as escribe a través de UDP. Entón esta *House convérteas en insercións, como rexistros, que KittenHouse entende. Pode entregar perfectamente estes rexistros a ClickHouse, que debería poder lelos.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

O esquema con memcache, stats-daemon e logs-collectors base de datos substitúese por este.

Preguntas frecuentes sobre arquitectura e traballo de VKontakte

O esquema con memcache, stats-daemon e logs-collectors base de datos substitúese por este.

  • Hai un envío do código aquí, que está escrito localmente en StatsHouse.
  • StatsHouse escribe métricas UDP, xa convertidas en insercións SQL, en KittenHouse por lotes.
  • KittenHouse envíanos a ClickHouse.
  • Se queremos lelos, leémolos sen pasar a StatsHouse, directamente desde ClickHouse usando SQL normal.

Aínda está un experimento, pero gústanos como queda. Se solucionamos os problemas co esquema, quizais cambiemos a el por completo. Persoalmente, espero que si.

O esquema non aforra ferro. Necesítanse menos servidores, non se necesitan datos locais de estatísticas e recopiladores de rexistros, pero ClickHouse require un servidor máis grande que os do esquema actual. Necesítanse menos servidores, pero deben ser máis caros e poderosos.

Implantar

En primeiro lugar, vexamos o despregue de PHP. Estamos a desenvolver en ir: usar GitLab и TeamCity para o despregamento. As ramas de desenvolvemento únense na rama mestra, desde a mestra para probar únense na posta en escena e desde a posta en escena na produción.

Antes da implantación, tómanse a rama de produción actual e a anterior, e considéranse nelas os ficheiros diff - cambios: creado, eliminado, modificado. Este cambio rexístrase no binlog dun motor especial de copia rápida, que pode replicar rapidamente os cambios en toda a nosa flota de servidores. O que se usa aquí non é copiar directamente, senón replicación de fofocas, cando un servidor envía cambios aos seus veciños máis próximos, aqueles aos seus veciños, etc. Isto permítelle actualizar o código en decenas e unidades de segundos en toda a flota. Cando o cambio chega á réplica local, esta aplica estes parches á súa sistema de ficheiros local. A recuperación tamén se realiza segundo o mesmo esquema.

Tamén implementamos kPHP moito e tamén ten o seu propio desenvolvemento ir segundo o diagrama anterior. Dende isto Servidor HTTP binario, entón non podemos producir diff - o binario de lanzamento pesa centos de MB. Polo tanto, aquí hai outra opción: a versión está escrita binlog copyfast. Con cada compilación aumenta, e durante a recuperación tamén aumenta. Versión replicado en servidores. Os copyfasts locais ven que unha nova versión entrou no binlog e, coa mesma replicación de fofocas, toman a última versión do binario por si mesmos, sen cansar o noso servidor mestre, pero repartindo coidadosamente a carga pola rede. O que segue relanzamento gracioso para a nova versión.

Para os nosos motores, que tamén son esencialmente binarios, o esquema é moi semellante:

  • rama mestra de git;
  • binario en .deb;
  • a versión está escrita en binlog copyfast;
  • replicado en servidores;
  • o servidor saca un novo .dep;
  • dpkg -i;
  • relanzamento elegante á nova versión.

A diferenza é que o noso binario está empaquetado en arquivos .deb, e ao bombear eles dpkg -i están colocados no sistema. Por que se desprega kPHP como un binario e os motores se despregan como dpkg? Aconteceu así. Funciona, non o toques.

Ligazóns útiles:

Alexey Akulovich é un dos que, como parte do Comité do Programa, axuda PHP Rusia o 17 de maio converterase no maior evento para desenvolvedores de PHP dos últimos tempos. Mirade que PC tan chulo temos, que altofalantes (dous deles están a desenvolver o núcleo de PHP!) - Parece algo que non podes perder se escribes PHP.

Fonte: www.habr.com

Engadir un comentario