Bioyino: agregador de métricas distribuída e escalable

Así que recolles métricas. Como somos. Tamén recollemos métricas. Por suposto, necesario para o negocio. Hoxe falaremos da primeira ligazón do noso sistema de monitorización: un servidor de agregación compatible con statsd bioino, por que o escribimos e por que abandonamos o brubeck.

Bioyino: agregador de métricas distribuída e escalable

Dos nosos artigos anteriores (1, 2) podes descubrir que ata algún tempo recollemos marcas utilizando Brubeck. Está escrito en C. Desde o punto de vista do código, é tan sinxelo coma un enchufe (isto é importante cando queres contribuír) e, o máis importante, manexa os nosos volumes de 2 millóns de métricas por segundo (MPS) ao máximo. sen ningún problema. A documentación indica o soporte para 4 millóns de MPS cun asterisco. Isto significa que obterá a cifra indicada se configura a rede correctamente en Linux. (Non sabemos cantos MPS podes obter se deixas a rede como está). A pesar destas vantaxes, tivemos varias queixas serias sobre o brubeck.

Reclamación 1. Github, o desenvolvedor do proxecto, deixou de apoialo: publicando parches e correccións, aceptando o noso e (non só o noso) PR. Nos últimos meses (algún lugar de febreiro a marzo de 2018), a actividade retomouse, pero antes había case 2 anos de total calma. Ademais, o proxecto estase a desenvolver para necesidades internas de Gihub, o que pode converterse nun serio obstáculo para a introdución de novas funcións.

Reclamación 2. Exactitude dos cálculos. Brubeck recolle un total de 65536 valores para a agregación. No noso caso, para algunhas métricas, durante o período de agregación (30 segundos), poden chegar moitos máis valores (1 no pico). Como resultado desta mostraxe, os valores máximos e mínimos parecen inútiles. Por exemplo, así:

Bioyino: agregador de métricas distribuída e escalable
Como foi

Bioyino: agregador de métricas distribuída e escalable
Como debería ser

Polo mesmo motivo, as cantidades adoitan calcularse incorrectamente. Engade aquí un erro cun desbordamento flotante de 32 bits, que xeralmente envía o servidor a un fallo secundario cando recibe unha métrica aparentemente inocente, e todo se fai xenial. O erro, por certo, non foi corrixido.

E, finalmente, Reclamación X. No momento de escribir este artigo, estamos preparados para presentalo ás 14 implementacións de statsd máis ou menos funcionantes que puidemos atopar. Imaxinemos que unha única infraestrutura medrou tanto que aceptar 4 millóns de MPS xa non é suficiente. Ou aínda que aínda non medrou, pero as métricas xa son tan importantes para ti que incluso baixas curtas de 2-3 minutos nos gráficos xa poden chegar a ser críticas e provocar ataques de depresión insuperable entre os xestores. Dado que tratar a depresión é unha tarefa ingrata, son necesarias solucións técnicas.

En primeiro lugar, a tolerancia ás fallas, para que un problema repentino no servidor non provoque un apocalipse psiquiátrico zombie na oficina. En segundo lugar, escalar para poder aceptar máis de 4 millóns de MPS, sen afondar na pila de rede de Linux e crecer con calma "en amplitude" ata o tamaño necesario.

Como tiñamos espazo para escalar, decidimos comezar coa tolerancia a fallos. "Sobre! Tolerancia a fallos! É sinxelo, podemos facelo", pensamos e lanzamos 2 servidores, levantando unha copia de brubeck en cada un. Para iso, tivemos que copiar o tráfico con métricas a ambos os servidores e mesmo escribir para iso pequena utilidade. Resolvemos o problema de tolerancia a fallos con isto, pero... non moi ben. Ao principio, todo parecía xenial: cada brubeck recolle a súa propia versión de agregación, escribe datos en Graphite unha vez cada 30 segundos, sobreescribindo o intervalo antigo (isto faise no lado de Graphite). Se un servidor falla de súpeto, sempre temos un segundo coa súa propia copia dos datos agregados. Pero aquí está o problema: se o servidor falla, aparece unha "serra" nos gráficos. Isto débese ao feito de que os intervalos de 30 segundos de brubeck non están sincronizados e, no momento dun accidente, un deles non se sobrescribe. Cando se inicia o segundo servidor, ocorre o mesmo. Moi tolerable, pero quero mellor! O problema da escalabilidade tampouco desapareceu. Todas as métricas aínda "voan" a un único servidor e, polo tanto, estamos limitados aos mesmos 2-4 millóns de MPS, dependendo do nivel de rede.

Se pensas un pouco sobre o problema e ao mesmo tempo desenterras a neve cunha pa, entón podes vir a túa mente a seguinte idea obvia: necesitas un statsd que poida funcionar en modo distribuído. É dicir, aquel que implementa a sincronización entre nodos en tempo e métricas. "Por suposto, tal solución probablemente xa exista", dixemos e fomos a Google... E non atoparon nada. Despois de revisar a documentación de diferentes estatísticas (https://github.com/etsy/statsd/wiki#server-implementations a partir do 11.12.2017 de decembro de XNUMX), non atopamos absolutamente nada. Ao parecer, nin os desenvolvedores nin os usuarios destas solucións aínda atoparon tantas métricas, se non, definitivamente chegarían a algo.

E entón lembramos sobre o "xoguete" statsd - bioyino, que foi escrito no hackathon Just for Fun (o nome do proxecto foi xerado polo guión antes do inicio do hackathon) e decatámonos de que necesitabamos urxentemente as nosas propias estatísticas. Para qué?

  • porque hai moi poucos clons de estatísticas no mundo,
  • porque é posible proporcionar a tolerancia a fallos e escalabilidade desexadas ou próximas á desexada (incluída a sincronización de métricas agregadas entre servidores e a resolución do problema do envío de conflitos),
  • porque é posible calcular métricas con máis precisión que Brubeck,
  • porque pode recoller estatísticas máis detalladas, que Brubeck practicamente non nos proporcionou,
  • porque tiven a oportunidade de programar a miña propia aplicación a escala distribuída de hiperperformance, que non repetirá completamente a arquitectura doutra hiperperformance similar para... ben, iso é todo.

En que escribir? Por suposto, en Rust. Por que?

  • porque xa había un prototipo de solución,
  • porque o autor do artigo xa coñecía a Rust nese momento e estaba ansioso por escribir algo nel para a súa produción coa oportunidade de poñelo en código aberto,
  • porque os idiomas con GC non son axeitados para nós debido á natureza do tráfico recibido (case en tempo real) e as pausas GC son practicamente inaceptables,
  • porque necesitas un rendemento máximo comparable ao C
  • porque Rust ofrécenos simultaneidade sen medo, e se comezamos a escribilo en C/C++, teríamos aínda máis vulnerabilidades, desbordamentos de búfer, condicións de carreira e outras palabras de medo que brubeck.

Tamén houbo un argumento contra Rust. A empresa non tiña experiencia na creación de proxectos en Rust, e agora tampouco pensamos usalo no proxecto principal. Polo tanto, había serios temores de que nada funcionase, pero decidimos arriscar e intentámolo.

O tempo pasou...

Finalmente, despois de varios intentos fallidos, a primeira versión de traballo estaba lista. Que pasou? Isto foi o que pasou.

Bioyino: agregador de métricas distribuída e escalable

Cada nodo recibe o seu propio conxunto de métricas e acumúlaas, e non agrega métricas para aqueles tipos onde o seu conxunto completo é necesario para a agregación final. Os nodos están conectados entre si por algún tipo de protocolo de bloqueo distribuído, que permite seleccionar entre eles o único (aquí choramos) que é digno de enviar métricas ao Grande. Este problema está sendo resolto actualmente por Cónsul, pero no futuro as ambicións do autor esténdense a propio implementación Raft, onde o máis digno será, por suposto, o nó líder do consenso. Ademais do consenso, os nodos con bastante frecuencia (unha vez por segundo por defecto) envían aos seus veciños aquelas partes de métricas pre-agregadas que conseguiron recoller nese segundo. Acontece que se preservan a escala e a tolerancia a fallos: cada nodo aínda ten un conxunto completo de métricas, pero as métricas envíanse xa agregadas, a través de TCP e codificadas nun protocolo binario, polo que os custos de duplicación redúcense significativamente en comparación co UDP. A pesar da cantidade bastante grande de métricas entrantes, a acumulación require moi pouca memoria e aínda menos CPU. Para os nosos mertics altamente comprimibles, isto son só unhas poucas decenas de megabytes de datos. Como bonificación adicional, non obtemos reescrituras de datos innecesarias en Graphite, como foi o caso de burbeck.

Os paquetes UDP con métricas están desequilibrados entre os nodos dos equipos de rede mediante un simple Round Robin. Por suposto, o hardware da rede non analiza o contido dos paquetes e, polo tanto, pode extraer moito máis de 4 millóns de paquetes por segundo, sen esquecer métricas das que non sabe nada. Se temos en conta que as métricas non veñen unha por vez en cada paquete, non prevemos ningún problema de rendemento neste lugar. Se un servidor falla, o dispositivo de rede detecta este feito rapidamente (dentro de 1-2 segundos) e elimina da rotación o servidor accidentado. Como resultado diso, os nós pasivos (é dicir, que non son líderes) pódense activar e desactivar practicamente sen notar caídas nos gráficos. O máximo que perdemos é parte das métricas que entraron no último segundo. Unha perda/apagada/cambio repentinos dun líder aínda creará unha pequena anomalía (o intervalo de 30 segundos aínda está dessincronizado), pero se hai comunicación entre os nodos, estes problemas pódense minimizar, por exemplo, enviando paquetes de sincronización. .

Un pouco sobre a estrutura interna. A aplicación é, por suposto, multiproceso, pero a arquitectura de fíos é diferente da usada en Brubeck. Os fíos de brubeck son os mesmos: cada un deles é responsable tanto da recollida de información como da agregación. En bioyino, os traballadores divídense en dous grupos: os responsables da rede e os responsables da agregación. Esta división permítelle xestionar a aplicación de forma máis flexible dependendo do tipo de métricas: onde se require unha agregación intensiva, pode engadir agregadores, onde hai moito tráfico de rede, pode engadir o número de fluxos de rede. Neste momento, nos nosos servidores traballamos en 8 fluxos de rede e 4 de agregación.

A parte do reconto (responsable da agregación) é bastante aburrida. Os búfers enchidos por fluxos de rede distribúense entre os fluxos de reconto, onde posteriormente son analizados e agregados. A solicitude, ofrécense métricas para enviar a outros nodos. Todo isto, incluído o envío de datos entre nodos e o traballo con Consul, realízase de forma asíncrona, executándose no marco. tal.

Moitos máis problemas durante o desenvolvemento foron causados ​​pola parte da rede responsable de recibir métricas. O obxectivo principal de separar os fluxos de rede en entidades separadas era o desexo de reducir o tempo que pasa un fluxo non para ler datos do socket. As opcións que usaban UDP asíncrono e recvmsg regulares desapareceron rapidamente: a primeira consome demasiado espazo de CPU para o procesamento de eventos, a segunda require demasiados cambios de contexto. Polo tanto, agora úsase recvmmsg con grandes amortiguadores (e os buffers, señores oficiais, non vos son nada!). O soporte para UDP normal resérvase para casos leves nos que non se necesita recvmmsg. No modo multimensaxe, é posible lograr o principal: a gran maioría das veces, o fío de rede rastrilla a cola do SO: le os datos do socket e transfire ao búfer do espazo de usuario, cambiando só ocasionalmente a dar o búfer cheo a agregadores. A cola no socket practicamente non se acumula, o número de paquetes abandonados practicamente non crece.

Nota

Na configuración predeterminada, o tamaño do búfer está configurado para ser bastante grande. Se de súpeto decides probar o servidor ti mesmo, podes atoparte co feito de que despois de enviar un pequeno número de métricas, estas non chegarán en Graphite, permanecendo no búfer de fluxo de rede. Para traballar cun pequeno número de métricas, cómpre configurar o tamaño da cola de tarefas e o tamaño da cola de tarefas en valores máis pequenos na configuración.

Para rematar, algúns gráficos para os amantes das cartas.

Estatísticas sobre o número de métricas de entrada para cada servidor: máis de 2 millóns de MPS.

Bioyino: agregador de métricas distribuída e escalable

Desactivando un dos nós e redistribuíndo as métricas entrantes.

Bioyino: agregador de métricas distribuída e escalable

Estatísticas sobre as métricas de saída: un só nodo sempre envía: o xefe da incursión.

Bioyino: agregador de métricas distribuída e escalable

Estatísticas do funcionamento de cada nodo, tendo en conta os erros en varios módulos do sistema.

Bioyino: agregador de métricas distribuída e escalable

Detalle das métricas entrantes (os nomes das métricas están ocultos).

Bioyino: agregador de métricas distribuída e escalable

Que pensamos facer a continuación con todo isto? Por suposto, escribe código, carallo...! O proxecto foi orixinalmente planeado para ser de código aberto e seguirá sendo así ao longo da súa vida. Os nosos plans inmediatos inclúen cambiar á nosa propia versión de Raft, cambiar o protocolo par a outro máis portátil, introducir estatísticas internas adicionais, novos tipos de métricas, corrección de erros e outras melloras.

Por suposto, todos poden axudar no desenvolvemento do proxecto: crear PR, Problemas, se é posible responderemos, melloraremos, etc.

Dito isto, iso é todo, cómpre os nosos elefantes!



Fonte: www.habr.com

Engadir un comentario