当前位置:首页 > 问答 > 正文

Redis源码里那些数据类型到底是咋回事,深入浅出带你理解解析

说到Redis为啥这么快,很多人都会提到它是内存数据库,但这只说对了一半,它肚子里那些高效的数据结构才是真正的功臣,你要是以为Redis就只是简单地把数据扔进内存,用个哈希表糊弄一下,那就大错特错了,它的作者可精明了,为了省内存和提速度,设计了好几套“组合拳”,今天咱们就掰开揉碎,看看这些数据类型在源码里到底是怎么玩的。

你得知道Redis最基础的键值对是怎么存的,这就要提到一个核心的结构体,叫redisObject(来源:Redis源码server.h),你可以把它想象成每个数据值的“身份证”或者“包装盒”,这个盒子里有几个关键信息:type用来标明它是字符串、列表、哈希还是别的什么类型;encoding则更底层,它决定了这个类型具体用什么数据结构来实现,这是Redis灵活高效的关键;还有一个ptr指针,它才真正指向数据存放的内存地址。

Redis源码里那些数据类型到底是咋回事,深入浅出带你理解解析

咱们就从最简单的字符串(String)说起,你可能会想,字符串不就是个char数组吗?Redis可不这么傻,当你的字符串很短,比如就几个字符时,Redis会用一种叫EMBSTR的编码方式(来源:Redis源码object.c),把redisObject头和字符串本身紧挨着分配在同一块内存里,这样做的好处是只需要一次内存分配,找起来也快,能充分利用CPU缓存,如果字符串很长,或者能被表示成整数、浮点数,它又会变身为INTRAW编码,用最合适的方式存起来,一个简单的String背后,可能就有三种不同的实现。

再说说列表(List),早些年,Redis的列表是用标准的双向链表实现的,这很好理解,就像一根链条,每个节点指向前一个和后一个,增删节点很快,但缺点是比较耗内存,因为每个节点除了存数据,还得存两个指针,后来Redis引入了ziplist(压缩列表)(来源:Redis源码ziplist.c),这东西就厉害了,它是一大块连续的内存,像数组一样一个挨着一个存放数据和长度信息,特别省空间,当列表元素很少、且每个元素体积也很小时,Redis默认就用ziplist,但ziplist的缺点是修改起来可能牵一发而动全身,需要重新分配内存,所以当元素变多或变大时,Redis会自动把它转换成快速的双向链表,你看,它会根据情况“变脸”。

Redis源码里那些数据类型到底是咋回事,深入浅出带你理解解析

哈希(Hash)和有序集合(Zset)玩的是同样的“花招”,对于小的哈希表,Redis也用ziplist来存,把字段和值依次摆开,一旦数据量上来了,就立马升级成标准的哈希表(字典),用哈希函数来快速定位,保证操作速度,有序集合就更复杂一点,它同时使用了两种数据结构:一个哈希表用来快速根据成员名查分数,另一个叫skiplist(跳跃表)(来源:Redis源码t_struct.h)的结构用来维持分数排序,跳跃表可以理解为一种多层级的链表,从上往下查找,能快速跳过大量节点,效率接近平衡树,但实现起来简单多了。

最后提一下集合(Set),当集合里全是整数且元素不多时,Redis会用一种叫intset(整数集合)(来源:Redis源码intset.h)的神奇结构,它也是把整数紧凑地放在一个数组里,极度省内存,一旦你往里塞了非整数,或者整数太多,它就会“摇身一变”成为标准的哈希表(只不过哈希表里只存键,值设为空)。

所以你看,Redis源码里的数据类型根本不是死板的,它们就像一个个智能机器人,会根据你存入数据的大小、类型,自动选择最经济、最高效的底层实现(encoding),这种设计思想,才是Redis在性能和内存之间取得完美平衡的终极秘诀,它永远在帮你做最优选择,而你却几乎无感,这才是高手的设计。