Alta concurrencia, ¿realmente la entiendes a fondo?
La alta concurrencia es una experiencia que casi todos los programadores quieren tener. La razón es simple: a medida que aumenta el tráfico, encontraremos varios problemas técnicos, como tiempo de espera de respuesta de la interfaz, aumento de la carga de la CPU, GC frecuente, interbloqueo, gran almacenamiento de datos, etc. Estos problemas pueden promovernos en la mejora continua en profundidad técnica.
En entrevistas anteriores, si el candidato ha trabajado en proyectos de alta concurrencia, generalmente le pido que hable sobre su comprensión de la alta concurrencia, pero no muchas personas pueden responder esta pregunta de manera sistemática.
Se puede dividir aproximadamente en las siguientes categorías:
1. Sin concepto de indicadores basados en datos: ¿No está seguro de qué indicadores elegir para medir sistemas de alta concurrencia? No puedo distinguir entre concurrencia y QPS, y ni siquiera sé la cantidad total de usuarios de mi sistema, la cantidad de usuarios activos, QPS y TPS durante las horas planas y pico, y otros datos clave.
3. Comprensión unilateral, que equipara el diseño de alta concurrencia con la optimización del rendimiento: se habla de programación concurrente, almacenamiento en caché multinivel, asincronización y expansión horizontal, pero se ignora el diseño, la gestión de servicios y la operación de alta disponibilidad. y garantías de mantenimiento.
4. Domine el gran plan, pero ignore las cosas más básicas: puedo explicar claramente las grandes ideas como las capas verticales, la partición horizontal y el almacenamiento en caché, pero no tengo la conciencia para analizar si la estructura de datos es razonable y si el algoritmo es eficiente. Pensé en optimizar los detalles de las dos dimensiones más fundamentales de IO y informática.
En este artículo, quiero combinar mi propia experiencia en proyectos de alta concurrencia para resumir sistemáticamente el conocimiento y las ideas prácticas que deben dominarse en alta concurrencia. Espero que le resulte útil. El contenido se divide en las siguientes tres partes:
La alta concurrencia significa un gran tráfico y se deben utilizar medios técnicos para resistir el impacto del tráfico. Estos medios son como operar el tráfico y permitir que el tráfico se procese. por el sistema de manera más fluida y brindándolo a los usuarios una mejor experiencia.
Nuestros escenarios comunes de alta concurrencia incluyen: el Doble 11 de Taobao, obtención de entradas durante el Festival de Primavera, noticias de actualidad de Weibo Vs, etc. Además de estas cosas típicas, los sistemas de ventas flash con cientos de miles de solicitudes por segundo, los sistemas de pedidos con decenas de millones de pedidos por día, los sistemas de flujo de información con cientos de millones de actividades diarias, etc., pueden clasificarse como de alto nivel. concurrencia.
Obviamente, los escenarios de alta concurrencia mencionados anteriormente tienen diferentes cantidades de concurrencia. Entonces, ¿cuánta concurrencia se considera alta concurrencia?
1. No te fijes sólo en los números, sino también en escenarios empresariales específicos. No se puede decir que la venta flash de 10W QPS sea de alta concurrencia, pero el flujo de información de 1W QPS no sea de alta concurrencia. El escenario del flujo de información implica modelos de recomendación complejos y varias estrategias manuales, y su lógica empresarial puede ser más de 10 veces más compleja que el escenario de venta flash. Por tanto, no están en la misma dimensión y no tienen significado comparativo.
2. El negocio se construye de 0 a 1. La concurrencia y el QPS son solo indicadores de referencia. Lo más importante es: en el proceso, el volumen de negocio se vuelve gradualmente 10 o 100 veces mayor que el original. ¿Utilizó métodos de procesamiento de alta concurrencia para hacer evolucionar su sistema y prevenir y resolver problemas causados por la alta concurrencia desde las dimensiones del diseño de arquitectura, implementación de codificación e incluso soluciones de productos? En lugar de actualizar ciegamente el hardware y agregar máquinas para la expansión horizontal.
Además, las características comerciales de cada escenario de alta concurrencia son completamente diferentes: hay escenarios de flujo de información con más lectura y menos escritura, y hay escenarios de transacciones con más lectura y escritura. Solución técnica para resolver escenarios de alta concurrencia en diferentes escenarios. ¿Qué pasa con los problemas de concurrencia?
Creo que podemos aprender de las grandes ideas y de los planes de otras personas, pero en el proceso de implementación real, habrá innumerables dificultades en los detalles. Además, dado que el entorno de software y hardware, la pila de tecnología y la lógica del producto no son completamente consistentes, conducirán al mismo escenario comercial. Incluso si se utiliza la misma solución técnica, se enfrentarán diferentes problemas y estos escollos tienen que ser inevitables. ser superados uno por uno.
Por lo tanto, en este artículo me centraré en los conocimientos básicos, las ideas generales y las experiencias efectivas que he practicado, con la esperanza de brindarles una comprensión más profunda de la alta concurrencia.
Es significativo y específico comprender primero los objetivos del diseño del sistema de alta concurrencia y luego discutir el plan de diseño y la experiencia práctica.
La alta concurrencia no significa solo buscar un alto rendimiento. Esta es una comprensión unilateral de muchas personas.
Desde una perspectiva macro, existen tres objetivos para el diseño de sistemas de alta concurrencia: alto rendimiento, alta disponibilidad y alta escalabilidad.
1. Alto rendimiento: el rendimiento refleja la capacidad de procesamiento paralelo del sistema. Con una inversión limitada en hardware, mejorar el rendimiento significa ahorrar costos. Al mismo tiempo, el rendimiento también refleja la experiencia del usuario. Los tiempos de respuesta son de 100 milisegundos y 1 segundo respectivamente, lo que da a los usuarios sensaciones completamente diferentes.
2. Alta disponibilidad: Indica el tiempo durante el cual el sistema puede servir con normalidad. Uno no tiene tiempo de inactividad ni fallas durante todo el año; el otro tiene accidentes en línea y tiempo de inactividad de vez en cuando. Los usuarios definitivamente elegirán el primero. Además, si el sistema sólo puede estar disponible en un 90%, obstaculizará enormemente el negocio.
3. Alta escalabilidad: indica la escalabilidad del sistema, si puede completar la expansión en poco tiempo durante los picos de tráfico y manejar mejor el tráfico pico, como eventos de Doble 11, divorcios de celebridades y otros eventos candentes.
Estos tres objetivos deben considerarse de forma integral, porque están interrelacionados e incluso se afectan entre sí.
Por ejemplo: considerando la escalabilidad del sistema, diseñará el servicio para que no tenga estado. Este diseño de clúster garantiza una alta escalabilidad y, de hecho, mejora indirectamente el rendimiento y la disponibilidad del sistema.
Otro ejemplo: para garantizar la disponibilidad, las configuraciones de tiempo de espera generalmente se configuran para las interfaces de servicio para evitar que una gran cantidad de subprocesos bloqueen solicitudes lentas y provoquen una avalancha del sistema. Entonces, ¿qué es una configuración de tiempo de espera razonable? Generalmente, realizaremos configuraciones basadas en el desempeño de los servicios dependientes.
Desde una perspectiva micro, ¿cuáles son los indicadores específicos para medir el alto rendimiento, la alta disponibilidad y la alta escalabilidad? ¿Por qué se eligieron estos indicadores?
2.2.1 Indicadores de desempeño
Los indicadores de desempeño se pueden utilizar para medir los problemas de desempeño actuales y servir como base para la evaluación de la optimización del desempeño. En términos generales, el tiempo de respuesta de la interfaz dentro de un período de tiempo se utiliza como indicador.
1. Tiempo medio de respuesta: Es el más utilizado, pero sus fallos son evidentes y no es sensible a solicitudes lentas. Por ejemplo, hay 10.000 solicitudes, de las cuales 9.900 son de 1 ms y 100 son de 100 ms. El tiempo de respuesta promedio es de 1,99 ms. Aunque el tiempo promedio solo ha aumentado en 0,99 ms, el tiempo de respuesta para el 1% de las solicitudes ha aumentado en 100. veces.
2. Valores cuantiles como TP90 y TP99: ordene los tiempos de respuesta de pequeño a grande. TP90 representa el tiempo de respuesta clasificado en el percentil 90. Cuanto mayor es el valor cuantil, más sensible es. solicitudes lentas.
3. Rendimiento: es inversamente proporcional al tiempo de respuesta. Por ejemplo, si el tiempo de respuesta es 1 ms, el rendimiento es 1000 veces por segundo.
Por lo general, al establecer objetivos de rendimiento, se tendrán en cuenta tanto el rendimiento como el tiempo de respuesta. Por ejemplo, por debajo de 10 000 solicitudes por segundo, AVG se controla por debajo de 50 ms y TP99 se controla por debajo de 100 ms. Para sistemas de alta concurrencia, los valores de cuantiles AVG y TP deben considerarse al mismo tiempo.
Además, desde la perspectiva de la experiencia del usuario, 200 milisegundos se consideran el primer punto de división y los usuarios no pueden sentir el retraso. 1 segundo es el segundo punto de división y los usuarios pueden sentir el retraso, pero lo sienten. puede aceptar.
Por lo tanto, para un sistema saludable de alta concurrencia, TP99 debe controlarse en 200 milisegundos y TP999 o TP9999 debe controlarse en 1 segundo.
2.2.2 Indicadores de disponibilidad
La alta disponibilidad significa que el sistema tiene una alta capacidad de funcionamiento sin fallos. Disponibilidad = tiempo de funcionamiento normal/tiempo total de funcionamiento del sistema. Generalmente, se utilizan varios 9. Describir la disponibilidad del sistema.
Para sistemas de alta concurrencia, el requisito más básico es garantizar 3 9 o 4 9. La razón es simple. Si solo puede hacer dos nueves, significa que hay un tiempo de falla del 1%. Por ejemplo, algunas empresas grandes suelen tener más de 100 mil millones en GMV o ingresos cada año del 1%. nivel de mil millones.
2.2.3 Índice de escalabilidad
Ante el tráfico repentino, es imposible transformar temporalmente la arquitectura. La forma más rápida es agregar máquinas para mejorar linealmente las capacidades de procesamiento del sistema.
Para grupos empresariales o componentes básicos, escalabilidad = índice de mejora del rendimiento / índice de adición de máquinas. La escalabilidad ideal es: los recursos aumentan varias veces y el rendimiento mejora varias veces. En términos generales, la capacidad de expansión debe mantenerse por encima del 70%.
Pero desde la perspectiva de la arquitectura general de un sistema de alta concurrencia, el objetivo de la expansión no es solo diseñar el servicio para que no tenga estado, porque cuando el tráfico aumenta 10 veces, el servicio empresarial puede Se expande rápidamente 10 veces, pero la base de datos puede convertirse en un nuevo cuello de botella.
Los servicios de almacenamiento con estado como MySQL suelen ser técnicamente difíciles de ampliar. Si la arquitectura no se planifica con antelación (división vertical y horizontal), implicará la migración de una gran cantidad de datos.
Por lo tanto, una alta escalabilidad debe considerar: clústeres de servicios, middleware como bases de datos, cachés y colas de mensajes, equilibrio de carga, ancho de banda, terceros dependientes, etc. Cuando la concurrencia alcanza un cierto nivel, cada uno de los anteriores Es necesario considerar cada factor.
Después de comprender los tres objetivos principales del diseño de alta concurrencia, resumiremos sistemáticamente el plan de diseño de alta concurrencia en las dos partes siguientes: primero, resumiremos los métodos de diseño generales y luego nos centraremos en el alto rendimiento y la alta disponibilidad. y la alta escalabilidad proporcionan soluciones prácticas específicas, respectivamente.
El método de diseño general parte principalmente de las dos dimensiones de "vertical" y "horizontal", comúnmente conocidas como los dos pilares del procesamiento de alta concurrencia: expansión vertical y expansión horizontal.
3.1.1 Expansión vertical (scale-up)
Tiene como objetivo mejorar las capacidades de procesamiento de una sola máquina. Las soluciones incluyen:
1. Mejore la potencia de procesamiento de una sola máquina. Rendimiento del hardware: mejore aumentando la memoria, los núcleos de CPU, la capacidad de almacenamiento o actualizando discos para acumular hardware como SSD.
2. Mejore el rendimiento del software de una sola máquina: use caché para reducir la cantidad de IO y use métodos concurrentes o asincrónicos para aumentar el rendimiento.
3.1.2 Expansión horizontal (escalamiento horizontal)
Debido a que siempre existen límites para el rendimiento de una sola máquina, en última instancia es necesario introducir la expansión horizontal y mejorar aún más las capacidades de procesamiento simultáneo a través de La implementación del clúster también incluye las dos direcciones siguientes:
1. Establecer una arquitectura jerárquica: este es un avance para la expansión horizontal, porque los sistemas de alta concurrencia a menudo tienen negocios complejos y el procesamiento jerárquico puede simplificar problemas complejos. y hacer que sea más fácil lograr escalar horizontalmente.
El diagrama anterior es la arquitectura en capas más común en Internet. Por supuesto, la arquitectura real del sistema de alta concurrencia se mejorará aún más sobre esta base. Por ejemplo, se realizará una separación dinámica y estática y se introducirá CDN. La capa de proxy inverso puede ser LVS+Nginx, la capa web puede ser una puerta de enlace API unificada y la capa de servicios empresariales puede recibir microservicios adicionales según el negocio vertical. Y la capa de almacenamiento puede ser varias bases de datos heterogéneas.
2. Expansión horizontal de cada capa: expansión horizontal sin estado, enrutamiento de fragmentos con estado. Los clústeres empresariales generalmente se pueden diseñar para que no tengan estado, mientras que las bases de datos y los cachés suelen tener estado. Por lo tanto, las claves de partición deben diseñarse para la fragmentación del almacenamiento. Por supuesto, el rendimiento de lectura también se puede mejorar mediante la sincronización maestro-esclavo y la separación de lectura y escritura.
A continuación, combinado con mi experiencia personal, resumiré soluciones prácticas que se pueden implementar en los tres aspectos de alto rendimiento, alta disponibilidad y alta escalabilidad.
3.2.1 Solución práctica de alto rendimiento
1. Implementación de clústeres, que reduce la presión sobre una sola máquina mediante el equilibrio de carga.
2. Almacenamiento en caché multinivel, incluido el uso de CDN, almacenamiento en caché local, almacenamiento en caché distribuido, etc. para datos estáticos, así como el procesamiento de teclas de acceso rápido, penetración de caché, concurrencia de caché, coherencia de datos y Otros problemas en escenarios de almacenamiento en caché.
3. Optimización de subbases de datos, tablas e índices, así como uso de motores de búsqueda para resolver problemas de consultas complejas.
4. Considere el uso de bases de datos NoSQL, como HBase, TiDB, etc., pero el equipo debe estar familiarizado con estos componentes y tener sólidas capacidades de operación y mantenimiento.
5. Asíncrono, procese procesos secundarios de forma asíncrona a través de subprocesos múltiples, MQ e incluso tareas retrasadas.
6. Para la limitación actual, primero debe considerar si la empresa permite la limitación actual (por ejemplo, se permiten escenarios de venta flash), incluida la limitación actual del front-end, la limitación actual de la capa de acceso de Nginx y la limitación actual del servidor. -Limitación de corriente lateral.
7. Lleve a cabo la reducción de picos y el llenado de valles del tráfico, y acepte el tráfico a través de MQ.
8. Procesamiento concurrente, paralelizando la lógica en serie mediante subprocesos múltiples.
9. Cálculo previo. Por ejemplo, en la escena de agarrar sobres rojos, la cantidad del sobre rojo se puede calcular por adelantado y almacenarse en caché, y se puede usar directamente al enviar sobres rojos.
10. Precalentamiento de caché, precalentamiento de datos en caché local o caché distribuido por adelantado mediante tareas asincrónicas.
11. Reduzca la cantidad de IO, como la lectura y escritura por lotes de bases de datos y cachés, el soporte de interfaz por lotes para RPC o la eliminación de llamadas RPC a través de datos redundantes.
12. Reducir el tamaño de los paquetes de datos durante IO, incluido el uso de protocolos de comunicación livianos, estructuras de datos apropiadas, eliminación de campos redundantes en las interfaces, reducción del tamaño de las claves de caché, compresión de valores de caché, etc.
13. Optimización de la lógica del programa, como preposicionar la lógica de juicio que tiene una alta probabilidad de bloquear el proceso de ejecución, optimizar la lógica de cálculo del bucle For o utilizar algoritmos más eficientes.
14. El uso de varias tecnologías de agrupación y la configuración del tamaño del grupo, incluido el grupo de solicitudes HTTP, el grupo de subprocesos (considere el uso intensivo de CPU o IO para establecer los parámetros principales), la base de datos y el grupo de conexiones de Redis. etc.
15. Optimización de JVM, incluido el tamaño de la nueva y antigua generación, la selección del algoritmo de GC, etc., para reducir la frecuencia de GC y el consumo de tiempo tanto como sea posible.
16. Selección de bloqueo, utilice el bloqueo optimista en escenarios donde hay más lectura y menos escritura, o considere reducir los conflictos de bloqueo mediante el bloqueo segmentado.
La solución anterior no es más que considerar todos los puntos de optimización posibles desde las dos dimensiones de informática e IO. Requiere un sistema de monitoreo de soporte para comprender el rendimiento actual en tiempo real y ayudarlo a realizar análisis de cuellos de botella en el rendimiento. Y luego seguir Según el principio 28, debemos centrarnos en las principales contradicciones y optimizarlas.
3.2.2 Soluciones prácticas de alta disponibilidad
1. Conmutación por error de nodos pares Tanto Nginx como el marco de gobierno de servicios admiten el acceso a otro nodo después de que falla un nodo.
2. Conmutación por error de nodos no pares, mediante la detección de latidos y la implementación de la conmutación maestro-esclavo (como el modo centinela de Redis o el modo de clúster, la conmutación maestro-esclavo de MySQL, etc.).
3. Configuración de tiempo de espera, estrategia de reintento y diseño idempotente a nivel de interfaz.
4. Procesamiento de degradación: garantice los servicios principales, sacrifique los servicios no principales y realice un disyuntor cuando sea necesario o, si hay un problema con el enlace principal, habrá un enlace alternativo.
5. Limitación de procesamiento actual: Las solicitudes que exceden la capacidad de procesamiento del sistema se rechazan directamente o se devuelve un código de error.
6. Garantía de confiabilidad del mensaje en escenarios MQ, incluido el mecanismo de reintento en el lado del productor, la persistencia en el lado del corredor, el mecanismo de confirmación en el lado del consumidor, etc.
7. El lanzamiento en escala de grises puede admitir la implementación de tráfico pequeño de acuerdo con la dimensión de la máquina, observar los registros del sistema y los indicadores comerciales y luego impulsar el volumen completo una vez que la operación sea estable.
8. Monitoreo y alarmas: un sistema de monitoreo integral, que incluye el monitoreo más básico de CPU, memoria, disco y red, así como monitoreo de servidores web, JVM, bases de datos, diversos middleware y negocios. indicadores.
9. Simulacro de recuperación ante desastres: similar a la "ingeniería del caos" actual, utilice algunos métodos destructivos en el sistema para observar si las fallas locales causarán problemas de disponibilidad.
Las soluciones de alta disponibilidad se consideran principalmente desde tres direcciones: redundancia, compensaciones y operación y mantenimiento del sistema. Al mismo tiempo, deben contar con mecanismos de servicio de soporte y procesos de manejo de fallas. ocurren, pueden ser seguidos a tiempo.
3.2.3 Soluciones prácticas altamente escalables
1. Arquitectura jerárquica razonable: como la arquitectura jerárquica más común en Internet mencionada anteriormente, además de un mayor acceso a datos La capa de microservicios y La capa de lógica de negocios está estratificada de una manera más detallada (pero es necesario evaluar el rendimiento, ya que puede haber un salto más en la red).
2. División de la capa de almacenamiento: división vertical según las dimensiones del negocio y división horizontal adicional (subbase de datos y subtabla) según las dimensiones características de los datos.
3. Dividir la capa empresarial: lo más común es dividirla según dimensiones comerciales (como servicios de productos básicos, servicios de pedidos, etc. en escenarios de comercio electrónico. También se puede dividir según). interfaces principales e interfaces no principales También se pueden dividir según la fuente solicitada (como A C y A B, APP y H5).
La alta concurrencia es de hecho un problema complejo y sistémico. Debido al espacio limitado, se deben considerar puntos técnicos como el seguimiento distribuido, las pruebas de estrés de enlace completo y las transacciones flexibles. Además, si los escenarios comerciales son diferentes, las soluciones de implementación de alta concurrencia también serán diferentes, pero las ideas de diseño generales y las soluciones que se pueden usar como referencia son básicamente similares.
El diseño de alta concurrencia también debe cumplir con los tres principios del diseño arquitectónico: simplicidad, adecuación y evolución. "La optimización prematura es la raíz de todos los males". No puede divorciarse de la situación real del negocio, y mucho menos el diseño excesivo. La solución adecuada es la más perfecta.
Sobre el autor: Maestro de 1985, ex ingeniero de Amazon, ahora director técnico de una gran fábrica.