¿Cómo aseguramos el crecimiento de CityMobile?

imagen

Mi nombre es Ivan, soy el jefe de desarrollo de servidores en Citimobil. Hoy hablaré sobre qué es este desarrollo de servidor, qué problemas encontramos y cómo planeamos desarrollarlo.

Inicio de crecimiento


Pocas personas saben que CityMobile existe desde hace mucho tiempo, 13 años. Una vez fue una pequeña empresa que solo trabajaba en Moscú. Había muy pocos desarrolladores y sabían muy bien cómo funcionaba el sistema, porque ellos mismos lo crearon. El mercado de taxis apenas comenzaba a desarrollarse, las cargas eran de un centavo. Ni siquiera teníamos la tarea de proporcionar tolerancia a fallas y escalado.

En 2018, Mail.Ru Group invirtió en CityMobile y comenzamos a crecer rápidamente. No hubo tiempo para reescribir la plataforma, o al menos una refactorización significativa, fue necesario desarrollar sus capacidades funcionales para ponerse al día con nuestro principal competidor y contratar personas rápidamente. Cuando me uní a la compañía, solo 20 desarrolladores estaban involucrados en el backend, y casi todos estaban en juicio. Por lo tanto, decidimos "arrancar fruta baja": hacer cambios simples que den un gran resultado.

En ese momento, teníamos un monolito en PHP y tres servicios en Go, así como una base maestra en MySQL a la que estaba accediendo el monolito (los servicios se usaron como repositorios de Redis y EasticSearch). Y gradualmente, con el aumento de la carga, las solicitudes de sistemas pesados ​​comenzaron a ralentizar la base.

¿Qué se podría hacer con esto?

Primero, tomamos el paso obvio: poner un esclavo en producción. Pero si le llegan muchas solicitudes pesadas, ¿lo soportará? También era obvio que con una gran cantidad de solicitudes de informes analíticos, el esclavo comenzaría a retrasarse. Una fuerte acumulación de esclavos podría afectar negativamente el rendimiento de todo el CityMobile. Como resultado, ponemos otro esclavo para los analistas. Su retraso nunca conduce a problemas en la producción. Pero incluso esto nos pareció insuficiente. Escribimos un replicador de tablas de MySQL en Clickhouse. Y hoy, el análisis vive solo, usando una pila que está más diseñada para OLAP.

De esta forma, el sistema funcionó durante algún tiempo, luego comenzaron a aparecer funciones que eran más exigentes para el hardware. Hubo más y más solicitudes con cada semana: ni siquiera pasó una semana sin un nuevo registro. Además, colocamos una bomba de tiempo bajo nuestro sistema. Anteriormente, solo teníamos un punto de falla: la base maestra MySQL, pero con la adición de un esclavo había dos puntos: el maestro y el esclavo. La falla de cualquiera de estas máquinas conduciría a una falla completa del sistema.

Para protegernos contra esto, comenzamos a usar un proxy local para realizar esclavos de verificación de salud. Esto nos permitió usar muchos esclavos sin cambiar el código. Introdujimos verificaciones automáticas periódicas del estado de cada esclavo y sus métricas generales:

  • la;
  • retraso esclavo;
  • disponibilidad portuaria;
  • número de cerraduras, etc.

Si se supera un cierto umbral, el sistema elimina al esclavo de la carga. Pero al mismo tiempo, no se puede retirar más de la mitad de los esclavos, de modo que, debido al aumento de la carga en los restantes, no pueden organizar un tiempo de inactividad por sí mismos. Como proxy, utilizamos HAProxy e inmediatamente incluimos un plan para cambiar a ProxySQL en la cartera de pedidos. La elección fue algo extraña, pero nuestros administradores ya tenían una buena experiencia trabajando con HAProxy, y el problema era grave y requería una solución temprana. Por lo tanto, creamos un sistema a prueba de fallas para esclavos, que se escalaba con bastante facilidad. A pesar de su simplicidad, ella nunca nos decepcionó.

Mayor crecimiento


A medida que el negocio se desarrolló, encontramos otro cuello de botella en nuestro sistema. Con los cambios en las condiciones externas, por ejemplo, la lluvia comenzó en una gran región, el número de pedidos de taxis aumentó rápidamente. En tales situaciones, los conductores no tuvieron tiempo de reaccionar lo suficientemente rápido, y hubo escasez de automóviles. Mientras se distribuían las órdenes, creaban una carga en esclavos MySQL en un bucle.

Encontramos una solución exitosa: Tarantool. Fue difícil reescribir el sistema, por lo que resolvimos el problema de manera diferente: usando la herramienta de replicación mysql-tarantoolReplicó algunas tablas de MySQL a Tarantool. ¡Todas las solicitudes de lectura que surgieron durante la escasez de automóviles, comenzamos a transmitir en Tarantool y desde entonces ya no nos preocupan las tormentas eléctricas y los huracanes! Y resolvimos el problema con el punto de falla aún más fácil: instalamos inmediatamente varias réplicas a las que accedemos con el chequeo de salud a través de HAProxy. Cada instancia de Tarantool es replicada por un replicador separado. Como beneficio adicional, también resolvimos el problema de los esclavos rezagados en esta sección del código: la replicación de MySQL a Tarantool funciona mucho más rápido que de MySQL a MySQL.

Sin embargo, nuestra base maestra seguía siendo un punto de falla y no escalaba en las operaciones de grabación. Comenzamos a resolver este problema de esta manera.

En primer lugar, en ese momento ya habíamos comenzado a crear activamente nuevos servicios (por ejemplo, antifraude, sobre los cuales ya escribieron mis colegas ). Además, los servicios requerían inmediatamente la escalabilidad del almacenamiento. Para Redis, comenzamos a usar solo Redis-cluster, y para Tarantool - Vshard. Cuando usamos MySQL, comenzamos a usar Vitess para una nueva lógica . Dichas bases de datos son inmediatamente reparables, por lo que casi no hay problemas con la grabación, y si surgen de repente, será fácil resolverlas agregando servidores. Ahora usamos Vitess solo para servicios no críticos y estudiamos las dificultades, pero, en el futuro, estará en todas las bases de datos MySQL.

En segundo lugar, dado que fue difícil y largo implementar Vitess para la lógica ya existente, fuimos de una manera más simple, aunque menos universal: comenzamos a distribuir la base maestra en diferentes servidores, tabla por tabla. Tuvimos mucha suerte: resultó que la carga principal en el registro es creada por tablas que no son críticas para la funcionalidad principal. Y cuando hacemos tales tablas, no creamos puntos adicionales de fracaso empresarial. El principal enemigo para nosotros fue la fuerte conexión de las tablas en el código usando JOIN (había JOIN y 50-60 tablas cada una). Los cortamos sin piedad.

Ahora es el momento de recordar dos patrones muy importantes para diseñar sistemas de alta carga:

  • Graceful degradation. , - . , , , , .. , .
  • Circuit breaker. , . , , , . ? ( - graceful degradation). , FPM- , . - ( ) , . , - , ( ).

Entonces, comenzamos a escalar al menos, pero todavía había puntos de falla.

Luego decidimos recurrir a la replicación semisíncrona (y la implementamos con éxito). ¿Cuál es su característica? Si durante la replicación asincrónica normal, el limpiador en el centro de datos vierte un balde de agua en el servidor, entonces las últimas transacciones no tendrán tiempo de replicarse a los esclavos y se perderán. Y debemos estar seguros de que en este caso no tendremos problemas serios después de que uno de los esclavos se convierta en un nuevo amo. Como resultado, decidimos no perder transacciones en absoluto, y para esto utilizamos la replicación semisíncrona. Ahora los esclavos pueden retrasarse, pero incluso si se destruye el servidor de la base de datos maestra, la información sobre todas las transacciones se almacenará en al menos un esclavo.

Este fue el primer paso hacia el éxito. El segundo paso fue utilizar la utilidad del orquestador. También monitoreamos constantemente todo MySQL en el sistema. Si la base maestra falla, entonces la automatización convertirá al maestro en el último esclavo (y teniendo en cuenta la replicación semisíncrona, contendrá todas las transacciones) y le cambiará toda la carga de escritura. Así que ahora podemos revivir la historia de una señora de la limpieza y un balde de agua.

¿Que sigue?

Cuando llegué a CityMobile, teníamos tres servicios y un monolito. Hoy hay más de 20 de ellos, y lo principal que frena nuestro crecimiento es que todavía tenemos una única base maestra. Luchamos heroicamente contra esto y lo dividimos en bases separadas.

¿Cómo nos desarrollamos más?


Expande el conjunto de microservicios. Esto resolverá muchos de los problemas que enfrentamos hoy. Por ejemplo, el equipo ya no tiene una sola persona que conozca el dispositivo de todo el sistema. Y dado que debido al rápido crecimiento, no siempre tenemos documentación actualizada, y es muy difícil mantenerla, es difícil para los principiantes profundizar en el curso de las cosas. Y si el sistema constará de numerosos servicios, escribir documentación para cada uno de ellos será incomparablemente más fácil. Y la cantidad de código para un estudio único se reduce considerablemente.

Ir a ir.Realmente amo este lenguaje, pero siempre creí que no era práctico reescribir el código de trabajo de un idioma a otro. Pero recientemente, la experiencia ha demostrado que las bibliotecas PHP, incluso las estándar y las más populares, no son de la más alta calidad. Es decir, parcheamos muchas bibliotecas. Digamos que el equipo de SRE parcheó la biblioteca estándar para interactuar con RabbitMQ: resultó que una función tan básica como el tiempo de espera no funcionaba. Y cuanto más profundizamos el equipo de SRE y entiendo estos problemas, más se hace evidente que pocas personas piensan en los tiempos de espera en PHP, pocas personas se preocupan por probar bibliotecas, pocas personas piensan en los bloqueos. ¿Por qué esto se está convirtiendo en un problema para nosotros? Porque las soluciones Go son mucho más fáciles de mantener.

¿Qué más me impresiona con Go? Curiosamente, escribir sobre él es muy simple. Además, Go facilita la creación de una variedad de soluciones de plataforma. Este lenguaje tiene un conjunto muy poderoso de herramientas estándar. Si por alguna razón nuestro backend comienza a ralentizarse repentinamente, simplemente vaya a una URL específica y podrá ver todas las estadísticas: el diagrama de asignación de memoria, para comprender dónde está inactivo el proceso. Y en PHP es más difícil identificar problemas de rendimiento.

Además, Go tiene linters muy buenos, programas que encuentran automáticamente los errores más comunes para usted. La mayoría de ellos se describen en el artículo "50 sombras de Go", y los linters los detectan perfectamente.

Continúa fragmentando las bases. Cambiaremos a Vitess en todos los servicios.

Traducción de un monolito PHP en redis-cluster.En nuestros servicios, redis-cluster demostró ser excelente. Desafortunadamente, implementarlo en PHP es más difícil. El monolito usa comandos que no son compatibles con redis-cluster (lo cual es bueno, tales comandos traen más problemas que beneficios).

Investigaremos los problemas de RabbitMQ. Se cree que RabbitMQ no es el software más confiable. Estudiaremos este problema, encontraremos y resolveremos problemas. Quizás pensemos en cambiarnos a Kafka o Tarantool.

All Articles