Me estoy adentrando en la clandestinidad, o lo que debe saber, optimizando la aplicación de red

Saludos amigos!

En los dos artículos anteriores ( uno , dos ), nos sumergimos en la complejidad de la elección entre tecnologías y buscamos la configuración óptima para nuestra solución en Ostrovok.ru . ¿Qué tema vamos a plantear hoy?

Cada servicio debería funcionar en algún servidor, comunicándose con el hardware a través de las herramientas del sistema operativo. Existen muchas de estas herramientas, así como configuraciones para ellas. En la mayoría de los casos, su configuración predeterminada será más que suficiente. En este artículo, me gustaría hablar sobre aquellos casos en los que la configuración estándar aún no era suficiente, y tuve que conocer un poco más el sistema operativo, en nuestro caso con Linux .



Usamos los núcleos sabiamente


En un artículo anterior, hablé sobre la opción cpu-map en Haproxy . Con él, vinculamos los procesos de Haproxy a subprocesos de un núcleo en un servidor de doble procesador. Le dimos el segundo núcleo al manejo de interrupciones de tarjetas de red.

A continuación se muestra una pantalla donde puede ver una separación similar. A la izquierda, los núcleos están ocupados por Haproxy en user space , y a la derecha, procesando interrupciones en el kernel space .



Las interrupciones de enlace a una tarjeta de red se realizan automáticamente usando esto
Guión Bash:
 #! /bin/bash interface=${1} if [ -z "${interface}" ];then echo "no interface specified" echo "usage: ${0} eth1" exit 1 fi nproc=$(grep 'physical id' /proc/cpuinfo|sort -u|wc -l) ncpu=$(grep -c 'processor' /proc/cpuinfo) cpu_per_proc=$[ncpu / nproc] queue_threads=$[cpu_per_proc / 2] binary_map="" cpumap="" for(( i=0; i < ncpu; i++ ));do cpumap=${cpumap}1 b+='{0..1}' done binary_map=($(eval echo ${b})) ###         ###    ,      . ethtool -L ${interface} combined ${queue_threads} || true count=${ncpu} while read irq queue;do let "cpu_num=$[count-1]" let "cpu_index=$[2**cpu_num]" printf "setting ${queue} to %d (%d)\n" $((2#${binary_map[${cpu_index}]})) ${cpu_num} printf "%x\n" "$((2#${binary_map[${cpu_index}]}))" > /proc/irq/${irq}/smp_affinity [ ${interface} != ${queue} ] && count=$[count-1] [ $[ncpu - count] -gt ${queue_threads} ] && count=${ncpu} done < <(awk "/${interface}/ {if(NR > 1){ sub(\":\", \"\", \$1); print \$1,\$(NF)} }" /proc/interrupts) exit 0 


Hay muchas secuencias de comandos simples y más complejas adecuadas en Internet que hacen el mismo trabajo, pero esta secuencia de comandos es suficiente para nuestras necesidades.

En Haproxy, vinculamos los procesos a los núcleos, comenzando con el primer núcleo. El mismo script vincula las interrupciones, comenzando por el último. Por lo tanto, podemos dividir los procesadores del servidor en dos campos.

Para una visión más profunda de las interrupciones y la creación de redes, recomiendo leer este artículo .

Revelamos las capacidades de los dispositivos de red.


Sucede que una gran cantidad de tramas pueden volar a través de la red en un momento, y la cola de la tarjeta puede no estar lista para una afluencia tan grande de invitados, incluso si tiene la oportunidad de hacerlo.

Hablemos del búfer de la tarjeta de red. Muy a menudo, los valores predeterminados no utilizan todo el búfer disponible. Puede ver la configuración actual con la potente herramienta ethtool.

Ejemplo de uso de comando:

 > ethtool -g eno1 Ring parameters for eno1: Pre-set maximums: RX: 4096 RX Mini: 0 RX Jumbo: 0 TX: 4096 Current hardware settings: RX: 256 RX Mini: 0 RX Jumbo: 0 TX: 256 

Ahora tomemos todo de la vida:

 > ethtool -G eno1 rx 4096 tx 4096 > ethtool -g eno1 Ring parameters for eno1: Pre-set maximums: RX: 4096 RX Mini: 0 RX Jumbo: 0 TX: 4096 Current hardware settings: RX: 4096 RX Mini: 0 RX Jumbo: 0 TX: 4096 

Ahora puede estar seguro de que la tarjeta no está restringida y funciona al máximo de sus capacidades.

Configuración mínima de sysctl para obtener el máximo beneficio


Sysctl tiene una gran variedad de opciones en todos los colores y tamaños que puedas imaginar. Y, como regla, los artículos en Internet, que abordan el tema de la optimización, cubren una parte bastante impresionante de estos parámetros. Consideraré solo aquellos que fueron realmente útiles para cambiar en nuestro caso.

net.core.netdev_max_backlog : la cola donde se obtienen los marcos de la tarjeta de red, que luego son procesados ​​por el núcleo. Con interfaces rápidas y grandes cantidades de tráfico, puede llenarse rápidamente. Predeterminado : 1000.
Podemos observar el exceso de esta cola mirando la segunda columna en el archivo / proc / net / softnet_stat.
 awk '{print $2}' /proc/net/softnet_stat 

El archivo en sí describe la estructura de netif_rx_stats por línea para cada CPU del sistema.
Específicamente, la segunda columna describe el número de paquetes en el estado descartado. Si el valor en la segunda columna crece con el tiempo, entonces probablemente valga la pena aumentar el valor de net.core.netdev_max_backlog o poner la CPU más rápido.

net.core.rmem_default / net.core.rmem_max && net.core.wmem_default / net.core.wmem_max : estos parámetros indican el valor predeterminado / valor máximo para los buffers de lectura y escritura de socket. El valor predeterminado se puede cambiar en el nivel de la aplicación en el momento en que se crea el socket (por cierto, Haproxy tiene un parámetro que hace esto). Tuvimos casos en que el kernel arrojó más paquetes de los que Haproxy logró rakear, y luego comenzaron los problemas. Por lo tanto, la cosa es importante.

net.ipv4.tcp_max_syn_backlog : es responsable del límite de nuevas conexiones aún no establecidas para las cuales se recibió un paquete SYN . Si hay una gran cantidad de conexiones nuevas (por ejemplo, muchas solicitudes HTTP de Connection: close ), tiene sentido aumentar este valor para no perder el tiempo enviando paquetes reenviados.

net.core.somaxconn : aquí estamos hablando de conexiones establecidas, pero aún no procesadas por la aplicación. Si el servidor tiene un solo subproceso, y llegaron dos solicitudes, entonces la primera solicitud será procesada por la función accept() , y la segunda se bloqueará en la backlog , cuyo tamaño es responsable de este parámetro.

nf_conntrack_max es probablemente el más famoso de todos los parámetros. Creo que casi todos los que trataron con iptables lo saben. Idealmente, por supuesto, si no necesita utilizar el enmascaramiento de iptables, puede descargar el módulo conntrack y no pensar en ello. En mi caso, se usa Docker , por lo que no subirás nada especial.

Monitoreo Obvio y no muy


Para no buscar ciegamente por qué "su proxy se está ralentizando", será útil configurar un par de gráficos y superponerlos con disparadores.

nf_conntrack_count es la métrica más obvia. En él puede controlar cuántas conexiones hay actualmente en la tabla conntrack . Cuando la tabla se desborda, la ruta para las nuevas conexiones se cerrará.

El valor actual se puede encontrar aquí:

 cat /proc/sys/net/netfilter/nf_conntrack_count 



Segmentos Tcp retransmitidos : el número de transferencias de segmento. La métrica es muy voluminosa, ya que puede hablar sobre problemas en diferentes niveles. El crecimiento de las transferencias puede indicar problemas de red, la necesidad de optimizar la configuración del sistema o incluso que el software final (por ejemplo, Haproxy) no está haciendo su trabajo. Sea como fuere, el crecimiento anormal de este valor puede servir como motivo para los procedimientos.



En nuestro país, un aumento en los valores a menudo indica problemas con uno de los proveedores, aunque ha habido problemas con el rendimiento de los servidores y la red.

Ejemplo de verificación:

 netstat -s|grep 'segments retransmited' 

Socket Recv-Q : recuerde, hablamos de momentos en que una aplicación puede no tener suficiente tiempo para procesar solicitudes, y luego la socket backlog crecerá. El crecimiento de este indicador deja en claro que algo está mal con la aplicación y que no puede hacer frente.

Vi montañas en gráficos con esta métrica, cuando el parámetro maxconn en Haproxy tenía un valor predeterminado (2000), y simplemente no aceptaba nuevas conexiones.

Y de nuevo un ejemplo:

 ss -lntp|awk '/LISTEN/ {print $2}' 



No será superfluo tener un gráfico con un desglose por estado de las conexiones TCP:



Y renderizar por separado el time-wait/established , porque sus valores, por regla general, son muy diferentes del resto:



Además de estas métricas, hay muchas otras, pero más obvias, por ejemplo, la carga en la interfaz de red o CPU. Su elección ya dependerá más de los detalles de su carga de trabajo.

En lugar de una conclusión


En general, eso es todo. Intenté describir los puntos clave que tuve que enfrentar al configurar el proxy inverso http. Parece que la tarea no es difícil, pero con un aumento en la carga, también aumenta el número de escollos que siempre aparecen en el momento equivocado. Espero que este artículo te ayude a evitar las dificultades que tuve que enfrentar.

Toda la paz

Source: https://habr.com/ru/post/440782/


All Articles