"Diseño e implementación de Redis": cadena dinámica simple

cadena dinámica simple

Redis crea un tipo abstracto de cadena dinámica (cadena dinámica simple, SDS) y usa este tipo como la representación de caracteres predeterminada de Redis,
mientras que los literales de cadena del lenguaje C solo se usan como algunas cadenas que no necesitan ser manipuladas. modificar, si el lugar para imprimir el registro.

Definición de FDS

struct sdshdr {
    // 记录buf数组中已使用字符的数量,等于SDS保存字符串的长度
    int len;
    // 记录的是buf数组中未使用的字节的数量
    int free;
    // 字节数组,用于保存字符串
    char buf[];
};
复制代码

SDS sigue la convención terminada en nulo para cadenas C. Seguir esta convención permite que SDS reutilice directamente algunas funciones en cadenas C.

Diferencia entre SDS y cadena c

obtener la longitud de la cadena

La cadena c no registra la información de longitud de la cadena en sí, por lo que si desea obtener la longitud de la cadena c, debe realizar una operación transversal. La complejidad de esta operación es O (N). Para SDS, registros en sí mismo La longitud de SDS en sí, si desea obtener la información de longitud de la cadena, solo necesita O (1) complejidad

desbordamiento de búfer

Dado que la cadena C no registra su propia longitud, es fácil provocar un desbordamiento del búfer al modificar la cadena. Por ejemplo, hay dos cadenas adyacentes S1, S2, S1="Redis" en la memoria, S2="MongoDB"

image.pngCuando se llama a la operación de extracción (S1, "Cluster"), S1 se modificará a: Redis Cluster, pero si no se asigna suficiente espacio para S1 antes de llamar al método, los datos de S1 se desbordarán al espacio donde está S2 ubicado , lo que luego hace que se modifique S2.
A diferencia de la cadena C, SDS primero verificará si el espacio SDS cumple con el contenido modificado al modificarlo. De lo contrario, expandirá automáticamente el espacio al tamaño requerido y luego realizará la operación de modificación real. .

El número de reasignaciones de memoria provocadas por la modificación de la cadena.

Dado que la cadena C no registra su propia longitud, para una cadena C que contiene N caracteres, almacena una matriz de caracteres de longitud N+1 en la parte inferior (se usa un espacio adicional para almacenar una cadena vacía). Debido a tal asociación, siempre se requiere una operación de reasignación de memoria cada vez que se aumenta o acorta una cadena C.

  • 如果是增加字符的操作,那么在这个操作的之前需要通过重新分配内存来扩展底层数组空间的大小,不然就会产生缓冲的溢出
  • 如果是缩短字符串的操作,那么在这个操作之后就需要通过内存分配来释放不再使用的那部分的空间,如果忘记了就会产生内存的泄漏。

但Redis做为一个数据库,经常被使用在一些速度快,数据被频繁修改的场景里面,如果每次对于字符串的操作都需要进行一个内存的重分配的操作这是需要占用大量的时间的,且对性能造成影响。
所以为了避免这种缺陷,基于SDS的结构,SDS通过未使用空间解除了字符串长度与底层数组空间的关系:在SDS的底层,buf数组的长度就并不一定是字符串的长度加1,数组里面可以包含没有使用的字节,而这些字节的长度就由SDS的fee属性来记录。
基于SDS的未使用空间,SDS实现了空间预分配和惰性空间释放策略:

  • 空间预分配,这个操作简而言之就是当SDS进行修改需要扩张的时候,程序不仅会为SDS分配修改所需要的空间,同时也会为SDS分配额外的未使用空间。
    • 如果SDS进行修改之后,SDS的长度(len属性)如果小于1MB,那么程序分配和len同样大小的未使用空间,这个时候len属性将会与fee属性的值一样。
    • 如果SDS进行修改之后,SDS的长度大于等于1MB,那么程序会分配1MB的未使用空间。
  • 惰性空间释放,当SDS保存的字符串缩短的时候,程序并不会马上进行内存的重分配来回收缩短之后多出来的字节,而是使用fee这个属性将这些字节数量记录下来,并等待使用。

二进制安全

C字符串中字符必须符合某一种规则(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符会被误认为是字符串结尾。而这些限制使得C字符串只能保存文本内容。
但Redis需要的不仅仅是对文本内容的保存场景,有时候也需要对一些二进制数据保存的场景,为了使得Redis适用于不同的场景。SDS的所有API操作都是二进制安全的,所有SDS的API操作都会以二进制的方式来处理SDS存放在buf数组里面的数据,程序并不会对其中的数据做任何的限制,过滤和假设。数据被写入的时候是什么样的,它被读取的时候就是什么样的。

总结

  • Redis只会使用C字符串做字面量,在大多数的情况下Redis使用SDS作为字符串的表示。
  • 比起C字符串,SDS有以下的优点
    • 常数复杂度获取字符串的长度
    • 杜绝了缓冲区的溢出
    • 减少了修改字符串长度所需要的内存分配次数
    • 二进制安全
    • 兼容部分C字符串的函数

补充

Redis源码中关于SDS结构定义:

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[];
};
复制代码

Supongo que te gusta

Origin juejin.im/post/7079779869168500744
Recomendado
Clasificación