Notas de lectura de código fuente de Redis 6.0 (4) -Análisis de código fuente de tipo de datos de cadena

1. Estructura de almacenamiento

En la introducción del objeto String de redis String , sabemos que redis tiene tres formas de almacenamiento para el almacenamiento de cadenas. La estructura de memoria de su almacenamiento se muestra en la siguiente imagen de ejemplo:

  • OBJ_ENCODING_INT: La longitud de la cadena guardada es menor que 20 y se puede analizar como un valor entero de tipo long, entonces el método de almacenamiento esApunte directamente el puntero ptr de redisObject a este valor entero
    Inserte la descripción de la imagen aquí

  • OBJ_ENCODING_EMBSTR: Las cadenas con una longitud inferior a 44 (OBJ_ENCODING_EMBSTR_SIZE_LIMIT) se almacenarán en el redisObject en forma de cadenas dinámicas simples (SDS), peroEl encabezado del objeto redisObject seguirá existiendo con el objeto SDS
    Inserte la descripción de la imagen aquí

  • OBJ_ENCODING_RAW: La cadena se almacena en forma de cadena dinámica simple (SDS),El encabezado del objeto redisObject y el objeto SDS son generalmente dos piezas discontinuas de memoria en la dirección de memoria
    Inserte la descripción de la imagen aquí

2. Análisis del código fuente del almacenamiento de datos

2.1 Proceso de almacenamiento de datos

  1. En el lado del servidor (1) -Redis, inicie el código fuente de Redis 6.0 para leer las notas y la ejecución del comando , ya sabemos que el cliente para guardar una cadena de setcomandos será llamado a t_string.c#setCommand()funcionar, su código fuente para lograr lo siguiente:

    En este método se llaman las siguientes dos funciones clave. Esta sección se centra principalmente en la función tryObjectEncoding ()

    1. tryObjectEncoding () Intenta codificar el objeto de cadena que debe guardarse transmitido desde el cliente para ahorrar memoria
    2. setGenericCommand () guarda el valor-clave en la base de datos
    void setCommand(client *c) {
          
          
    
     ......
    
     c->argv[2] = tryObjectEncoding(c->argv[2]);
     setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
    }
    
  2. object.c#tryObjectEncoding() La lógica de la función es muy clara, se puede ver que se realizan principalmente las siguientes operaciones:

    1. Cuando la longitud de la cadena es inferior a 20 y puede resolverse como un tipo de datos largo, los datos se almacenarán en forma de número entero y, en robj->ptr = (void*) valueesta forma, el almacenamiento de asignación directa
    2. Cuando la longitud de la cadena es menor o igual que la configuración OBJ_ENCODING_EMBSTR_SIZE_LIMIT y todavía es codificación sin formato, llame a la función createEmbeddedStringObject () para convertirla a codificación embstr
    3. Este objeto de cadena ya no se puede transcodificar, por lo que tengo que llamar a la función trimStringObjectIfNeeded () para intentar eliminar todo el espacio libre del objeto de cadena
    robj *tryObjectEncoding(robj *o) {
          
          
     long value;
     sds s = o->ptr;
     size_t len;
    
     /* Make sure this is a string object, the only type we encode
      * in this function. Other types use encoded memory efficient
      * representations but are handled by the commands implementing
      * the type. */
     serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
    
     /* We try some specialized encoding only for objects that are
      * RAW or EMBSTR encoded, in other words objects that are still
      * in represented by an actually array of chars. */
     if (!sdsEncodedObject(o)) return o;
    
     /* It's not safe to encode shared objects: shared objects can be shared
      * everywhere in the "object space" of Redis and may end in places where
      * they are not handled. We handle them only as values in the keyspace. */
      if (o->refcount > 1) return o;
    
     /* Check if we can represent this string as a long integer.
      * Note that we are sure that a string larger than 20 chars is not
      * representable as a 32 nor 64 bit integer. */
     len = sdslen(s);
     if (len <= 20 && string2l(s,len,&value)) {
          
          
         /* This object is encodable as a long. Try to use a shared object.
          * Note that we avoid using shared integers when maxmemory is used
          * because every object needs to have a private LRU field for the LRU
          * algorithm to work well. */
         if ((server.maxmemory == 0 ||
             !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
             value >= 0 &&
             value < OBJ_SHARED_INTEGERS)
         {
          
          
             decrRefCount(o);
             incrRefCount(shared.integers[value]);
             return shared.integers[value];
         } else {
          
          
             if (o->encoding == OBJ_ENCODING_RAW) {
          
          
                 sdsfree(o->ptr);
                 o->encoding = OBJ_ENCODING_INT;
                 o->ptr = (void*) value;
                 return o;
             } else if (o->encoding == OBJ_ENCODING_EMBSTR) {
          
          
                 decrRefCount(o);
                 return createStringObjectFromLongLongForValue(value);
             }
         }
     }
    
     /* If the string is small and is still RAW encoded,
      * try the EMBSTR encoding which is more efficient.
      * In this representation the object and the SDS string are allocated
      * in the same chunk of memory to save space and cache misses. */
     if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
          
          
         robj *emb;
    
         if (o->encoding == OBJ_ENCODING_EMBSTR) return o;
         emb = createEmbeddedStringObject(s,sdslen(s));
         decrRefCount(o);
         return emb;
     }
    
     /* We can't encode the object...
      *
      * Do the last try, and at least optimize the SDS string inside
      * the string object to require little space, in case there
      * is more than 10% of free space at the end of the SDS string.
      *
      * We do that only for relatively large strings as this branch
      * is only entered if the length of the string is greater than
      * OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
     trimStringObjectIfNeeded(o);
    
     /* Return the original object. */
     return o;
    }
    
  3. object.c#createEmbeddedStringObject() La función para implementar la codificación embstr también es muy simple, los pasos principales son los siguientes:

    1. Primero llame a la función zmalloc () para solicitar memoria. Puede ver que no solo se aplica la memoria de la cadena a almacenar y la memoria de redisObject, sino también la memoria de sdshdr8, una de las estructuras de implementación de SDS.Esta es la razón por la que la codificación embstr mencionada anteriormente solo se aplica a la memoria una vez, y el encabezado del objeto redisObject seguirá existiendo con el objeto SDS.
    2. Apunte el puntero ptr del objeto redisObject a la dirección de memoria comenzando desde sdshdr8
    3. Llenando cada objeto de atributo sdshdr8, incluida lenla longitud de la cadena de alloccaracteres , la capacidad de la matriz de caracteres se almacena realmente en la bufmatriz de caracteres de la cadena
    robj *createEmbeddedStringObject(const char *ptr, size_t len) {
          
          
     robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);
     struct sdshdr8 *sh = (void*)(o+1);
    
     o->type = OBJ_STRING;
     o->encoding = OBJ_ENCODING_EMBSTR;
     o->ptr = sh+1;
     o->refcount = 1;
     if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
          
          
         o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
     } else {
          
          
         o->lru = LRU_CLOCK();
     }
    
     sh->len = len;
     sh->alloc = len;
     sh->flags = SDS_TYPE_8;
     if (ptr == SDS_NOINIT)
         sh->buf[len] = '\0';
     else if (ptr) {
          
          
         memcpy(sh->buf,ptr,len);
         sh->buf[len] = '\0';
     } else {
          
          
         memset(sh->buf,0,len+1);
     }
     return o;
    }
    
  4. Crear cadena codificada sin formato puede hacer referencia a object.c#createRawStringObject()una función, que se relaciona con dos aplicaciones de memoria, sds.c#sdsnewlen()la memoria de la aplicación para crear un objeto SDS, object.c#createObject()para los objetos de memoria creados redisObject

    robj *createRawStringObject(const char *ptr, size_t len) {
          
          
     return createObject(OBJ_STRING, sdsnewlen(ptr,len));
    }
    
  5. De la función de detectar el tamaño de la capacidad t_string.c#checkStringLength(),La longitud máxima de la cuerda es 512M., Se informará un error si se supera este valor

    static int checkStringLength(client *c, long long size) {
          
          
     if (size > 512*1024*1024) {
          
          
         addReplyError(c,"string exceeds maximum allowed size (512MB)");
         return C_ERR;
     }
     return C_OK;
    }
    

2.2 SDS de cadena dinámica simple

2.2.1 Estructura de SDS

SDS(简单动态字符串) En Redis, es una herramienta para el almacenamiento de cadenas. Esencialmente, sigue siendo una matriz de caracteres, pero no termina con '\ 0' como las cadenas de lenguaje C.

La cadena tradicional C se ajusta a la codificación ASCII. La característica de esta operación de codificación es: parada en cero. Es decir, al leer una cadena, siempre que encuentre '\ 0', se considera que ha llegado al final y todos los caracteres después de '\ 0' se ignoran. Además, el método para obtener la longitud de la cuerda es atravesar la cuerda y detenerse en cero. La complejidad del tiempo es O (n), que es relativamente ineficiente.

La estructura de implementación se define en la SDS sds.h, que se define a continuación. Debido a que SDS juzga si llega al final de la cadena en función del atributo len del encabezado, puede calcular de manera eficiente la longitud de la cadena y agregar datos rápidamente

Hay 5 definiciones de encabezado en la estructura sds,El propósito es proporcionar encabezados de diferentes tamaños para cadenas de diferentes longitudes para ahorrar memoria. Tome sdshdr8 como ejemplo, su atributo len es de tipo uint8_t y el tamaño de la memoria es de 1 byte, por lo que la longitud máxima de la cadena almacenada es 256. El encabezado contiene principalmente los siguientes atributos:

  1. len: La longitud real de la cadena, excluyendo el terminador nulo
  2. alloc: La longitud de la matriz buf excluyendo el encabezado y el terminador, que es la capacidad máxima
  3. flags: Tipo de encabezado del logotipo
  4. buf: Matriz de caracteres, en realidad almacena caracteres
/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    
    
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    
    
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    
    
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    
    
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    
    
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

2.2.2 Ajuste de capacidad SDS
  1. La función de la expansión SDS es sds.c#sdsMakeRoomFor(),Cuando la longitud de la cadena es menor a 1M, la expansión duplicará el espacio existente. Si excede 1M, la expansión solo expandirá 1M más de espacio a la vez.. La siguiente es la implementación del código fuente:

    Antes de que la longitud de la cadena sea inferior a SDS_MAX_PREALLOC (1024 * 1024, que es 1 MB, definido en sds.h), la capacidad se amplía 2 veces, es decir, se reserva el 100% de espacio redundante. Cuando la longitud excede SDS_MAX_PREALLOC, cada expansión solo asignará más espacio redundante del tamaño de SDS_MAX_PREALLOC para evitar un espacio redundante excesivo después de duplicar la expansión y causar desperdicio

    sds sdsMakeRoomFor(sds s, size_t addlen) {
          
          
     void *sh, *newsh;
     size_t avail = sdsavail(s);
     size_t len, newlen;
     char type, oldtype = s[-1] & SDS_TYPE_MASK;
     int hdrlen;
    
     /* Return ASAP if there is enough space left. */
     if (avail >= addlen) return s;
    
     len = sdslen(s);
     sh = (char*)s-sdsHdrSize(oldtype);
     newlen = (len+addlen);
     if (newlen < SDS_MAX_PREALLOC)
         newlen *= 2;
     else
         newlen += SDS_MAX_PREALLOC;
    
     type = sdsReqType(newlen);
    
     /* Don't use type 5: the user is appending to the string and type 5 is
      * not able to remember empty space, so sdsMakeRoomFor() must be called
      * at every appending operation. */
     if (type == SDS_TYPE_5) type = SDS_TYPE_8;
    
     hdrlen = sdsHdrSize(type);
     if (oldtype==type) {
          
          
         newsh = s_realloc(sh, hdrlen+newlen+1);
         if (newsh == NULL) return NULL;
         s = (char*)newsh+hdrlen;
     } else {
          
          
         /* Since the header size changes, need to move the string forward,
          * and can't use realloc */
         newsh = s_malloc(hdrlen+newlen+1);
         if (newsh == NULL) return NULL;
         memcpy((char*)newsh+hdrlen, s, len+1);
         s_free(sh);
         s = (char*)newsh+hdrlen;
         s[-1] = type;
         sdssetlen(s, len);
     }
     sdssetalloc(s, newlen);
     return s;
    }
    
  2. La función de escalado SDS es sds.c#sdsclear(), a partir de la implementación del código fuente, tiene principalmente las siguientes operaciones, a saberNo libera la memoria real ocupada, lo que refleja una especie de estrategia perezosa.

    1. Restablezca el valor del atributo len del encabezado SDS a 0
    2. Poner el terminador en la parte superior de la matriz buf es equivalente a borrar perezosamente el contenido en buf
    /* Modify an sds string in-place to make it empty (zero length).
    * However all the existing buffer is not discarded but set as free space
    * so that next append operations will not require allocations up to the
    * number of bytes previously available. */
    void sdsclear(sds s) {
          
          
     sdssetlen(s, 0);
     s[0] = '\0';
    }
    

Supongo que te gusta

Origin blog.csdn.net/weixin_45505313/article/details/108292168
Recomendado
Clasificación