Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

La historia de la creación de VKontakte está en Wikipedia, la contó el propio Pavel. Parece que ya todo el mundo la conoce. Sobre el interior, la arquitectura y la estructura del sitio en HighLoad++ Pavel me lo dijo en 2010. Desde entonces se han filtrado muchos servidores, por lo que actualizaremos la información: lo diseccionaremos, sacaremos el interior, lo pesaremos y veremos el dispositivo VK desde un punto de vista técnico.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Alexey Akulovich (AterCattus) desarrollador backend en el equipo de VKontakte. La transcripción de este informe es una respuesta colectiva a preguntas frecuentes sobre el funcionamiento de la plataforma, la infraestructura, los servidores y la interacción entre ellos, pero no sobre el desarrollo, es decir sobre el hierro. Por separado, sobre las bases de datos y lo que tiene VK en su lugar, sobre la recopilación de registros y el seguimiento de todo el proyecto en su conjunto. Detalles bajo el corte.



Durante más de cuatro años me he ocupado de todo tipo de tareas relacionadas con el backend.

  • Carga, almacenamiento, procesamiento y distribución de medios: vídeo, transmisión en vivo, audio, fotografías, documentos.
  • Infraestructura, plataforma, monitoreo de desarrolladores, registros, cachés regionales, CDN, protocolo RPC propietario.
  • Integración con servicios externos: notificaciones push, análisis de enlaces externos, feed RSS.
  • Ayudar a colegas con diversas preguntas, cuyas respuestas requieren sumergirse en un código desconocido.

Durante este tiempo, participé en muchos componentes del sitio. Quiero compartir esta experiencia.

Arquitectura general

Todo, como es habitual, comienza con un servidor o grupo de servidores que aceptan solicitudes.

Servidor frontal

El servidor frontal acepta solicitudes a través de HTTPS, RTMP y WSS.

HTTPS - estas son solicitudes para las versiones web principal y móvil del sitio: vk.com y m.vk.com, y otros clientes oficiales y no oficiales de nuestra API: clientes móviles, mensajeros. tenemos una recepcion RTMP-tráfico para transmisiones en vivo con servidores frontales separados y WSS- conexiones para Streaming API.

Para HTTPS y WSS en servidores vale la pena nginx. Para transmisiones RTMP, recientemente cambiamos a nuestra propia solución. kive, pero está más allá del alcance del informe. Para lograr tolerancia a fallas, estos servidores anuncian direcciones IP comunes y actúan en grupos para que, si hay un problema en uno de los servidores, las solicitudes de los usuarios no se pierdan. Para HTTPS y WSS, estos mismos servidores cifran el tráfico para asumir parte de la carga de la CPU.

No hablaremos más sobre WSS y RTMP, sino solo sobre las solicitudes HTTPS estándar, que generalmente están asociadas con un proyecto web.

Backend

Detrás del frente suele haber servidores backend. Procesan las solicitudes que el servidor frontal recibe de los clientes.

Lo servidores kPHP, en el que se ejecuta el demonio HTTP, porque HTTPS ya está descifrado. kPHP es un servidor que se ejecuta en modelos de prehorquilla: inicia un proceso maestro, un grupo de procesos secundarios, les pasa sockets de escucha y ellos procesan sus solicitudes. En este caso, los procesos no se reinician entre cada solicitud del usuario, sino que simplemente restablecen su estado al estado original de valor cero, solicitud tras solicitud, en lugar de reiniciar.

Distribución de la carga

Todos nuestros backends no son un gran conjunto de máquinas que puedan procesar cualquier solicitud. nosotros ellos dividido en grupos separados: general, móvil, api, video, staging... El problema en un grupo separado de máquinas no afectará a todas las demás. En caso de problemas con el vídeo, el usuario que escucha música ni siquiera se enterará del problema. nginx decide a qué backend enviar la solicitud en el frente de acuerdo con la configuración.

Recopilación y reequilibrio de métricas

Para entender cuántos autos necesitamos tener en cada grupo, no confíes en QPS. Los backends son diferentes, tienen diferentes solicitudes y cada solicitud tiene una complejidad diferente para calcular el QPS. Por eso nosotros Operamos con el concepto de carga en el servidor en su conjunto: en la CPU y el rendimiento..

Tenemos miles de servidores de este tipo. Cada servidor físico ejecuta un grupo kPHP para reciclar todos los núcleos (porque kPHP es de un solo subproceso).

Servidor de contenido

CS o Content Server es un almacenamiento. CS es un servidor que almacena archivos y también procesa archivos cargados y todo tipo de tareas sincrónicas en segundo plano que le asigna la interfaz web principal.

Contamos con decenas de miles de servidores físicos que almacenan archivos. A los usuarios les encanta cargar archivos y a nosotros nos encanta almacenarlos y compartirlos. Algunos de estos servidores están cerrados por servidores pu/pp especiales.

pu/pp

Si abriste la pestaña de red en VK, viste pu/pp.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

¿Qué es pu/pp? Si cerramos un servidor tras otro, entonces hay dos opciones para cargar y descargar un archivo al servidor que se cerró: directamente a través de http://cs100500.userapi.com/path o a través del servidor intermedio - http://pu.vk.com/c100500/path.

Pu es el nombre histórico para la carga de fotografías y pp es el proxy de fotografías.. Es decir, un servidor es para cargar fotos y otro es para cargar. Ahora no solo se cargan las fotos, sino que también se conserva el nombre.

Estos servidores terminar sesiones HTTPSpara eliminar la carga del procesador del almacenamiento. Además, dado que los archivos de los usuarios se procesan en estos servidores, cuanto menos información confidencial se almacene en estas máquinas, mejor. Por ejemplo, claves de cifrado HTTPS.

Dado que las máquinas están cerradas por nuestras otras máquinas, podemos darnos el lujo de no darles IP externas "blancas", y dar "gris". De esta manera ahorramos en el grupo de IP y garantizamos la protección de las máquinas contra el acceso externo; simplemente no hay ninguna IP para ingresar.

Resiliencia sobre IP compartidas. En términos de tolerancia a fallas, el esquema funciona igual: varios servidores físicos tienen una IP física común y el hardware frente a ellos elige dónde enviar la solicitud. Hablaré de otras opciones más adelante.

El punto controvertido es que en este caso el cliente mantiene menos conexiones. Si existe la misma IP para varias máquinas, con el mismo host: pu.vk.com o pp.vk.com, el navegador del cliente tiene un límite en la cantidad de solicitudes simultáneas a un host. Pero en la época del omnipresente HTTP/2, creo que esto ya no es tan relevante.

La desventaja obvia del plan es que tiene que bombear todo el tráfico, que va al almacenamiento, a través de otro servidor. Dado que bombeamos tráfico a través de máquinas, todavía no podemos bombear tráfico pesado, por ejemplo, vídeo, utilizando el mismo esquema. Lo transmitimos directamente: una conexión directa separada para almacenamientos separados específicamente para video. Transmitimos contenidos más ligeros a través de un proxy.

No hace mucho obtuvimos una versión mejorada del proxy. Ahora te diré en qué se diferencian de los habituales y por qué es necesario.

Dom

En septiembre de 2017, Oracle, que anteriormente había comprado Sun, despidió a una gran cantidad de empleados de Sun. Podemos decir que en este momento la empresa dejó de existir. Al elegir un nombre para el nuevo sistema, nuestros administradores decidieron rendir homenaje a la memoria de esta empresa y llamaron al nuevo sistema Sun. Entre nosotros simplemente la llamamos “soles”.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

pp tuvo algunos problemas. Una IP por grupo: caché ineficaz. Varios servidores físicos comparten una dirección IP común y no hay forma de controlar a qué servidor irá la solicitud. Por lo tanto, si diferentes usuarios vienen por el mismo archivo, entonces si hay un caché en estos servidores, el archivo termina en el caché de cada servidor. Se trata de un plan muy ineficaz, pero no se puede hacer nada.

Como consecuencia - no podemos fragmentar el contenido, porque no podemos seleccionar un servidor específico para este grupo: tienen una IP común. También por algunas razones internas tenemos no fue posible instalar dichos servidores en regiones. Sólo se encontraban en San Petersburgo.

Con los soles cambiamos el sistema de selección. Ahora tenemos enrutamiento de cualquier difusión: enrutamiento dinámico, anycast, demonio de autocomprobación. Cada servidor tiene su propia IP individual, pero una subred común. Todo está configurado de tal manera que si un servidor falla, el tráfico se distribuye automáticamente entre los demás servidores del mismo grupo. Ahora es posible seleccionar un servidor específico, sin almacenamiento en caché redundantey la confiabilidad no se vio afectada.

soporte de peso. Ahora podemos permitirnos el lujo de instalar máquinas de diferente potencia según sea necesario, y también, en caso de problemas temporales, cambiar el peso de los “soles” de trabajo para reducir la carga sobre ellos, para que “descansen” y comiencen a trabajar nuevamente.

Fragmentación por ID de contenido. Algo curioso acerca de la fragmentación: generalmente fragmentamos el contenido para que diferentes usuarios accedan al mismo archivo a través del mismo "sol" para que tengan un caché común.

Recientemente lanzamos la aplicación “Clover”. Se trata de un cuestionario online transmitido en directo, donde el presentador hace preguntas y los usuarios responden en tiempo real, eligiendo opciones. La aplicación tiene un chat donde los usuarios pueden chatear. Puede conectarse simultáneamente a la transmisión. más de 100 mil personas. Todos escriben mensajes que se envían a todos los participantes y un avatar acompaña el mensaje. Si 100 mil personas vienen por un avatar en un "sol", a veces puede rodar detrás de una nube.

Para resistir ráfagas de solicitudes del mismo archivo, es para cierto tipo de contenido que activamos un estúpido esquema que distribuye archivos entre todos los “soles” disponibles en la región.

Sol desde el interior

Proxy inverso en nginx, caché en RAM o en discos rápidos Optane/NVMe. Ejemplo: http://sun4-2.userapi.com/c100500/path — un enlace al “sol”, que se encuentra en la cuarta región, el segundo grupo de servidores. Cierra el archivo de ruta, que se encuentra físicamente en el servidor 100500.

cache

Agregamos un nodo más a nuestro esquema arquitectónico: el entorno de almacenamiento en caché.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

A continuación se muestra el diagrama de diseño. cachés regionales, hay alrededor de 20 de ellos. Estos son los lugares donde se encuentran los cachés y los "soles", que pueden almacenar el tráfico a través de ellos mismos.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Se trata de un almacenamiento en caché de contenido multimedia; aquí no se almacenan datos del usuario, solo música, vídeos y fotos.

Para determinar la región del usuario, nosotros Recopilamos prefijos de red BGP anunciados en las regiones.. En el caso del respaldo, también tenemos que analizar la base de datos geoip si no pudimos encontrar la IP por prefijos. Determinamos la región por la IP del usuario.. En el código, podemos mirar una o más regiones del usuario, aquellos puntos a los que está más cerca geográficamente.

Como funciona?

Contamos la popularidad de los archivos por región.. Hay un número de caché regional donde se encuentra el usuario y un identificador de archivo; tomamos este par e incrementamos la calificación con cada descarga.

Al mismo tiempo, los demonios (servicios en regiones) de vez en cuando vienen a la API y dicen: “Soy tal o cual caché, dame una lista de los archivos más populares en mi región que aún no están en mí. " La API entrega un montón de archivos ordenados por clasificación, el demonio los descarga, los lleva a las regiones y entrega los archivos desde allí. Ésta es la diferencia fundamental entre pu/pp y Sun respecto a los cachés: ellos mismos entregan el archivo inmediatamente, incluso si este archivo no está en el caché, y el caché primero descarga el archivo en sí mismo y luego comienza a devolvérselo.

En este caso obtenemos Contenido más cerca de los usuarios. y distribuir la carga de la red. Por ejemplo, sólo desde la caché de Moscú distribuimos más de 1 Tbit/s durante las horas pico.

Pero hay problemas Los servidores de caché no son de goma.. Para contenido muy popular, a veces no hay suficiente red para un servidor independiente. Nuestros servidores de caché son de 40-50 Gbit/s, pero hay contenido que obstruye por completo dicho canal. Estamos avanzando hacia la implementación del almacenamiento de más de una copia de archivos populares en la región. Espero que lo implementemos antes de fin de año.

Observamos la arquitectura general.

  • Servidores frontales que aceptan solicitudes.
  • Backends que procesan solicitudes.
  • Almacenamientos que se cierran mediante dos tipos de proxies.
  • Cachés regionales.

¿Qué falta en este diagrama? Por supuesto, las bases de datos en las que almacenamos datos.

Bases de datos o motores

No los llamamos bases de datos, sino motores: motores, porque prácticamente no tenemos bases de datos en el sentido generalmente aceptado.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Esta es una medida necesaria.. Esto sucedió porque en 2008-2009, cuando VK tuvo un crecimiento explosivo en popularidad, el proyecto funcionó completamente en MySQL y Memcache y hubo problemas. A MySQL le encantaba fallar y corromper archivos, después de lo cual no se recuperaba, y Memcache gradualmente degradó su rendimiento y tuvo que reiniciarse.

Resulta que el proyecto cada vez más popular tenía almacenamiento persistente, que corrompe los datos, y un caché, que ralentiza. En tales condiciones, es difícil desarrollar un proyecto en crecimiento. Se decidió intentar reescribir los aspectos críticos en los que se centró el proyecto en nuestras propias bicicletas.

La solución fue exitosa.. Había una oportunidad de hacer esto, así como una necesidad extrema, porque en ese momento no existían otras formas de escalar. No había muchas bases de datos, NoSQL aún no existía, solo MySQL, Memcache, PostrgreSQL, y eso es todo.

Operación universal. El desarrollo fue dirigido por nuestro equipo de desarrolladores de C y todo se hizo de manera consistente. Independientemente del motor, todos tenían aproximadamente el mismo formato de archivo escrito en el disco, los mismos parámetros de inicio, procesaban señales de la misma manera y se comportaban aproximadamente de la misma manera en caso de situaciones límite y problemas. Con el crecimiento de los motores, es conveniente para los administradores operar el sistema: no hay ningún zoológico que deba mantenerse y tienen que volver a aprender a operar cada nueva base de datos de terceros, lo que hizo posible aumentar de manera rápida y conveniente su número.

tipos de motores

El equipo escribió bastantes motores. Estos son solo algunos de ellos: amigo, sugerencias, imagen, ipdb, cartas, listas, registros, memcached, meowdb, noticias, nostradamus, foto, listas de reproducción, pmemcached, sandbox, búsqueda, almacenamiento, me gusta, tareas,…

Para cada tarea que requiere una estructura de datos específica o procesa solicitudes atípicas, el equipo de C escribe un nuevo motor. Por qué no.

Tenemos un motor separado. memcached, que es similar a uno normal, pero con un montón de ventajas y que no se ralentiza. No ClickHouse, pero también funciona. Disponible por separado pmemcached - es memcached persistente, que también puede almacenar datos en el disco, más de lo que cabe en la RAM, para no perder datos al reiniciar. Hay varios motores para tareas individuales: colas, listas, conjuntos: todo lo que nuestro proyecto requiere.

Clústeres

Desde la perspectiva del código, no es necesario pensar en los motores o las bases de datos como procesos, entidades o instancias. El código funciona específicamente con clusters, con grupos de motores... un tipo por grupo. Digamos que hay un clúster de Memcached: es solo un grupo de máquinas.

El código no necesita conocer la ubicación física, el tamaño o la cantidad de servidores en absoluto. Va al clúster utilizando un identificador determinado.

Para que esto funcione, necesita agregar una entidad más que se encuentre entre el código y los motores: apoderado.

proxy RPC

Apoderado autobús de conexión, en el que se ejecuta casi todo el sitio. Al mismo tiempo tenemos sin descubrimiento de servicio — en cambio, hay una configuración para este proxy, que conoce la ubicación de todos los clústeres y todos los fragmentos de este clúster. Esto es lo que hacen los administradores.

A los programadores no les importa en absoluto cuánto, dónde y cuánto cuesta: simplemente van al clúster. Esto nos permite mucho. Al recibir una solicitud, el proxy redirige la solicitud, sabiendo dónde; lo determina él mismo.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

En este caso, el proxy es un punto de protección contra fallas del servicio. Si algún motor se ralentiza o falla, entonces el proxy lo entiende y responde en consecuencia al lado del cliente. Esto le permite eliminar el tiempo de espera: el código no espera a que el motor responda, pero comprende que no está funcionando y debe comportarse de alguna manera diferente. El código debe estar preparado para el hecho de que las bases de datos no siempre funcionan.

Implementaciones específicas

A veces todavía queremos tener algún tipo de solución no estándar como motor. Al mismo tiempo, se decidió no utilizar nuestro proxy rpc ya preparado, creado específicamente para nuestros motores, sino crear un proxy separado para la tarea.

Para MySQL, que todavía tenemos aquí y allá, usamos db-proxy, y para ClickHouse - casa del gatito.

Generalmente funciona así. Hay un servidor determinado que ejecuta kPHP, Go, Python; en general, cualquier código que pueda utilizar nuestro protocolo RPC. El código se ejecuta localmente en un proxy RPC: cada servidor donde se encuentra el código ejecuta su propio proxy local. Si lo solicita, el apoderado entiende adónde ir.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Si un motor quiere ir a otro, aunque sea vecino, lo hace a través de un proxy, porque el vecino puede estar en otro centro de datos. El motor no debe depender de conocer la ubicación de nada más que de sí mismo; esta es nuestra solución estándar. Pero claro que hay excepciones :)

Un ejemplo de un esquema TL según el cual funcionan todos los 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 es un protocolo binario, cuyo análogo más cercano es protobuf. El esquema predescribe campos opcionales, tipos complejos (extensiones de escalares integrados y consultas). Todo funciona según este protocolo.

RPC sobre TL sobre TCP/UDP… ¿UDP?

Tenemos un protocolo RPC para ejecutar solicitudes de motor que se ejecuta sobre el esquema TL. Todo esto funciona a través de una conexión TCP/UDP. TCP es comprensible, pero ¿por qué necesitamos UDP con frecuencia?

UDP ayuda evitar el problema de una gran cantidad de conexiones entre servidores. Si cada servidor tiene un proxy RPC y, en general, puede ir a cualquier motor, entonces hay decenas de miles de conexiones TCP por servidor. Hay una carga, pero es inútil. En el caso de UDP este problema no existe.

Sin protocolo de enlace TCP redundante. Este es un problema típico: cuando se lanza un nuevo motor o un nuevo servidor, se establecen muchas conexiones TCP a la vez. Para solicitudes pequeñas y ligeras, por ejemplo, carga útil UDP, toda la comunicación entre el código y el motor es dos paquetes UDP: uno vuela en una dirección, el segundo en la otra. Un viaje de ida y vuelta y el código recibió una respuesta del motor sin un apretón de manos.

Sí, todo simplemente funciona. con un porcentaje muy pequeño de pérdida de paquetes. El protocolo tiene soporte para retransmisiones y tiempos de espera, pero si perdemos mucho, obtendremos casi TCP, lo cual no es rentable. No llevamos UDP a través de océanos.

Tenemos miles de servidores de este tipo y el esquema es el mismo: se instala un paquete de motores en cada servidor físico. En su mayoría, son de un solo subproceso para ejecutarse lo más rápido posible sin bloquearse y están fragmentados como soluciones de un solo subproceso. Al mismo tiempo, no tenemos nada más confiable que estos motores y se presta mucha atención al almacenamiento persistente de datos.

Almacenamiento de datos persistente

Los motores escriben binlogs. Un binlog es un archivo al final del cual se agrega un evento de cambio de estado o de datos. En diferentes soluciones se llama de manera diferente: registro binario, WAL, AOF, pero el principio es el mismo.

Para evitar que el motor vuelva a leer el binlog completo durante muchos años al reiniciar, los motores escriben instantáneas - estado actual. Si es necesario, primero lo leen y luego terminan de leer del binlog. Todos los binlogs están escritos en el mismo formato binario, según el esquema TL, para que los administradores puedan administrarlos igualmente con sus herramientas. No existe tal necesidad de tomar instantáneas. Hay un encabezado general que indica de quién es la instantánea int, la magia del motor y qué cuerpo no es importante para nadie. Este es un problema con el motor que grabó la instantánea.

Describiré rápidamente el principio de funcionamiento. Hay un servidor en el que se ejecuta el motor. Abre un nuevo binlog vacío para escribir y escribe un evento para cambiarlo.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

En algún momento, decide tomar una instantánea él mismo o recibe una señal. El servidor crea un nuevo archivo, escribe su estado completo en él, agrega el tamaño actual del binlog (desplazamiento) al final del archivo y continúa escribiendo. No se crea un nuevo binlog.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

En algún momento, cuando el motor se reinicie, habrá un binlog y una instantánea en el disco. El motor lee la instantánea completa y eleva su estado en un punto determinado.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Lee la posición que estaba en el momento en que se creó la instantánea y el tamaño del binlog.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Lee el final del binlog para obtener el estado actual y continúa escribiendo más eventos. Este es un esquema simple, todos nuestros motores funcionan según él.

Replicación de datos

Como resultado, la replicación de datos en nuestro basado en declaraciones — no escribimos en el binlog ningún cambio de página, sino concretamente solicitudes de cambio. Muy similar a lo que viene por la red, sólo ligeramente modificado.

El mismo esquema se utiliza no sólo para la replicación, sino también para crear copias de seguridad. Tenemos un motor: un maestro de escritura que escribe en el binlog. En cualquier otro lugar donde los administradores lo configuren, este binlog se copia y listo: tenemos una copia de seguridad.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Si es necesario réplica de lecturaPara reducir la carga de lectura de la CPU, simplemente se inicia el motor de lectura, que lee el final del binlog y ejecuta estos comandos localmente.

El retraso aquí es muy pequeño y es posible averiguar cuánto está por detrás la réplica del maestro.

Fragmentación de datos en proxy RPC

¿Cómo funciona la fragmentación? ¿Cómo entiende el proxy a qué fragmento de clúster enviar? El código no dice: "¡Envíe por 15 fragmentos!" - No, esto lo hace el proxy.

El esquema más simple es firstint. — el primer número de la solicitud.

get(photo100_500) => 100 % N.

Este es un ejemplo de un protocolo de texto simple en Memcached, pero, por supuesto, las consultas pueden ser complejas y estructuradas. El ejemplo toma el primer número de la consulta y el resto cuando se divide por el tamaño del clúster.

Esto es útil cuando queremos tener la localidad de datos de una sola entidad. Digamos que 100 es un ID de usuario o grupo y queremos que todos los datos de una entidad estén en un fragmento para consultas complejas.

Si no nos importa cómo se distribuyen las solicitudes en el clúster, existe otra opción: hash de todo el fragmento.

hash(photo100_500) => 3539886280 % N

También obtenemos el hash, el resto de la división y el número del fragmento.

Ambas opciones solo funcionan si estamos preparados para el hecho de que cuando aumentemos el tamaño del clúster, lo dividiremos o lo aumentaremos varias veces. Por ejemplo, teníamos 16 fragmentos, no tenemos suficientes, queremos más; podemos obtener 32 de forma segura y sin tiempo de inactividad. Si no queremos aumentar múltiplos, habrá tiempo de inactividad, porque no podremos dividir todo con precisión sin pérdidas. Estas opciones son útiles, pero no siempre.

Si necesitamos agregar o eliminar una cantidad arbitraria de servidores, usamos Hash consistente en el ring a la Ketama. Pero al mismo tiempo, perdemos por completo la localidad de los datos; tenemos que fusionar la solicitud con el clúster para que cada pieza devuelva su propia pequeña respuesta y luego fusionar las respuestas con el proxy.

Hay solicitudes súper específicas. Tiene este aspecto: el proxy RPC recibe la solicitud, determina a qué clúster ir y determina el fragmento. Luego están los maestros de escritura o, si el clúster tiene soporte para réplicas, envía a una réplica a pedido. El proxy hace todo esto.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Registros

Escribimos registros de varias maneras. El más obvio y simple es escribir registros en memcache.

ring-buffer: prefix.idx = line

Hay un prefijo clave: el nombre del registro, una línea, y está el tamaño de este registro: el número de líneas. Tomamos un número aleatorio de 0 al número de líneas menos 1. La clave en Memcache es un prefijo concatenado con este número aleatorio. Guardamos la línea de registro y la hora actual en el valor.

Cuando es necesario leer logs, realizamos Obtener múltiples todas las claves, ordenadas por tiempo, y así obtener un registro de producción en tiempo real. El esquema se utiliza cuando necesitas depurar algo en producción en tiempo real, sin romper nada, sin detener ni permitir el tráfico a otras máquinas, pero este registro no dura mucho.

Para un almacenamiento fiable de troncos disponemos de un motor. motor de registros. Precisamente por eso fue creado y se utiliza ampliamente en una gran cantidad de grupos. El clúster más grande que conozco almacena 600 TB de registros empaquetados.

El motor es muy viejo, hay grupos que ya tienen entre 6 y 7 años. Hay problemas que estamos tratando de resolver, por ejemplo, comenzamos a usar ClickHouse activamente para almacenar registros.

Recopilación de registros en ClickHouse

Este diagrama muestra cómo entramos en nuestros motores.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Hay un código que va localmente a través de RPC al proxy RPC y comprende dónde ir al motor. Si queremos escribir registros en ClickHouse, necesitamos cambiar dos partes en este esquema:

  • reemplazar algún motor con ClickHouse;
  • reemplace el proxy RPC, que no puede acceder a ClickHouse, con alguna solución que sí pueda, y a través de RPC.

El motor es simple: lo reemplazamos con un servidor o un grupo de servidores con ClickHouse.

Y para ir a ClickHouse, lo hicimos casa del gatito. Si pasamos directamente de KittenHouse a ClickHouse, no da abasto. Incluso sin solicitudes, se acumula a partir de conexiones HTTP de una gran cantidad de máquinas. Para que el esquema funcione, en un servidor con ClickHouse Se genera el proxy inverso local., que está escrito de tal manera que pueda soportar los volúmenes requeridos de conexiones. También puede almacenar datos dentro de sí mismo de manera relativamente confiable.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

A veces no queremos implementar el esquema RPC en soluciones no estándar, por ejemplo, en nginx. Por lo tanto, KittenHouse tiene la capacidad de recibir registros a través de UDP.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Si el remitente y el destinatario de los registros trabajan en la misma máquina, entonces la probabilidad de perder un paquete UDP dentro del host local es bastante baja. Como compromiso entre la necesidad de implementar RPC en una solución de terceros y la confiabilidad, simplemente utilizamos el envío UDP. Volveremos sobre este esquema más adelante.

Monitoreo

Tenemos dos tipos de registros: los recopilados por los administradores en sus servidores y los escritos por los desarrolladores a partir de código. Corresponden a dos tipos de métricas: sistema y producto.

Métricas del sistema

Funciona en todos nuestros servidores. datos de red, que recopila estadísticas y las envía a Carbono de grafito. Por tanto, como sistema de almacenamiento se utiliza ClickHouse y no Whisper, por ejemplo. Si es necesario, puede leer directamente desde ClickHouse o utilizar Grafana para métricas, gráficos e informes. Como desarrolladores, tenemos suficiente acceso a Netdata y Grafana.

Métricas del producto

Por conveniencia, hemos escrito muchas cosas. Por ejemplo, existe un conjunto de funciones ordinarias que le permiten escribir valores Counts, UniqueCounts en estadísticas, que se envían a algún lugar más adelante.

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 y agrupación y hacer todo lo que queramos con las estadísticas: crear gráficos, configurar Watchdogs.

escribimos muy muchas métricas el número de eventos es de 600 mil millones a 1 billón por día. Sin embargo, queremos mantenerlos al menos un par de añospara comprender las tendencias en las métricas. Poniéndolo todo junto es un gran problema que aún no hemos resuelto. Te contaré cómo ha estado funcionando durante los últimos años.

Tenemos funciones que escriben estas métricas. a Memcache localpara reducir el número de entradas. Una vez en un corto período de tiempo lanzado localmente demonio de estadísticas recoge todos los registros. A continuación, el demonio fusiona las métricas en dos capas de servidores. recolectores de registros, que agrega estadísticas de un grupo de nuestras máquinas para que la capa detrás de ellas no muera.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Si es necesario, podemos escribir directamente en los recopiladores de registros.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Pero escribir desde el código directamente a los recopiladores, sin pasar por stas-daemom, es una solución poco escalable porque aumenta la carga en el recopilador. La solución es adecuada solo si por alguna razón no podemos activar el demonio de estadísticas de Memcache en la máquina, o si falló y fuimos directamente.

A continuación, los recopiladores de registros fusionan las estadísticas en miauDB - esta es nuestra base de datos, que también puede almacenar métricas.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Luego podemos hacer selecciones binarias “casi SQL” a partir del código.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Experimento

En el verano de 2018, tuvimos un hackathon interno y surgió la idea de intentar reemplazar la parte roja del diagrama con algo que pudiera almacenar métricas en ClickHouse. Tenemos registros en ClickHouse. ¿Por qué no probarlo?

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Teníamos un esquema que escribía registros a través de KittenHouse.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

Decidimos agregue otra “*Casa” al diagrama, que recibirá exactamente las métricas en el formato tal como nuestro código las escribe a través de UDP. Luego esta *House los convierte en insertos, como troncos, que KittenHouse entiende. Puede entregar perfectamente estos registros a ClickHouse, que debería poder leerlos.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

El esquema con la base de datos Memcache, stats-daemon y logs-collectors se reemplaza por este.

Preguntas frecuentes sobre arquitectura y trabajo de VKontakte

El esquema con la base de datos Memcache, stats-daemon y logs-collectors se reemplaza por este.

  • Hay un envío desde el código aquí, que está escrito localmente en StatsHouse.
  • StatsHouse escribe métricas UDP, ya convertidas en inserciones SQL, en KittenHouse en lotes.
  • KittenHouse los envía a ClickHouse.
  • Si queremos leerlos, entonces los leemos sin pasar por StatsHouse, directamente desde ClickHouse utilizando SQL normal.

Es todavía experimento, pero nos gusta cómo queda. Si solucionamos los problemas con el esquema, quizás lo cambiemos por completo. Personalmente, eso espero.

esquema no ahorra hierro. Se necesitan menos servidores, no se necesitan demonios de estadísticas ni recolectores de registros locales, pero ClickHouse requiere un servidor más grande que los del esquema actual. Se necesitan menos servidores, pero deben ser más caros y potentes.

Desplegar

Primero, veamos la implementación de PHP. Nos estamos desarrollando en git: usar GitLab и TeamCity para el despliegue. Las ramas de desarrollo se fusionan en la rama maestra, desde la maestra para pruebas se fusionan en la etapa de prueba y de la etapa de prueba en producción.

Antes de la implementación, se toman la rama de producción actual y la anterior, y en ellas se consideran los archivos de diferencias: cambios: creados, eliminados, modificados. Este cambio se registra en el binlog de un motor copyfast especial, que puede replicar rápidamente los cambios en toda nuestra flota de servidores. Lo que se utiliza aquí no es copiar directamente, sino replicación de chismes, cuando un servidor envía cambios a sus vecinos más cercanos, a sus vecinos, etc. Esto le permite actualizar el código en decenas y unidades de segundos en toda la flota. Cuando el cambio llega a la réplica local, aplica estos parches a su sistema de archivos locales. La reversión también se realiza según el mismo esquema.

También implementamos mucho kPHP y también tiene su propio desarrollo en git según el diagrama anterior. Desde esto binario del servidor HTTP, entonces no podemos producir diferencias: el binario de lanzamiento pesa cientos de MB. Por lo tanto, aquí hay otra opción: la versión está escrita en copia rápida de binlog. Con cada construcción aumenta y durante la reversión también aumenta. Versión replicado a servidores. Los copyfasts locales ven que ha entrado una nueva versión en el binlog y, según la misma replicación de chismes, toman la última versión del binario, sin cansar a nuestro servidor maestro, pero distribuyendo cuidadosamente la carga por toda la red. Que sigue relanzamiento elegante para la nueva versión.

Para nuestros motores, que también son esencialmente binarios, el esquema es muy similar:

  • rama maestra de git;
  • binario en . Deb;
  • la versión está escrita en binlog copyfast;
  • replicado a servidores;
  • el servidor extrae un .dep nuevo;
  • dpkg-i;
  • relanzamiento elegante a la nueva versión.

La diferencia es que nuestro binario está empaquetado en archivos. . Deb, y al bombear dpkg-i se colocan en el sistema. ¿Por qué se implementa kPHP como binario y los motores se implementan como dpkg? Sucedió de esa manera. Funciona, no lo toques.

Enlaces de interés:

Alexey Akulovich es uno de los que, como parte del Comité de Programa, ayuda PHP Rusia El 17 de mayo se convertirá en el mayor evento para desarrolladores de PHP de los últimos tiempos. Mira qué PC tan genial tenemos, qué Altavoces (¡dos de ellos están desarrollando el núcleo de PHP!) - parece algo que no te puedes perder si escribes PHP.

Fuente: habr.com

Añadir un comentario