我们长话短说,本文会带您了解redis的五种基本类型底层的数据结构以及如何实现。
RedisObject
redis中任意类型的 key 和 value 都会被封装成一个RedisObject ,这哥们也叫作redis对象,
- 什么是redisObject:从redis使用者的角度来说,一个redis节点在非集群的情况下包含十六个库。redis是一个k-v型数据库,库内对于k-v的映射使用一个dict来维护的。 key很好说了,是一个sds,value就比较多变了,为了在一个dict内能够存储不同类型的value,就需要RedisObject这种通用的数据结构。
- redis会根据存储的数据类型不同,选择不同的编码方式。包含11种类型。
编号 | 编码方式 | 说明 |
---|---|---|
0 | OBJ_ENCODING_RAW | raw编码动态字符串 |
1 | OBJ_ENCODING_INT | long类型的整数的字符串 |
2 | OBJ_ENCODING_HT | hash表(字典dict) |
3 | OBJ_ENCODING_ZIPMAP | 已废弃 |
4 | OBJ_ENCODING_LINKEDLIST | 双端链表 |
5 | OBJ_ENCODING_ZIPLIST | 压缩列表 |
6 | OBJ_ENCODING_INTSET | 整数集合 |
7 | OBJ_ENCODING_SKIPLIST | 跳表 |
8 | OBJ_ENCODING_EMBSTR | embstr的动态字符串 |
9 | OBJ_ENCODING_QUICKLIST | 快速列表 |
10 | OBJ_ENCODING_STREAM | Stream流 |
五种数据结构
Redis中会根据存储的数据类型不同,选择不同的编码方式。每种数据类型的使用的编码方式如下:
数据类型 | 编码方式 |
---|---|
OBJ_STRING | int、embstr、raw |
OBJ_LIST | LinkedList和ZipList(3.2以前)、QuickList(3.2以后) |
OBJ_SET | intset、HT |
OBJ_ZSET | ZipList、HT、SkipList |
OBJ_HASH | ZipList、HT |
String
根据上文可知,String这种数据类型有三种编码方式, 分别是, int,embstr , raw。 基本的编码方式是Raw,基于sds实现。 小于44字节就会用embstr,此时redisobject和sds是一片连续的空间,redis底层直接做了redisObject 的内存空间 的下一位就是sds。他俩紧挨着的。如果存的字符串是整数值,并且大小在LONG_MAX的范围内,采用int编码。数据直接保存在redisObject的指向实际保存数据空间的指针的位置上(8字节)。不需要SDS。
图中的5只是redis用来测试的,实际上用不到。
总结:
String在redis里面确切的说使用一个redisObject表示的。Redisobject可能有三种编码格式,raw , embstr , int 。前两种用sds存储数据,int类型存放在指针位置,存long类型。
在对string进行incr,decr操作的时候,如果内部是int编码,可以直接进行加减操作。如果内部是另外俩哥们,redis试图把sds存储的字符串转换成long类型。如果能转成功,在进行加减操作。对一个内部表示long类型的string执行append,setbit,setrange这些命令,针对的还是string的值。而不是对long进行操作。这些操作会先把龙转成字符串操作。
List
Redis的List类型可以从首、尾操作列表中的元素
3.2版本之后,redis采用quickList作为list的数据结构。quickList是一种双向链表 + ziplist的数据结构。ziplist是链表的节点。
Set
Set是Redis中的单列集合,满足下列特点:
- 不保证有序性
- 保证元素唯一
- 求交集、并集、差集
redis采用dict存储元素,key存储元素,value统一为nil。
存储的数据都是整数,并且元素数量不超过set-max-intset-entrys,set采用intset编码,节省内存空间。
ZSET(important)
zet每一个元素都需要制定一个score和member。
- 可以根据score值排序
- member必须唯一
- 可以根据member查询分数
redis采用跳表加dict实现zset。因为跳表可以排序,同时存储score和member,但是跳表不能通过ele去找到对应的分数。而dict可以根据key找到value。所以redis结合二者。存两份数据,skipList里面一份,dict里面一份。这样查找排序效率非常高。就是比较耗内存。所以zset还会采用ziplist节省内存。 使用ziplist的条件(标准情况下):
- 元素数量小于128
- 每个元素小于64字节。
ziplist没有排序功能,需要编码实现。
- score和ele是紧挨在一起的两个entry。
- element在前,score在后,按照score升序排列。
Hash
Hash结构与Redis中的Zset非常类似:
- 都是键值存储
- 都需求根据键获取值
- 键必须唯一
区别如下:
- zset的键是member,值是score;hash的键和值都是任意值
- zset要根据score排序;hash则无需排序
底层实现方式:
- ziplist或者dict,hash数据比较少的情况下,hash底层采用压缩列表进行存储。数据到达一定程度的时候会被转成dict。转换的原因就是ziplist很大的时候会有如下的缺点:
- 每次插入或修改产生的realloc可能会造成内存拷贝,降低性能
- 一旦发生内存拷贝,内存拷贝的成本也要增加,因为要拷贝更大的一块数据
- ziplist数据项过多的时候,在他上面查找指定的数据项就会使性能变得低,因为ziplist查找需要遍历。
Hash结构默认采用ZipList编码,用以节省内存。 ZipList中相邻的两个entry 分别保存field和value(相对于zset,无非就是去了一个跳表,别的差不多)
当数据量较大时,Hash结构会转为HT编码,也就是Dict,触发条件有两个:
- ZipList中的元素数量超过了hash-max-ziplist-entries(默认512)
- ZipList中的任意entry大小超过了hash-max-ziplist-value(默认64字节)