Programación de servidores de red de alto rendimiento: por qué epoll en Linux
El proceso básico de programación IO (incluida la IO de red y la IO de archivo) es abrir un descriptor de archivo (Windows es el controlador, Java es flujo o canal), captura multicanal (Multiplexe, es decir, seleccionar y poll y epoll) Estado de lectura y escritura de IO, y luego el descriptor de archivo que se puede leer y escribir se usa para la lectura y escritura de IO. Dado que la velocidad del dispositivo IO y la proporción de memoria de la CPU serán más lentas, para utilizar mejor la CPU. y memoria, se abrirán varios subprocesos, cada subproceso lee y escribe un descriptor de archivo.
Pero el problema del C10K nos hizo darnos cuenta de que con una cantidad extremadamente grande de conexiones de red, el equipo de la máquina y la velocidad de la red ya no son el cuello de botella. El cuello de botella reside en la forma en que el sistema operativo y las aplicaciones IO se comunican y colaboran. .
Por ejemplo, si se conectan 10,000 sockets, el modelo de programación IO tradicional necesita abrir 10,000 subprocesos para manejarlo. También tenga en cuenta que el socket se cerrará y abrirá, y 10,000 subprocesos deben cerrarse y cerrarse continuamente. Se desperdician subprocesos y recursos en esto. Calculamos que establecer un subproceso consume 1 M de memoria. Una máquina con 10,000 subprocesos requiere al menos 10 G de memoria. Esto es básicamente imposible en la arquitectura de la máquina IA-32. encendido). Ahora solo la arquitectura x64 puede ser más cómoda. Debes saber que esto es solo un cálculo aproximado del consumo de memoria. ¿Qué pasa con otros recursos?
Por lo tanto, para la programación de redes de alto rendimiento (es decir, programación IO), primero es necesario aflojar la relación correspondiente entre las conexiones IO y los subprocesos de la aplicación. Este es el requisito de no bloqueo (sin bloqueo). asincrónico (asincrónico) El motivo (construya un grupo de subprocesos, epoll monitorea numerosos fds, pasa los fds al grupo de subprocesos y estos subprocesos de trabajo leen y escriben io). En segundo lugar, se requiere un sistema operativo de alto rendimiento para notificar al dispositivo IO que el dispositivo IO puede leer y escribir (vienen datos): desde la notificación activada por nivel hasta la notificación activada por borde, hablaremos de este método de notificación más adelante.
Cabe señalar que asincrónico no es igual a AIO (IO asincrónico de Linux y AIO de Java son formas de lograr asincrónico, y ambos son basura. Hablaremos de esto a continuación.
Con respecto a los dos puntos mencionados anteriormente, veamos los problemas de selección y encuesta.
Ambas funciones requieren que monitoreemos la necesidad cada vez que se llaman (ver si Los descriptores de archivos sin datos) se pasan al kernel a través de una matriz. El kernel escanea estos descriptores de archivos cada vez, los comprende y establece una matriz correspondiente al descriptor de archivo y a IO (el trabajo real del kernel tendrá una mejor implementación, pero así es). se puede entender de esta manera), de modo que cuando llegue IO, se notifiquen estos descriptores de archivos, y luego se notifiquen las selecciones y encuestas que esperan en el proceso. ¿Qué pasa cuando hay 10.000 descriptores de archivos para monitorear (10.000 conexiones de red)? Esta eficiencia laboral es muy baja, pero los requisitos de recursos son muy altos.
Veamos epoll
epoll es muy inteligente. Está dividido en tres funciones. La primera función crea una cosa similar a una sesión y la segunda función le dice al kernel que la mantenga. sesión y transferir el fd en la sesión se pasa al kernel. La tercera función epoll_wait es una función de monitoreo real para múltiples descriptores de archivos. Solo necesito decirle al kernel qué sesión estoy esperando. en la sesión y ya no se analiza cada vez que se llama a epoll, esto le ahorra al kernel la mayor parte del trabajo. De esta manera, cada vez que se llama a epoll, el kernel ya no volverá a escanear la matriz fd porque mantenemos la sesión.
Aquí solo hay una palabra que decir, código abierto, alabanza. Cuando todos agreguen leña, las llamas se elevarán, alabanza.
La eficiencia de epoll no solo se refleja aquí, sino que también se mejora en el método de notificación del kernel. Veamos primero el método de notificación de selección y sondeo, que es una notificación activada por nivel. DMA después de capturar los datos del dispositivo IO, solo necesita encontrar a qué descriptor de archivo pertenecen los datos y luego notificar a la función de espera en el hilo. Sin embargo, seleccionar y sondear requiere que el kernel continúe escaneando el archivo recién creado. la fase de notificación.La matriz correspondiente al kernel fd e io, porque es posible que la aplicación no lea el fd después de la última notificación de datos. La aplicación no lo leyó la última vez, y el kernel debe continuar notificando esta vez cuando seleccione y. Se llama a la encuesta. El método de comunicación entre el sistema operativo y las aplicaciones es ineficiente. Es solo por conveniencia de programación (no es necesario que lea esa red io, Founder continuará informándole la próxima vez).
Entonces, epoll diseñó otro método de notificación: notificación activada por borde. En este modo, cuando los dispositivos IO reciben datos, solo se notificarán los FD correspondientes a estos dispositivos IO. Los FD notificados la última vez no serán notificados. notificado nuevamente que el kernel ya no escanea una gran cantidad de fds.
Según el análisis anterior, podemos ver que epoll es un diseño diseñado específicamente para la comunicación y colaboración entre el sistema operativo y las aplicaciones bajo conexiones concurrentes en redes grandes. Al programar un servidor de red en Linux, esto debe usarse. , nginx, PHP Los marcos asincrónicos domésticos se lavan y barnizan todos usan esto.
Tenga en cuenta que también debe activar la notificación activada por borde de epoll. Sin embargo, NIO y NIO.2 de Java solo usan epoll y no activan la notificación activada por borde, por lo que no son tan buenos como Netty de JBoss.
A continuación, hablemos del problema de AIO. Lo que AIO espera es que necesite usar una función para monitorear una gran cantidad de fd para seleccionar, sondear y epoll. Entonces, AIO no la necesita. Le dice a fd Kernel que su aplicación no necesita esperar. El kernel le informará a la aplicación a través de interrupciones suaves como señales. Cuando lleguen los datos, puede leerlos directamente. .
Sin embargo, la forma en que se implementa Linux AIO es que el kernel y la aplicación comparten un área de memoria, y la aplicación prueba si llegan datos detectando esta área de memoria (evitando llamar a funciones de lectura y escritura sin bloqueo, porque incluso si se llama lectura y escritura sin bloqueo siguen siendo ineficientes porque el proceso tiene que cambiar entre el modo de usuario y el modo kernel) para saber si fd tiene datos, pero la detección del área de memoria no es en tiempo real después de todo. realice un bucle en el hilo para monitorear la memoria y configurar el modo de suspensión, la eficiencia general no es tan buena como la de las notificaciones en tiempo real como epoll. Por lo tanto, AIO es basura y es adecuado para operaciones IO de baja concurrencia. Por lo tanto, el AIO introducido por NIO.2 introducido en java7 también es basura para los programas de diseño de IO de red de alta concurrencia. Solo la notificación activada por epoll + edge de Netty es la mejor, que puede lograr la comunicación más eficiente entre las aplicaciones y el sistema operativo en Linux.