Redis底层实现--字符串

Redis字符串存储实现原理
  • Redis 中的字符串是可以修改的字符串,在内存中他是以字节数组的形式存在的。我们在入门语言C语言里面的字符串标准形式是以NULL(即0x\0)作为结束符,但是Redis里面,字符串表示方法不是这样,因为,要获取以null结尾的字符串需要遍历整个字符串,时间复杂度是O(n),对应单线程对外服务的Redis来说是无法承受的。
  • Redis的字符串结构叫做SDS,Simple Dynamic String。他的结构是一个带上长度信息的字节数组,类似C语言中的结构体。
struct SDS<T>{
	T capacity;			//数组容量
	T len;				//已有数据长度
	byte flags; 		//特殊标志位
	byte[] content;      //数组主体内容
}
  • Redis中SDS存储结构的设计类似于ArrayList机构,因为Redis允许字符串的修改,因此初始申请可以有一部分的冗余空间。
    • capacity 标识所有分配数组的长度,包括未存储数据的部分空间
    • len标识字符串的实际长度
    • 当冗余的空间不够时候,先扩容,在复制旧的内容,然后在添加新内容,如果字符串长度非常长,内存的分配和复制开销会特别大。
  • 以上结构体中,使用的泛型T,其中Capacity和len的类型是T,因为Redis对存储的压缩优化
    • 当存储字符串比较短的时候,了你和capacity可以使用byte和short来表示,
  • Redis规定字符串长度不超过512M,创建字符串时候len和capacity一样长,不会多分配冗余空间,这是因为绝大多数场景下我们不会去修改字符串。
embstr OR raw
  • Redis字符串有两种存储方式,在长度短的时候,使用embstr形式存储,长度超过44 字节时候,使用raw形式存储,如下实验:
新docker-redis:0>set codehole aaaaaaaabbbjjbjbdjjskjkjsdeuiopoiioioioioioi
"OK"
新docker-redis:0>debug object codehole
"Value at:0x7f0a3b3c22c0 refcount:1 encoding:embstr serializedlength:41 lru:12955894 lru_seconds_idle:8"
新docker-redis:0>set codehole aaaaaaaabbbjjbjbdjjskjkjsdeuiopoiioioioioioi1111
"OK"
新docker-redis:0>debug object codehole
"Value at:0x7f0a3d6d66a0 refcount:1 encoding:raw serializedlength:43 lru:12955928 lru_seconds_idle:2"
  • 一下我们通过分析Redis字符串对象存储结构来说明两个问题

    • 问题一:为什么是44个字节作为界限
    • 问题二:embstr 和raw存储的区别
  • Redis对象存储都会有一个头部结构,如下形式

struct RedisObject{
	int4 type;			//4bit
	int4 encoding;		//4bit
	int24 lru;			//24bit 3byte
	int32 refcount;		//32bit 4byte
	void *ptr;			//8byte, 64bit system
} robj;
  • 不同的对象具有不同的type 类型(4bit)。

  • 同一个类型的type也会有不同的存储方式encoding(4bit)。

  • 为了记录对象的lru信息,使用了24bit来记录lru信息

  • 每个对象都有一个引用计数,refcount,当他归零时候,对象不被任何地方使用,对象将被销毁,内存被回收

  • ptr指针结构将指向对象的具体存储位置(body)

  • 以上所有的综合一起 4bit+ 4bit+ 24bit + 32 bit + 64bit = 128bit = 16byte,所有Redis对象的对象头结构都需要占据16字节存储空间。

  • 接着我们在分享SDS结构体大小,在字符串比较小的时候,SDS对象头结构的大小如下:

struct SDS{
	int8 capacity;		//1byte
	int8 len;			//1byte
	int8 flags;			//1byte
	byte[] content; 	//存储数据的数组,长度capacity
}
  • 如上结构中 capacity ,len, flags 三个都占用1byte的内存,其他的就是 capacity长度的数组,用来存储具体数据。也就是最少也要3 个字节的存储空间。加上上面的16byte,我们一个没有存储字符串的Redis字符串对象,已经有19 byte的空间被系统各种属性占用。

  • 我们在内存分配的时候,使用jemalloc, tcmalloc等分配内存大小的单元都是2/4/4/8/16/32/64 byte,

  • 为了容纳完整的embstr对象,jemalloc最少分配32byte空间,如果字符串稍微长点,那就是64byte,如果字符串超过64byte,Redis会认为是一个大字符串,不在适合emdstr存储的形式,而使用raw形式

  • 我们用最大内存空间64 来计算最大字符串长度, 64 - 19 = 45 ,但是之前实验得到的是44

  • SDS结构中content中字符串是以null结尾,多出这个字节,便于直接使用glbc的字符串处理函数,以及便于字符串的调试打印输出。最终得出了44 的长度。如下图:
    在这里插入图片描述

  • 问题二中embstr存储形式与 raw的存储形式如下

    • embst存储将RedisObject对象头结构和SDS对象连续存储在一起,使用malloc方法一次性分配内存
    • raw存储形式不一样,他需要两次malloc方法,两个对象头在内存地址上不连续通过对象头中 ptr指针来寻址存储位置。
      在这里插入图片描述
扩容策略
  • 字符串的扩容两种方式:
    • 字符串长度在1MB之前,扩容空间都是加倍扩容,也就是保留100%的冗余空间
    • 字符串长度超过1MB后,避免加倍后冗余空间浪费过多,每次只多分配1MB大小的冗余空间。

上一篇:Redis服务信息–Info指令

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值