Al desarrollar aplicaciones de red altamente cargadas, es necesario equilibrar la carga.
Una herramienta de equilibrio L7 popular es Nginx. Le permite almacenar en caché las respuestas, elegir diferentes estrategias e incluso escribir en LUA.
A pesar de todos los encantos de Nginx, si:
- No es necesario trabajar con HTTP (s).
- Debe exprimir al máximo la red.
- No es necesario almacenar en caché nada: el equilibrador tiene servidores API limpios con dinámica.
Puede surgir la pregunta: ¿por qué necesita Nginx? ¿Por qué desperdiciar el equilibrio de recursos en L7? ¿No es más fácil simplemente reenviar el paquete SYN? (Enrutamiento directo L4).
Equilibrio de capa 4 o cómo equilibrar en la antigüedad
Una herramienta popular de reenvío de paquetes fue IPVS. Realizó tareas de equilibrio a través del túnel y enrutamiento directo.
En el primer caso, se estableció un canal TCP para cada conexión y el paquete del usuario fue al equilibrador, luego al minion y luego en el orden inverso.

En este esquema, el problema principal es visible: en la dirección opuesta, los datos van primero al equilibrador y luego al usuario (Nginx funciona de la misma manera). Se realiza un trabajo innecesario, dado el hecho de que generalmente se envían más datos al usuario, este comportamiento conduce a una pérdida de rendimiento.
No existe un método de equilibrio (pero dotado de nuevo) llamado Enrutamiento directo. Esquemáticamente, se ve así:

En el caso del enrutamiento directo, los paquetes de retorno van directamente al usuario, sin pasar por el equilibrador. Obviamente, tanto los recursos del equilibrador como la red se guardan. Al ahorrar recursos de red, no se trata tanto de ahorrar tráfico, porque la práctica habitual es conectar los servidores a una red separada y no tener en cuenta el tráfico, pero el hecho de que incluso la transferencia a través del equilibrador es una pérdida de milisegundos.
Este método impone ciertas restricciones:
- El centro de datos donde se ubica la infraestructura debe permitir falsificar direcciones locales. En el diagrama anterior, cada minion debe enviar paquetes de vuelta en nombre de IP 10.10.0.1, que se asigna al equilibrador.
- El equilibrador no sabe nada sobre el estado de los minions. En consecuencia, las estrategias de menor tiempo de conexión y menor tiempo no son factibles desde el primer momento. En uno de los siguientes artículos, intentaré implementarlos y mostrar el resultado.
Aquí viene NFTables
Hace unos años, Linux comenzó a promover activamente NFTables como un reemplazo para IPTables, ArpTables, EBTables y todos los demás [az] {1,} Tablas. En el momento en que en Adram tuvimos la necesidad de exprimir cada milisegundo de la respuesta de la red, decidí sacar el verificador y buscar, o tal vez ipTables aprendió a hacer el reenvío de iphash y puede acelerarlo para equilibrarlo. Luego me topé con nftables, que puede y no solo eso, sino que iptables aún no puede hacer esto.
Después de varios días de prueba, finalmente pude obtener enrutamiento directo y enrutamiento de canales a través de NFTables en el laboratorio de pruebas y también compararlos con nginx.
Entonces, el laboratorio de pruebas. Tenemos 5 autos:
- nft-router: un enrutador que realiza la tarea de conectar el cliente y la subred de AppServer. Hay 2 tarjetas de red: 192.168.56.254 - mira la red del servidor de aplicaciones, 192.168.97.254 - mira a los clientes. Ip_forward está habilitado y todas las rutas están registradas.
- nft-client: cliente desde el cual se perseguirá ab, ip 192.168.97.2
- nft-balancer: balanceador. Tiene dos IP: 192.168.56.4, a las que acceden los clientes y 192.168.13.1, desde la subred de minion.
- nft-minion-a y nft-minion-b: minions ipy: 192.168.56.2, 192.168.56.3 y 192.168.13.2 y 192.168.13.3 (intenté usar la misma red y diferentes para equilibrar). En las pruebas, me detuve ante el hecho de que los minions tienen tipos "externos", en la subred 192.168.56.0/24
Todas las interfaces MTU 1500.
Enrutamiento directo
Configuración de NFTables en el equilibrador:
table ip raw { chain input { type filter hook prerouting priority -300; policy accept; tcp dport http ip daddr set jhash tcp sport mod 2 map { 0: 192.168.56.2, 1: 192.168.56.3 } } }
Se crea una cadena sin procesar, en el gancho, con una prioridad de -300.
Si llega un paquete con una dirección de destino http, dependiendo del puerto de origen (hecho para probar desde una máquina, realmente necesita ip saddr), se selecciona 56.2 o 56.3 y se configura como la dirección de destino en el paquete, y luego se envía más a lo largo de las rutas. En términos generales, para los puertos pares 56.2, para los puertos impares, respectivamente, 56.3 (de hecho, no, porque para los hashes pares / impares, pero es más fácil de entender exactamente eso). Después de configurar la IP de destino, el paquete vuelve a la red. No se produce NAT, el paquete llega a los minions con la IP de origen del cliente y no con el equilibrador, que es importante para el enrutamiento directo.
Configuración de Minion NFT:
table ip raw { chain output { type filter hook output priority -300; policy accept; tcp sport http ip saddr set 192.168.56.4 } }
Se crea un enlace de salida sin formato con una prioridad de -300 (la prioridad es muy importante aquí, a niveles más altos, la menulación necesaria no funcionará para los paquetes de respuesta).
Todo el tráfico saliente desde el puerto http está firmado por 56.4 (equilibrador de ip) y se envía directamente al cliente, sin pasar por el equilibrador.
Para verificar si todo funcionará correctamente, traje el cliente a otra red y lo dejé pasar por el enrutador.
También deshabilité arp_filter, rp_filter (para que la suplantación funcionara) y habilité ip_forward tanto en el equilibrador como en el enrutador.
Para los bancos, en el caso de NFT, Nginx + php7.2-FPM se utiliza a través de un socket Unix en cada minion. No había nada en el equilibrador.
En el caso de Nginx, utilizamos: nginx en el equilibrador y php7.2-FPM sobre TCP en minions. Como resultado, no equilibré el servidor web detrás del equilibrador, sino inmediatamente FPM (que será más honesto con nginx y más coherente con la vida real).
Para NFT, solo se usó la estrategia hash (
nft dr en la tabla), para nginx: hash (
ngx eq ) y menos conn (
ngx lc )
Se han realizado varias pruebas.
- Pequeño guión rápido (pequeño) .
<?php system('hostname');
- El guión con un retraso aleatorio (rand) .
<?php usleep(mt_rand(100000,200000)); echo "ok";
- Un script con el envío de una gran cantidad de datos (tamaño) .
<?php $size=$_GET['size']; $file='/tmp/'.$size; if (!file_exists($file)) { $dummy=""; exec ("dd if=/dev/urandom of=$file bs=$size count=1 2>&1",$dummy); } fpassthru (fopen($file,'rb'));
Se utilizaron los siguientes tamaños:
512.1440.1460.1480.1500.2048.65535.655350 bytes.
Antes de las pruebas, calenté los archivos de estadísticas de cada minion.
Probado ab tres veces en cada prueba:
Inicialmente, planeé traer el tiempo de prueba, milisegundos y el resto, como resultado me detuve en RPS: son representativos y se correlacionan con los indicadores de tiempo.
Obtuve los siguientes resultados:
Prueba de tamaño - columnas - el tamaño de los datos dados.

Como puede ver, el enrutamiento directo nft gana por un amplio margen.
Contaba con algunos otros resultados relacionados con el tamaño del marco de ethernet, pero no se encontró correlación. Quizás el cuerpo 512 no cabe en 1500 MTU, aunque, dudo, la pequeña prueba será indicativa.
Noté que en grandes volúmenes (650k) nginx reduce la brecha. Tal vez esto tenga algo que ver con los búferes y el tamaño de Windows TCP.
El resultado de la prueba de rand. Muestra cómo se maneja conn menos en condiciones de diferente velocidad de ejecución del script en diferentes secuaces.

Sorprendentemente, nginx hash funcionó más rápido que la menor conexión, y solo en la pasada final la menor conexión avanzó un poco, lo que no pretende ser significativo.
Los números de los pases son muy diferentes debido al hecho de que salen 100 hilos a la vez, y el FPM-ok desde el principio carga alrededor de 10. Para el tercer pase, tuvieron tiempo de acostumbrarse, lo que muestra la aplicabilidad de las estrategias para los estallidos.
NFT se esperaba que perdiera esta prueba. Nginx optimiza bien la interacción con los FPM en tales situaciones.
pequeña prueba

nft gana marginalmente en RPS, menos conn vuelve a ser un extraño.
Por cierto, en esta prueba puede ver que se emiten 400-500RPS, aunque, en la prueba con el envío de 512 bytes, fue para 1500, parece que el sistema se come este millar.
Conclusiones
NFT funcionó bien en una situación de optimización de cargas uniformes: cuando se proporcionan muchos datos, se determina el tiempo de funcionamiento de la aplicación y los recursos del clúster son suficientes para procesar la transmisión entrante sin caer en picada.
En una situación en la que la carga para cada solicitud es caótica y es imposible equilibrar uniformemente la carga del servidor con el resto primitivo de la división hash, la NFT perderá.