redis常用的数据结构:
1.string
string 在redis中以sds (simple dynamic string)对象存在,数据结构:
struct sdshdr{
//记录buf数组中已使用字节的数量
//等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
由于是c语言写的,并没有string类型,所以string又一个char数据存储,而每次判断长度比较麻烦,所以直接填充了length,避免每次都进行遍历。
无论是int 型还是char,还是string,都是以sds存在,使用type统一返回都是string类型,如果我们想查看下具体哪种底层编码,可以使用object encoding keyName
string 类型如果value 是int(可以转化就自动转化)那么encoding 为int,如果为字符串,那么为embstr,如果数据进行了append,那么就变成了raw,因为对于append操作,redis直接创建了一个引用来连接,避免直接修改源内存。
2.set
set底层为hashtable,或者为intset,当我们add的数能强转成int时,且大小不超过512,全部为int或者为空,那么就是用intset,intset实际上是一个排序的数组,所以对于查找使用了二分查找,时间复杂度为logn,不满足条件时,则转化成hashtable.
typedef struct intset{
//编码方式
uint32_t encoding;
//集合包含的元素数量
uint32_t length;
//保存元素的数组
int8_t contents[];
}intset;
hashtable就是entry数组构成的,entry为双联表,时间复杂度为o1。
3.list
list由ziplist或者 linkedlist构成,ziplist是一个连续的数据,每个node占一个index,每个node带有上一个node的岂止地址,这样也达到了next,pre的效果。
linkedlist为双向链表。
4.hash
hash由ziplist或者hashtable构成,ziplist为数组结构,且大小为偶数,这样k-v按照奇偶顺序进行排列。
typedef struct dictht{
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩码,用于计算索引值
//总是等于 size-1
unsigned long sizemask;
//该哈希表已有节点的数量
unsigned long used;
}dictht
hash也有负载因子 size的定义,容量也是只能为capacity的大小,超过时进行扩容。
②、解决哈希冲突:这个问题上面我们介绍了,方法是链地址法。通过字典里面的 next 指针指向下一个具有相同索引值的哈希表节点。
③、扩容和收缩:当哈希表保存的键值对太多或者太少时,就要通过 rerehash(重新散列)来对哈希表进行相应的扩展或者收缩。具体步骤:
1、如果执行扩展操作,会基于原哈希表创建一个大小等于 ht[0].used2n 的哈希表(也就是每次扩展都是根据原哈希表已使用的空间扩大一倍创建另一个哈希表)。相反如果执行的是收缩操作,每次收缩是根据已使用空间缩小一倍创建一个新的哈希表。
2、重新利用上面的哈希算法,计算索引值,然后将键值对放到新的哈希表位置上。
3、所有键值对都迁徙完毕后,释放原哈希表的内存空间。
④、触发扩容的条件:
1、服务器目前没有执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且负载因子大于等于1。
2、服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且负载因子大于等于5。
ps:负载因子 = 哈希表已保存节点数量 / 哈希表大小。
⑤、渐近式 rehash
什么叫渐进式 rehash?也就是说扩容和收缩操作不是一次性、集中式完成的,而是分多次、渐进式完成的。如果保存在Redis中的键值对只有几个几十个,那么 rehash 操作可以瞬间完成,但是如果键值对有几百万,几千万甚至几亿,那么要一次性的进行 rehash,势必会造成Redis一段时间内不能进行别的操作。所以Redis采用渐进式 rehash,这样在进行渐进式rehash期间,字典的删除查找更新等操作可能会在两个哈希表上进行,第一个哈希表没有找到,就会去第二个哈希表上进行查找。但是进行 增加操作,一定是在新的哈希表上进行的。
5.zset
zset是由ziplist/skip list构成的,
ziplist也是连续的数组结构,按照score进行排序存放。
ziplist:
6 ziplist是由连续的node数组构成的,数据结构由
这样我们可以直接知道ziplist有多少个node,最后的byte地址,开始地址,且每个node都带有了previous_entry_ength,这样我们就可以通过当前node的地址,拿到上一个节点的开始地址,这样相当于pre。
redis的hashtable,
实际上redis的hashtable是有两个hash存储的,这样当rehash时,就可以实现渐进扩容,避免一次性全部rehash,影响性能。