¿Ves la arquitectura? Y no veo, pero ella es

En el desarrollo de hh.ru hoy unas 150 personas. Tenemos muchos equipos interesantes, y cada uno hace una contribución significativa. Pero en este artículo solo contaré sobre uno de ellos.


Porque soy el líder de su equipo y hay varias razones para esto:

  • a menudo los candidatos no entienden lo que estamos haciendo;
  • a veces, incluso los empleados dentro de la empresa no lo saben, porque nuestro equipo no tiene un gerente de producto, su propia área funcional de negocios y la lista de servicios respaldados por nosotros ...;
  • nuestros méritos a menudo permanecen a la sombra;
  • al final, "si quieres resolverlo, trata de explicárselo a alguien" :)

Por lo tanto, intentaré comprender con ejemplos comprensibles en qué consiste realmente nuestro trabajo.

Comencemos con el más general, es decir, con prioridades, hay dos de ellos:

  • Soporte del sistema para la confiabilidad y resistencia de nuestra plataforma. Aquí vale la pena prestar atención a la palabra "sistema": significa que no repararemos defectos de rendimiento específicos, sino que desarrollaremos reglas y patrones generales, los arreglaremos en marcos, verificaciones automáticas, etc. para que funcione para todos.
  • enfoque de desarrollo en lógica de negocios. Es decir, cuanto menos piense un desarrollador en admitir confiabilidad, arquitectura, etc. - todo lo mejor. Está claro que librar completamente a los colegas de tales pensamientos es bastante dañino, pero tiene sentido mantener un equilibrio razonable.

De estas prioridades se siguen las principales direcciones de nuestro trabajo:

0. Soporte y desarrollo de arquitectura.


hh.ru está a 5-6k rps de los usuarios, en el pico alcanza los 10k, que crecen en un orden de magnitud, alcanzando backends. Esto es más de 1,500 instancias, girando alrededor de 150 servicios en 3 DC. Entonces, sí, en primer lugar, estos son los esquemas muy ramificados con cuadrados, bancos y flechas: quién va a dónde, dónde debería estar. Por supuesto, no dibujamos esquemas: cubrimos las necesidades con automatización, registro y monitoreo, pero asustamos a nuestros estudiantes , por ejemplo, con tales cosas:



somos realmente responsables de encontrar y eliminar cuellos de botella y soluciones inflexibles en la arquitectura y desarrollarla de acuerdo con las necesidades.

Daré un ejemplo:

hh.ru ha estado trabajando lejos desde el primer año, y una vez que parecía una buena idea tener una máquina separada para realizar tareas en segundo plano en un horario, puede asignar más recursos para ello y no habrá carreras. Pero, ¿qué tenemos al final?

  • punto de falla para todas las tareas
  • configuración única reproducida solo en prod
  • tareas cuya lógica está diseñada para un lanzamiento dedicado en una máquina en negrita separada y no se escala horizontalmente

Cuando entendimos esto, nos aseguramos de que teníamos todos los medios para transferir las tareas de la corona a instancias generales, y comenzamos una gran tarea en la categoría de deuda técnica: ahora que llega el momento de pagar las deudas, los colegas están eliminando gradualmente este problema.

1. Estandarización de muletas


En primer lugar, estos son nuestros marcos y herramientas para el rápido desarrollo de servicios: tuercas y frontik . De todos modos, jclient y muchas otras bibliotecas abiertas en nuestro github surgieron de la idea de que tiene sentido agregar la experiencia de operar diversas tecnologías. Esto nos permite cultivar las limitaciones, los patrones de diseño y el comportamiento que desarrollamos en la batalla, y consideramos que son los más adecuados, comprensibles y confiables.

Además de estos ejemplos obvios de estandarización, existen aquellos en los que tiene sentido generalizar soluciones particulares.

Por ejemplo, en algún momento, comenzamos a tener periódicamente la necesidad de enviar mensajes a rabbitmq (al menos una vez). Las tareas se resolvieron una y otra vez con colas autoescritas en la base, y una y otra vez dba dijo cuánto les gustaban las colas en la base, especialmente las muy cargadas. Al final, se hizo evidente que se necesitaba una solución estándar aquí, que sería aceptable para dba, garantizaría una entrega confiable y sería conveniente para el desarrollo: así es como escribimos nuestra biblioteca para integrar pgq y rabbitmq. Ahora hay una alta probabilidad de que usemos pgq también junto con kafka.

1.0. Loco


Los errores también son globales. Por ejemplo, en algún momento, descubrimos que nuestro python-framework está registrado en el cónsul en cada trabajador de proceso, e incluso lo hace antes de que la aplicación esté lista para aceptar solicitudes. Después de corregir en el marco, los cambios llegarán gradualmente a todos los servicios a medida que se actualicen.

Hablé sobre otro error general relacionado con la configuración de jvm en la etapa de demostración jpoint 2019 .

¿Y qué, por ejemplo, hacer con un error que se reproduce una vez por semana en una de las instancias, se trata con un reinicio, pero ni la carga ni los sintéticos lo reproducen?
, java- . nuts-and-bolts:

"qtp1778300121-22" #22 prio=5 os_prio=0 cpu=797.67ms elapsed=11737.06s tid=0x00007f5890139000 nid=0x26 waiting for monitor entry [0x00007f58922c7000]
java.lang.Thread.State: BLOCKED (on object monitor)
at ch.qos.logback.core.AppenderBase.doAppend(AppenderBase.java:63)
- waiting to lock <0x00000000e86acad0> (a ru.hh.nab.logging.HhSyslogAppender)
at ru.hh.nab.logging.HhMultiAppender.doAppend(HhMultiAppender.java:47)
at ru.hh.nab.logging.HhMultiAppender.doAppend(HhMultiAppender.java:21)
at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51)


:

"qtp1778300121-22" #22 prio=5 os_prio=0 cpu=5718.81ms elapsed=7767.14s tid=0x00007f1537dba000 nid=0x24 waiting for monitor entry [0x00007f153d2b9000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(java.base@11.0.4/ConcurrentHashMap.java:1723)
- waiting to lock <0x00000000e976a668> (a java.util.concurrent.ConcurrentHashMap$Node)
at org.springframework.beans.factory.BeanFactoryUtils.transformedBeanName(BeanFactoryUtils.java:86)


jackson:

"qtp1778300121-23" #23 prio=5 os_prio=0 cpu=494.19ms elapsed=7234.32s tid=0x00007f6c01218800 nid=0x25 waiting for monitor entry [0x00007f6c07cfa000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.ProviderBase._endpointForWriting(ProviderBase.java:711)
- waiting to lock <0x00000000e9f94c38> (a org.glassfish.jersey.jackson.internal.jackson.jaxrs.util.LRUMap)
at org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.ProviderBase.writeTo(ProviderBase.java:588)


Code Cache:



java . java, , - . , .

1.1. Decisiones generales


A veces es posible encontrar soluciones estándar antes de que esto se convierta en un problema grave. Como ejemplos aquí, podemos citar la tarea de procesamiento de registros de la que habló nuestro Vlad Senin en el mismo jpoint 2019 , o la tarea de administración del tiempo de espera en nuestro cliente http.

Su significado es que es útil determinar un tiempo de espera razonable no en el lado del cliente, sino en el lado del servidor. Para el servidor, tenemos datos sobre qué tan rápido responde a sus puntos finales. Ahora nuestro cliente admite un tiempo de espera para el servicio. Pero es obvio que no todos los puntos finales de servicio responden de la misma manera, algunos más largos, otros más rápido. Me gustaría poder usar diferentes tiempos de espera. De lo contrario, surge una situación similar a esta:



Hasta ahora, tales situaciones aparecen solo bajo pruebas de estrés, pero quiero resolverlas antes de que esto se convierta en un problema.

1.2. Problemas abiertos


Pero no todos los problemas se explican por algunos lugares importantes y procesos manuales complicados. Además, daré algunos ejemplos de cuestiones que también entran en el campo de nuestras prioridades, pero al mismo tiempo son mucho menos deterministas. Por lo tanto, describiré solo los datos iniciales, y podemos discutir las soluciones si lo desea en los comentarios.

Entonces, el primer ejemplo: ahora queda claro que existe un problema de integrar nuestros servicios entre ellos. La integración de, por ejemplo, un identificador de sitio en una API puede llevar más tiempo que su desarrollo inicial.

Otro ejemplo, probablemente familiar para muchos, de un problema similar es cortar un monolito. Todos entienden que un monolito, cubierto de una gran cantidad de legado, complica el desarrollo y la operación. ¿Pero quién puede decir cuánto? ¿Vale la pena sacrificar otras tareas de la deuda técnica a favor del aserrado, cada una de las cuales tiene un valor desvanecedor individual?

La escala de estos y otros problemas similares es tal que, para resolverlos, a veces hay que ir más allá del marco técnico y sumergirse en áreas completamente nuevas del proceso de trabajo. Esto es aterrador por un lado, pero por otro lado brinda una libertad increíble para elegir decisiones.

2. Cómo trabajamos


La historia sobre las instrucciones de nuestro trabajo estará incompleta sin una descripción de CÓMO trabajamos con todo esto.

Para empezar, lo que me atrajo a trabajar en "Arquitectura" y lo que nos motiva a todos: realmente trabajamos por la calidad.

Y antes de que las piedras vuelen hacia mí, intentaré explicar a qué me refiero. Creo que ningún desarrollador puntúa deliberadamente la calidad. El punto está en la deuda técnica: si estamos hablando de una sección de lógica de negocios que no está planificada para ser reutilizada, lo más probable es que la cantidad de deuda de una solución no tan ideal crezca lentamente con el tiempo, si es que lo hace.

Esto le permite enfriar un poco su perfeccionismo: comience una tarea por deudas y pase a la siguiente iteración. Pero si estamos hablando de un marco o una herramienta de preparación de configuración global que se utiliza en cientos de aplicaciones y consolida ciertos diseños o patrones de nombres, entonces la tasa de crecimiento de la deuda de su decisión fallida puede bloquear cualquier ganancia. Está claro que hay situaciones en las que incluso la mejor solución revela debilidades a medida que se usa, pero esto no sucede a menudo ...

Más cerca de finalizar, me gustaría hablar sobre los obstáculos que aún se presentan en nuestro camino. Sin esto, una historia sobre nuestro trabajo sería deshonesta. Entonces.

2.0. Tareas de evaluación de dificultad


Como dije anteriormente, no podemos evaluar el efecto beneficioso para todas las tareas. ¿Cuánto disminuirá el tiempo de liberación de la tarea cuando salga una solución "en caja" para alguna función? ¿Cuál de las dos secciones problemáticas del código se debe refactorizar primero? Para desarrollar un sistema adecuado para evaluar las tareas, nos reunimos un par de veces a la semana durante varios meses, pero este es un tema para una publicación separada.

2.1. Inconsciente colectivo


Coordinar algo para 150 personas no es una tarea fácil. Nuestra estructura organizativa muy descentralizada a menudo se manifiesta con sus mejores lados, pero para la "Arquitectura" a veces es un obstáculo serio. Hay muy pocos acuerdos a los que sea posible llegar a un acuerdo, y menos aún aquellos cuyo cumplimiento pueda ser monitoreado.

Y todos los cambios deben realizarse sin problemas. Es posible que el servicio no se actualice durante meses, pero todavía hay un monolito ... Bueno, bastante triste.

Entonces hablamos


Espero que después de mi historia haya aclarado un poco lo que hace "Arquitectura" en hh.ru. Y si logré despertar su interés en nuestro trabajo, generalmente es fantástico. Además, justo ahora hay una vacante en nuestro equipo . Estaremos muy contentos con nuevas ideas que nos ayudarán a lograr nuestras miradas ocultas de las ociosas, pero tan importantes victorias.

PD Resulta que el KDPV original es una ilustración de este hombre . Espero que no esté en contra del uso de sus imágenes como KDPV

All Articles