三分钟了解redis-string存储结构

csdn的富文本编辑部分表格会乱列,未找到解决方式,先用截图了 


1.embstr和raw的区别?

如上图所示,embstr的数据储存空间是连续的

连续空间带来的优势:

  • 一次空间的分配、一次空间的释放

  • 查找的更快

2.redis字符串是如何储存的?

通过redisObject + sds 存储

RedisObject

typedef struct redisObject {
    // 类型                     4bit
    unsigned type:4;
    // 编码                     4bit
    unsigned encoding:4;
    // 对象最后一次被访问的时间    24bit
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
    // 引用计数                 4bytes
    int refcount;
    // 指向实际值的指针           8bytes
    void *ptr;
} robj;

type:类型

#define REDIS_STRING 0  // 字符串
#define REDIS_LIST 1    // 列表
#define REDIS_SET 2     // 集合
#define REDIS_ZSET 3    // 有序集
#define REDIS_HASH 4    // 哈希表

encoding:记录了对象所使用的编码,也就是说对象使用了什么数据结构作为对象底层实现。

#define REDIS_ENCODING_RAW 0            // 编码为字符串
#define REDIS_ENCODING_INT 1            // 编码为整数
#define REDIS_ENCODING_EMBSTR 2         // 编码为字符串embstr
#define REDIS_ENCODING_HT 3             // 编码为哈希表
#define REDIS_ENCODING_ZIPMAP 4         // 编码为 zipmap
#define REDIS_ENCODING_LINKEDLIST 5     // 编码为双端链表
#define REDIS_ENCODING_ZIPLIST 6        // 编码为压缩列表
#define REDIS_ENCODING_INTSET 7         // 编码为整数集合
#define REDIS_ENCODING_SKIPLIST 8       // 编码为跳跃表

ptr:指向实际保存值的数据结构

lru:对象最后一次被命令程序访问的时间

refcount:该对象被引用的次数

SDS

struct sdshdr {
    // 已使用长度 占4位字节
    unsigned int len;
    // 未使用长度 占4位字节
    unsigned int free;
    // 字节数组,存储字符串
    char buf[];
};
// redis根据字符串的长度创建不同的存储类以达到节约空间的目的。

// 3.2代码逻辑:存储上<44位强制用了sdshdr8,>44的至少使用sdshdr8往后的类,而key存储时有额外的复制逻辑转化并使用到sdshdr5。
// 因此sdshdr5只用在key上,键的底层是sdshdr5,而值的robj底层是根据字符串长度从sdshdr8往后的类开始创建。

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags;       (=0,只有3位有效位,因为类型的表示就是0到4,所有这个8位的flags 有5位没有被用到)   
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    // 已使用长度   占1位字节
    uint8_t len; 
    // 总长度
    uint8_t alloc;
    // 类的标识位
    unsigned char flags;       (=1)
    // 字节数组,存储字符串
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; 
    uint16_t alloc; 
    unsigned char flags;       (=2)
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len;
    uint32_t alloc;
    unsigned char flags;      (=3)
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; 
    uint64_t alloc; 
    unsigned char flags;      (=4)
    char buf[];
};

  

3.为什么用SDS,SDS的优势?

全称为simple dynamic string (简单动态字符串)。

Redis是C语言开发的,C语言自己就有字符类型,但是Redis却没直接采用C语言的字符串类型,而是自己构建了动态字符串(SDS)的抽象类型。

序号

优势

对比

1

计数方式更快

C

C采用遍历获取字符串长度,时间复杂度O(n)

SDS

而SDS储存了字符串的长度,时间复杂度O(1)

2

杜绝缓冲区溢出

C

假设内存中连续空间已存字符串['a','b','c']与['d','e'],此时内存空间如下

a

b

c

\0

d

e

如果C中未执行空间重分配,此时将['a','b','c']2改为['a','b','c','f','g'],内存空间如下

a

b

c

f

g

\0

字符串['d','e']的内容被意外修改

SDS

先检查当前剩余空间是否满足修改之后所需的空间,如果不满足会自动将SDS的空间扩展至修改之后所需空间大小

3

减少内存重分配次数

C

每次对字符串做操作时都要重新分配空间,如果忘记重分配,会造成内存溢出或泄露

SDS

存储了未使用空间,实现了空间预分配和惰性空间释放两种优化策略,减少了内存分配次数 (空间换时间)

4.内存分配?

1.空间预分配策略,不仅分配需要使用的空间,还会额外分配未使用空间

  1. 判断capacity长度,是否足够存储新增的字符,如果满足,则无需扩容,否则进行扩容;

  2. 如果len<=1MB,则加倍进行扩容,即len*2,然后继续进行判断;

  3. 如果len>1MB时,则1次只会增加1MB的空闲空间,然后继续进行判断;

  4. 字符串长度最大扩容到512MB,超出后报错

2.惰性回收

当字符串收缩时,程序不会立即执行内存重分配来回收收缩后内存多出来的空间,以备将来使用。

5.为什么小于44(3.2版本)/39(3.0版本)个字节时用embstr,大于用raw?

embstr是连续的空间,redis的内存只能开辟32,64这种

a) RedisObject长16字节

b) 3.2后的SDS(非SDS_5)结构体至少有3字节(uint8_t len、uint8_t alloc、unsigned char flags),

3.0前的SDS结构体至少有8个字节(unsigned int len、unsigned int free)

c) 字符串需要存储结束位\0

故3.2后可使用的空间为64-16-3-1=44

3.0前可使用的空间为64-16-8-1=39

3.0长 4 + 4 = 8字节

unsigned int len;

unsigned int free;

3.2长 1+1+1 = 3字节

uint8_t len;

uint8_t alloc;

unsigned char flags;

redis数据结构扩展(未完成,记录用)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值