Resumen del mecanismo de control de flujo y contrapresión de Flink

El autor revisó recientemente mi comprensión de los detalles de la pila de tecnología Flink y descubrió que hay un punto ciego relativamente grande en la pila de red de Flink, los mecanismos de control de flujo y contrapresión. Aunque me he ocupado del problema de la contrapresión del trabajo muchas veces, no entiendo completamente la implementación detrás de él. Así que escribí un resumen, apoyándome sobre los hombros de los grandes para comprender a fondo cómo Flink controla el flujo y maneja la contrapresión.

Dirección del flujo de datos de la transmisión de la red Flink

El flujo de datos de la transmisión de la red Flink se muestra en la siguiente figura:
Inserte la descripción de la imagen aquí
Al enviar datos, el remitente primero escribe en el búfer de red interno de TaskManager y usa Netty para la transmisión; los datos que se enviarán se almacenan en el ChannelOutboundBuffer de Netty y luego se envían a través de el búfer de envío del Socket Sal. El receptor es lo contrario cuando recibe datos, y también necesita pasar por tres capas de almacenamiento en búfer, a saber, búfer de recepción de socket → Netty ChannelInboundBuffer → búfer de red TaskManager. Lograr el control del flujo es hacer un escándalo en el proceso anterior.

Propagación de contrapresión de Flink

La contrapresión es el mecanismo de retroalimentación dinámica de la capacidad de procesamiento en el sistema de transmisión, y es la retroalimentación de aguas abajo a aguas arriba. La siguiente figura muestra la lógica del flujo de datos entre Flink TaskManager.

Inserte la descripción de la imagen aquí
Se puede ver que una vez que se produce la contrapresión debido a una capacidad de procesamiento aguas abajo insuficiente, la propagación de la señal de contrapresión debe dividirse en dos etapas: una es desde la entrada del TaskManager aguas abajo (InputGate) hasta la salida del TaskManager directamente aguas arriba TaskManager (ResultPartition); el otro es Propagar de salida a entrada dentro de TaskManager. Por supuesto, lo que debemos tener en cuenta es la propagación de contrapresión entre TaskManager, porque su enlace es relativamente largo (consulte el diagrama de flujo de datos en la sección anterior) y es más probable que se convierta en un cuello de botella.

Primero introduzcamos el mecanismo de control de flujo y contrapresión en la versión anterior.

Antes de Flink 1.5: control de flujo basado en TCP y contrapresión Antes de la versión 1.5, Flink no implementaba específicamente su propio mecanismo de control de flujo, sino que dependía directamente del mecanismo de ventana deslizante del protocolo TCP en la capa de transporte (el curso de red informática de la universidad debe hablar). Repasemos cómo la ventana deslizante de TCP implementa el control de flujo a través de ejemplos.

  1. La situación inicial se muestra en la figura siguiente. El remitente envía 3 paquetes por unidad de tiempo, el tamaño inicial de la ventana de envío es 3; el receptor recibe 1 paquete por unidad de tiempo y el tamaño inicial de la ventana de recepción es 5 (el mismo tamaño que el búfer de recepción).

Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí

  1. El receptor consume un paquete, desliza la ventana de recepción hacia adelante un espacio e informa al remitente ACK = 4 (indicando que puede comenzar a enviar desde el cuarto paquete) y Ventana = 3 (indicando que el margen vacante actual de la ventana de recepción es 3) .

Inserte la descripción de la imagen aquí

  1. El remitente envía 4 ~ 6 tres paquetes después de recibir el mensaje ACK, y el receptor los coloca en el búfer después de recibirlo.
    Inserte la descripción de la imagen aquí
  2. El receptor consume un paquete, desliza la ventana de recepción hacia adelante e informa al remitente ACK = 7 (indicando que puede comenzar a enviar desde el séptimo paquete) y Ventana = 1 (indicando que el margen libre actual de la ventana de recepción es 1). Después de recibir el mensaje ACK, el Remitente descubre que el Receptor solo puede recibir 1 paquete más, ajusta el tamaño de la ventana de envío a 1 y envía el paquete 7, logrando el propósito de limitar la corriente.
    Inserte la descripción de la imagen aquí
    Inserte la descripción de la imagen aquí
    Después de analizar este proceso, podemos saber que Sender no podrá enviar datos eventualmente (porque Receiver reporta Window = 0), y no continuará enviando hasta que Receiver consuma los datos en la caché. Al mismo tiempo, el remitente enviará periódicamente mensajes de detección de ZeroWindowProbe al receptor para garantizar que el receptor pueda informar la capacidad de consumo al remitente a tiempo.

A continuación, se utiliza un ejemplo para presentar el proceso de contrapresión.

  • Como se muestra en la figura, la relación entre la velocidad de envío del remitente y la velocidad de recepción del receptor es 2: 1, y al principio puede enviar y recibir normalmente.

Inserte la descripción de la imagen aquí

  • Después de un período de tiempo, el búfer del InputChannel en el lado del receptor se agota, por lo que se aplicará un nuevo búfer al grupo de búfer local LocalBufferPool.

Inserte la descripción de la imagen aquí

  • Después de un período de tiempo, la cuota disponible de LocalBufferPool se agotará, por lo que se aplicará una nueva caché al grupo de búfer de red NetworkBufferPool

Inserte la descripción de la imagen aquí

  • A medida que los datos continúen acumulándose, la cuota de NetworkBufferPool también se agotará. En este momento, no hay espacio para recibir nuevos datos. La lectura automática de Netty se cerrará y no se leerán más datos del caché de Socket.

Inserte la descripción de la imagen aquí

  • Una vez que se agota el búfer de Socket, Receiver informa Window = 0 (vea la ventana deslizante arriba), y Sender Socket dejará de enviar datos.

Inserte la descripción de la imagen aquí

  • La acumulación de la caché de Socket en el lado del remitente evita que Netty envíe más datos.

Inserte la descripción de la imagen aquí

  • Los datos que se enviarán están atrasados ​​en el ChannelOutboundBuffer del remitente. Cuando la cantidad de datos excede la marca de agua máxima de Netty, el canal se configura como no escribible y ResultSubPartition ya no escribe datos en Netty.

Inserte la descripción de la imagen aquí

  • Una vez que el búfer ResultSubPartition en el lado del remitente esté lleno, continuará solicitando nuevos búferes de LocalBufferPool y NetworkBufferPool, al igual que InputChannel en el lado del receptor, hasta que los búferes se agoten y RecordWriter ya no pueda escribir datos.

Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
De esta forma, nos hemos dado cuenta de la transferencia de contrapresión al TaskManager ascendente.

Después de Flink 1.5: el control de flujo basado en crédito
y las soluciones de control de flujo y contrapresión basadas en TCP de contrapresión tienen dos desventajas principales:

Siempre que una tarea ejecutada por TaskManager provoque contrapresión, el Socket del TaskManager y el TaskManager ascendente ya no pueden transmitir datos, lo que afecta a todas las demás tareas normales y al flujo de Checkpoint Barrier, lo que puede provocar una avalancha de trabajos;

El enlace de propagación de la contrapresión es demasiado largo y necesita agotar todos los búferes de la red antes de que pueda activarse de manera efectiva, y el retraso es relativamente grande.

Para resolver estos dos problemas, Flink 1.5+ introdujo el control de flujo y el mecanismo de contrapresión basado en Credit. Básicamente, promueve el mecanismo de control de flujo de TCP desde la capa de transporte a la capa de aplicación, es decir, los niveles ResultPartition y InputGate, evitando así la congestión en la capa de transporte. Específicamente:

  • ResultSubPartition en el lado del remitente contará el volumen de mensajes acumulados (en términos de la cantidad de búferes) y notificará al InputChannel en el lado del receptor en forma de tamaño de la acumulación;

  • El InputChannel en el lado del Receptor calculará cuánto espacio está disponible para recibir mensajes (también se cuenta como el número de búferes) y notificará a ResultSubPartition en el lado del Remitente en forma de crédito.

En otras palabras, el remitente y el receptor realizan con precisión el control de flujo al informarse mutuamente sobre sus capacidades de procesamiento (tenga en cuenta que el tamaño de la acumulación y el crédito también pasan por la capa de transporte, no se intercambian directamente). A continuación, usaremos ejemplos para ilustrar el proceso de control de flujo y contrapresión basado en Crédito.

  • Sigue siendo un escenario en el que la relación entre la velocidad de envío del remitente y la velocidad de recepción del receptor es 2: 1. ResultSubPartition en el lado del remitente tiene una acumulación de 2 datos almacenados en caché, por lo que el lote de datos que se enviará se enviará al receptor junto con el tamaño de la acumulación = 2. Una vez que Receiver recibe el lote actual de datos y el tamaño del backlog, calculará si InputChannel tiene suficiente búfer para recibir el siguiente lote de datos. Si no es suficiente, irá a LocalBufferPool / NetworkBufferPool para solicitar el búfer y notificará al ResultSubPartition ascendente de crédito = 3, lo que significa que puedo recibir 3 mensajes en caché.

imagen

Con la acumulación continua de datos en el lado del receptor, la memoria caché de la red eventualmente se agotará, por lo que se retroalimentará al crédito ascendente = 0 (equivalente a la ventana = 0 en la ventana deslizante de TCP) y al enlace entre ResultPartition en el lado del remitente y Netty se bloqueará. De acuerdo con el proceso descrito en la sección anterior, la caché de la red en el lado del remitente se agotará más rápido y el RecordWriter ya no podrá escribir datos, logrando así el efecto de contrapresión.

imagen

De lo anterior se puede ver que la señal de contrapresión no necesita retroalimentarse con los datos a través de la capa de transmisión entre TaskManagers, lo que reduce en gran medida el retraso de la contrapresión. Y no bloqueará todo el enlace de Socket debido a la contrapresión de una tarea, y puede controlar el flujo en la granularidad de la tarea con bastante precisión, que no solo es liviana, sino también eficiente.

Supongo que te gusta

Origin blog.csdn.net/qq_43081842/article/details/112404882
Recomendado
Clasificación