redis数据结构-动态字符串

简介

redis是使用C语言开发,但是redis的字符串并未采用c语言中传统的字符串,而是自己单独构建了一个sds(Simple Dynamic String)结构的数据类型来储存字符串,那么为什么redis不采用c语言传统的字符串去实现呢?这是因为原生的c语言有以下几点限制:

  1. c语言传统的字符串获取字符串长度的时候是O(n)复杂度的,这对于较大字符串来说非常耗费性能,而sds是O(1)。
  2. c字符串无法防止缓冲区溢出,sds储存可以规避这个风险。
  3. 每次修改字符串都需要重新分配内存,而sds可以减少修改字符串带来的内存重分配次数。
  4. 只能储存字符串,满足不了其他数据类型的储存,而sds以二进制方式储存,可以满足任何类型数据储存。

SDS数据结构

struct SDS<T> {
	T alloc; // 数组容量
	T len; // 数组长度
	byte flags; // 特殊标识位
	byte[] content; // 数组内容
}

其中buf储存的是字符串的内容,len表示使用长度,alloc是已经分配的空间大小,flags是一个标志位。

字符串的两种储存形式embstr、raw

redis字符串有两种储存方式,在长度比较短的时候储存按照embstr储存,如果字符串长度超过44的时候会按照raw形式储存,那么这两种储存方式有啥却别呢?为什么界限是44?

例子:

127.0.0.1:6379> SET uc qwertyuiopasdfghjklzxcvbnm123456789012345678
OK
127.0.0.1:6379> DEBUG OBJECT uc
Value at:0x7fa340d058d0 refcount:1 encoding:embstr serializedlength:45 lru:13853248 lru_seconds_idle:3

127.0.0.1:6379> SET uc qwertyuiopasdfghjklzxcvbnm1234567890123456789
OK
127.0.0.1:6379> DEBUG OBJECT uc
Value at:0x7fa340d057d0 refcount:1 encoding:raw serializedlength:46 lru:13853256 lru_seconds_idle:4

如上述代码执行结果可以看出,在字符串长度为44的时候储存形式为embstr,长度为45的时候储存形式为raw,那么为什么这两种储存方式有什么区别呢,为了解释这种现象我们先来了解下RedisObject结构头对象。

struct RedisObject {
	int4 type; // 4bits
	int4 encoding; // 4bits
	int24 lru; // 24bits
	int32 refcount; // 4bytes
	void *ptr; // 8bytes,64-bit system
}

type表示储存字符串的类型,encoding表示储存形式,lru记录了key的访问信息,refcount表示对象的引用计数,当计数为0的时候对象就会被回收,ptr指向对象内容具体储存位置,该对象头占据16个字节,redis内存分配是按照2、4、8、16、32、64等分配的,并且认为超过64大小的字符串成为大字符串,超过64字节的字符串会以raw形式进行储存。

embstr内存分配是连续内存,sds与redisObject是一起分配的。
raw内存分配是分开的,需要进行两次分配。
储存结构图

注意:上面我们说到当储存的字符串超过64字节的时候才会按照raw形式储存,因为redisObject为16bytes,然后字符串结尾必须有一个\0占1bytes,sds内部capacity、len、flags占3bytes,所以最终可用于储存字符串内容的空间为64-16-1-3=44bytes。

redis字符串的扩容机制

因为redis字符串内部采用的是sds结构储存,在字符串进行append操作的时候,如果发现分配空间不足则会进行扩容,当字符串长度小于1M的时候按照加倍策略,超过1M的时候每次扩容只会多分配1M大小的空间。

小于1M的字符串扩容:

127.0.0.1:6379> SET uc qwertyuiopasdfghjklzxcvbnm123456789012345678
OK
127.0.0.1:6379> INFO memory
# Memory
used_memory:1025280

127.0.0.1:6379> APPEND uc 1
(integer) 45
127.0.0.1:6379> INFO memory
# Memory
used_memory:1025328

因为uc这个字符串首次占据的是64bytes,减去对象头字符串内容占据48bytes大小,1025328-1025280=48bytes,由此得出确实是加倍扩充的。

大于1M的字符串扩容:

127.0.0.1:6379> STRLEN "\xac\xed\x00\x05t\x00\x011"
(integer) 1048564
127.0.0.1:6379> INFO memory
# Memory
used_memory:2073808

127.0.0.1:6379> APPEND "\xac\xed\x00\x05t\x00\x011" 1
(integer) 1048565
127.0.0.1:6379> INFO memory
# Memory
used_memory:3122384

如上图,当字符串长度由1048564变为1048565时,内存占用也由2073808变为了3122384,3122384-2073808=1048576=1M。

注意:这里特别解释下为什么是1048564这个数,1M不应该是1048576-3=1048573,其实直接用结果反推就可以得到原因,1M=1048576-1048564=12bytes,为什么是12bytes呢,前面我们说过sds有三个字段capacity、len、flags,这三个字段是会随着字符串长度变大而随之变的,当字符串小的时候redis采用的是1bytes储存,随后根据需求变动2bytes、4bytes、8bytes。

参考文献:
1.Redis 深度历险:核心原理与应用实践

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值