Comprender los parámetros de límite de búfer de salida del cliente de Redis y el análisis del código fuente

descripción general

El límite de búfer de salida del cliente de Redis se puede usar para desconectar a la fuerza a los clientes que no pueden leer los datos del servidor Redis lo suficientemente rápido.
Las reglas del mecanismo de protección son las siguientes:

  1. [límite duro] Límite de tamaño, cuando un búfer de cliente excede el valor establecido, la conexión se cerrará directamente.
  2. [límite suave] Límite de duración, cierre la conexión cuando un búfer de cliente ocupa demasiado espacio durante un período de tiempo.

Este parámetro se utiliza generalmente en los siguientes tipos de clientes:

  • Cliente ordinario, incluido el monitor
  • cliente esclavo durante la sincronización maestro-esclavo
  • cliente en modo Pub/Sub

Introducción y análisis de la configuración

La sintaxis de configuración para este parámetro:

client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>

Ejemplo de configuración:

# 普通client buffer限制 
client-output-buffer-limit normal 0 0 0
# slave client buffer限制
client-output-buffer-limit slave 256mb 64mb 60
# pubsub client buffer限制
client-output-buffer-limit pubsub 32mb 8mb 60
  • límite de búfer de salida de cliente normal 0 0 0

Establecer el límite duro y el límite suave en 0 al mismo tiempo significa desactivar el límite.

  • cliente-salida-búfer-límite esclavo 256mb 64mb 60

Esta configuración significa que para el cliente esclavo, si el búfer de salida ocupa 256 M o supera los 64 M durante 60 s, la conexión del cliente se cerrará.

  • cliente-salida-búfer-límite pubsub 32mb 8mb 60

Esta configuración significa que para el cliente Pub/Sub, si el búfer de salida ocupa 32M o supera los 8M durante 60 s, la conexión del cliente se cerrará.

Descripción general:
en general, para los clientes ordinarios, no hay límite para el búfer de salida del cliente, porque el servidor solo enviará datos cuando el cliente los solicite y no habrá acumulación.
En el escenario en el que el servidor envía datos activamente y el cliente los maneja, estos generalmente se procesan de forma asíncrona y se configurará un búfer para "almacenar temporalmente" datos sin procesar. Si el servidor envía datos más rápido de lo que el cliente procesa los datos, Se produce una acumulación de búfer. Para los clientes utilizados como Pub/Sub y esclavos, el servidor les enviará datos activamente, por lo que es necesario establecer el límite del búfer de salida del cliente.

Ejemplo de análisis

A continuación, analicemos el cliente esclavo durante la sincronización maestro-esclavo.
Cuando redis está en sincronización maestro-esclavo, el maestro creará un búfer de salida para el esclavo. Antes de que el maestro guarde el rdb, transfiera el archivo rdb al esclavo y el esclavo termine de cargar el rdb, el maestro escribirá todos los comandos de escritura recibidos en el búfer de salida en la memoria.
Si el guardado, la transmisión y la carga de rdb tardan demasiado, o si hay demasiados comandos de escritura durante este período, se puede exceder el límite del búfer, lo que provoca que la conexión entre el maestro y el esclavo se desconecte. En este caso, debe ajustar  client-output-buffer-limit slavela configuración adecuadamente.

Análisis de código fuente: uso del búfer de salida durante la sincronización maestro-esclavo

Basado en el código fuente de la versión redis5.0

El servidor redis envía datos al cliente a través de addReply, consulte  https://github.com/redis/redis/blob/5.0/src/networking.c#L190-L211 para obtener el siguiente código fuente

/* Add the object 'obj' string representation to the client output buffer. */
void addReply(client *c, robj *obj) {
    if (prepareClientToWrite(c) != C_OK) return;

    if (sdsEncodedObject(obj)) {
        if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
            _addReplyStringToList(c,obj->ptr,sdslen(obj->ptr));
    } else if (obj->encoding == OBJ_ENCODING_INT) {
        char buf[32];
        size_t len = ll2string(buf,sizeof(buf),(long)obj->ptr);
        if (_addReplyToBuffer(c,buf,len) != C_OK)
            _addReplyStringToList(c,buf,len);
    } else {
        serverPanic("Wrong obj->encoding in addReply()");
    }
}

Al comienzo de la función, se evaluará prepareClientToWrite(c)si los datos deben escribirse en el búfer de salida del cliente. Veamos en qué condiciones se escribirán los datos en el búfer de salida del cliente y se devolverán  C_OK.

/* This function is called every time we are going to transmit new data
 * to the client. The behavior is the following:
 *
 * If the client should receive new data (normal clients will) the function
 * returns C_OK, and make sure to install the write handler in our event
 * loop so that when the socket is writable new data gets written.
 *
 * If the client should not receive new data, because it is a fake client
 * (used to load AOF in memory), a master or because the setup of the write
 * handler failed, the function returns C_ERR.
 *
 * The function may return C_OK without actually installing the write
 * event handler in the following cases:
 *
 * 1) The event handler should already be installed since the output buffer
 *    already contains something.
 * 2) The client is a slave but not yet online, so we want to just accumulate
 *    writes in the buffer but not actually sending them yet.
 *
 * Typically gets called every time a reply is built, before adding more
 * data to the clients output buffers. If the function returns C_ERR no
 * data should be appended to the output buffers. */
int prepareClientToWrite(client *c) {
    /* If it's the Lua client we always return ok without installing any
     * handler since there is no socket at all. */
    if (c->flags & (CLIENT_LUA|CLIENT_MODULE)) return C_OK;

    /* CLIENT REPLY OFF / SKIP handling: don't send replies. */
    if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;

    /* Masters don't receive replies, unless CLIENT_MASTER_FORCE_REPLY flag
     * is set. */
    if ((c->flags & CLIENT_MASTER) &&
        !(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;

    if (c->fd <= 0) return C_ERR; /* Fake client for AOF loading. */

    /* Schedule the client to write the output buffers to the socket, unless
     * it should already be setup to do so (it has already pending data). */
    if (!clientHasPendingReplies(c)) clientInstallWriteHandler(c);

    /* Authorize the caller to queue in the output buffer of this client. */
    return C_OK;
}

/* Return true if the specified client has pending reply buffers to write to
 * the socket. */
int clientHasPendingReplies(client *c) {
    return c->bufpos || listLength(c->reply);
}

void clientInstallWriteHandler(client *c) {
    /* Schedule the client to write the output buffers to the socket only
     * if not already done and, for slaves, if the slave can actually receive
     * writes at this stage. */
    if (!(c->flags & CLIENT_PENDING_WRITE) &&
        (c->replstate == REPL_STATE_NONE ||
         (c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)))
    {
        c->flags |= CLIENT_PENDING_WRITE;
        listAddNodeHead(server.clients_pending_write,c);
    }
}

Dado que la función regresa por defecto C_OK, solo necesitamos verificar qué tipos de casos no se devuelven C_OK, es decir C_ERR, los datos no se escribirán en el búfer de salida del cliente.
La situación volvió C_ERR:

  • El cliente es un cliente falso (para cargar archivos AOF)
  • El cliente es un maestro.
  • El estado del esclavo es SLAVE_STATE_ONLINE y su función de devolución de llamada falló ((c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)), o el estado del esclavo es REPL_STATE_NONE

Si el cliente no debe recibir nuevos datos, porque es un cliente falso (utilizado para cargar AOF en la memoria), un maestro o porque la configuración del controlador de escritura falló, la función devuelve C_ERR.

Cuando el maestro guarda y envía el archivo rdb, el estado del esclavo es el siguiente, por lo que los comandos de escritura durante este período se guardarán en el búfer de salida del esclavo. Dado que la función de devolución de llamada no está configurada, los datos no se enviarán al esclavo, sino que solo se almacenarán en el búfer de salida creado por el maestro para el esclavo.

#define SLAVE_STATE_WAIT_BGSAVE_START 6 /* We need to produce a new RDB file. */
#define SLAVE_STATE_WAIT_BGSAVE_END 7 /* Waiting RDB file creation to finish. */
#define SLAVE_STATE_SEND_BULK 8 /* Sending RDB file to slave. */

Entonces, ¿cuándo se "vaciará" el esclavo del búfer de salida? Hasta que el maestro envíe completamente el archivo rdb al esclavo, el maestro realizará  sendBulkToSlaveoperaciones relacionadas en la función. Consulte el siguiente código fuente: https://github.com/redis/redis/blob/5.0/src/replication.c#L876-L930

void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
    // 此处省略部分源码

    // rdb 文件已完全发送给 slave 
    if (slave->repldboff == slave->repldbsize) {
        close(slave->repldbfd);
        slave->repldbfd = -1;
        aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
        putSlaveOnline(slave);
    }
}

void putSlaveOnline(client *slave) {
    slave->replstate = SLAVE_STATE_ONLINE;
    slave->repl_put_online_on_ack = 0;
    slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */
    if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
        sendReplyToClient, slave) == AE_ERR) {
        serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno));
        freeClient(slave);
        return;
    }
    refreshGoodSlavesCount();
    serverLog(LL_NOTICE,"Synchronization with replica %s succeeded",
        replicationGetSlaveName(slave));
}

Aquí, el estado esclavo  SLAVE_STATE_ONLINEse cambiará repl_put_online_on_acka 0 (¿está familiarizado con él, por cierto, es clientInstallWriteHandlerel contenido juzgado anteriormente?). Al mismo tiempo, sendReplyToClientse configurará una función de devolución de llamada para enviar todas las operaciones de escritura en el búfer de salida creado por el maestro para el esclavo al esclavo. Al mismo tiempo, el cambio de estado del esclavo hará que las operaciones de escritura posteriores en el maestro se envíen al esclavo normalmente (directamente, sin pasar por el búfer de salida).

Resumir

Esta vez,  client-output-buffer-limitaprendimos sobre sus escenarios de uso a través de los parámetros y nos enfocamos en un análisis de código fuente simple de la situación de escritura del búfer de salida durante la sincronización maestro-esclavo. Eso es todo por el estudio de hoy, lo continuaremos otro día.

contenido de referencia

  1. redis client-output-buffer-limit setting - Piao Piao Xue - Blog Garden

Autor: Max Colmillo

Fuente: comprensión de los parámetros de límite de búfer de salida del cliente de Redis y análisis del código fuente - immaxfang - 博客园

Supongo que te gusta

Origin blog.csdn.net/weixin_47367099/article/details/127440490
Recomendado
Clasificación