Redis数据类型及编码格式——介绍及String篇

本文介绍了Redis中的核心对象redisObject,并详细讨论了Redis的数据类型,特别是String类型。文章阐述了SDS(Simple Dynamic Strings)的优势,如快速获取长度、防止缓冲区溢出、处理非文本内容以及空间预分配和惰性释放策略。同时,文章还探讨了Redis String类型的三种编码格式:int、embstr和raw,解释了它们的应用场景和内存管理方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Redis核心对象

reids中定义了一个数据结构用来统一表示各种数据类型,它叫做redisObject

typedef struct redisObject {
    unsigned type:4;    //记录数据值的类型:string、list、hash、set、zset
    unsigned encoding:4;    //记录数据值的编码格式
    unsigned lru:LRU_BITS;  //记录操作时间,当redis内存超限时,该值可辅助lru算法清理数据
    int refcount;   //记录当前对象被引用的次数
    void *ptr;    //记录存储数据位置的指针
} robj;

Redis数据类型

截止到redis6版本,一共定义了以下七种基本数据类型

#define OBJ_STRING 0    /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */
#define OBJ_MODULE 5    /* Module object. */  下面这两个数据类型是之前没有的
#define OBJ_STREAM 6    /* Stream object. */

可通过命令type key来查看数据值的所属类型

 SDS

要了解redis字符串的编码格式,首先来了解以下SDS

redis使用SDS(“简单动态字符串”)结构体来存储字符串,redis源码中定义了如下几种结构体,根据字符串长度使用不同结构体来存储,尽可能对底层进行优化

/* 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[];
};

sds结构体相比c语言字符串的好处

  1. 获取字符长度的复杂度不同,C语言字符串没有记录字符长度,获取字符串长度时必须遍历整个字符串,时间为O(n),而SDS结构体中len字段记录了字符长度,直接读取即可,时间为O(1)
  2. 杜绝缓冲区溢出,C语言字符串拼接时,处理不当可能会因为分配的内存不够而造成缓冲区溢出,而SDS会根据长度来判断空间是否足够,空间不足时进行空间扩展,所以不会出现缓冲区溢出(注:redis单个key的值不能超过512M)
  3. 非文本内容存储,C语言字符串的字节数组是以\0结尾,而在处理图片或其他非文本数据时,可能会读取到该字符而进行提前结束,从而导致识别失败,而Redis记录了字符长度,所以不会出现提前结束而失败的情况
  4. 空间预分配,由于C语言字符串不记录自身长度,所以每个字符串的底层都是N+1个字符长的数组(包含末尾的结束符\0),而每次操作(增长或缩短)字符串时,就需要对这个字符串的数组进行内存重新分配,而Redis通过空间预分配策略,可以减少增长字符串时对SDS字符串内存的重分配次数
  5. 惰性空间释放,当SDS的字符串缩短时,程序并不立即使用重新分配来回收空出来的字节,而是内存大小及使用字符长度记录下来,等待下次使用

以下是sds结构体和C语言字符串的结构图

 Redis编码格式

redis数据值在底层存储的编码格式,对用户是完全透明的

redis针对每种数据类型都设计了多种编码格式进行数据存储,然后根据具体情况更改为更合适的编码格式来进行数据存储,以下是定义的所有编码格式:

#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of listpacks */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */

可通过命令object encoding key来查看数据值使用的编码格式

Redis string数据类型-编码格式

redis中string数据类型的值会采用三种编码格式来进行存储

  • int格式,当数据值为数字且长度小于20时,就会采用int格式进行存储,此时不需要用到sds结构体,直接将redisObject中ptr字段的值改为该数字(注:当数值为0-10000时,key会指向预先创建好的redisObject,而不需要新建redisObject)
    len = sdslen(s);
    if (len <= 20 && string2l(s,len,&value)) {
        //长度小于20
        if ((server.maxmemory == 0 ||
            !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
            value >= 0 &&
            value < OBJ_SHARED_INTEGERS)
        {
            //当数值为0-10000时会使用预先创建好的共享redisObject对象
            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);
            }
        }
    }

  • embstr格式,当数据值为数字且长度大于20时,或者数据值为字符串且长度不大于44时,就会采用embstr格式进行存储,此时就需要使用sds结构体(注:该种编码格式是将sds字符串与其对应的redisObject对象分配在同一块连续的内存空间,彷佛sds字符串嵌入在redisObject中一样)
    //此是object.c第119行的代码
    #define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
    robj *createStringObject(const char *ptr, size_t len) {
        if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)  //如果长度小于44则使用embstr编码格式
            return createEmbeddedStringObject(ptr,len);
        else
            return createRawStringObject(ptr,len);  //否则使用raw编码格式
    }
    //该是object.c第85行的代码
    //是创建embstr编码格式字符串的具体代码
    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; //将sds字符串结构体跟其对应的redisObject放在一起
        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;
    }

    类似于这种结构:

  •     raw格式,当字符串长度大于44时,将会使用raw编码格式,SDS字符串结构体的内存跟其对应的redisObject对象的内存也不一定是连续的了
    //该是object.c第78行代码
    //是创建raw编码格式字符串的方法
    robj *createRawStringObject(const char *ptr, size_t len) {
        return createObject(OBJ_STRING, sdsnewlen(ptr,len));
    }
    //该是object.c第42行代码
    //为公用的创建对象的代码
    robj *createObject(int type, void *ptr) {
        robj *o = zmalloc(sizeof(*o));
        o->type = type;
        o->encoding = OBJ_ENCODING_RAW;
        o->ptr = ptr;  //这里跟创建embstr时不一样,是直接指向传入的指针
        o->refcount = 1;
    
        /* Set the LRU to the current lruclock (minutes resolution), or
         * alternatively the LFU counter. */
        if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
            o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
        } else {
            o->lru = LRU_CLOCK();
        }
        return o;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值