看小林coding 和 黑马redis
常见问题
redis有哪些数据结构
sds简单动态字符串,(压缩列表)、哈希表、整数集合、跳表、(双向链表)
quicklist、listpack
😖Redis 为什么用跳表,而不用平衡树、红黑树?
三点(内存占用,实现难度,范围查找操作,插入删除操作)
- 从内 存占用上来比较 ,跳表比平衡树更灵活一些。平衡树每个节点包含 2 个指针,跳表每个节点平均包含1.333个指针,在redis中。
- 在做 范围查找 的时候,跳表比平衡树操作要简单(比如Zset的zrangebyscore命令) 。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点,而跳表是基于链表结构的,找到最小值之后可以根据链表遍历。
- 从算法 实现难度上 和 维护上 来比较,跳表比平衡树要简单得多 并且 容易维护。(跳表的发明者在论文中说,插入删除比平衡树更高效)平衡树插入删除时 引发树的调整,而跳表只需要改变相邻节点的指针。
😖Redis 为什么用跳表,而不用B+树?
两点
B+树是多路平衡树。它的核心思想是通过可能少的 IO 定位到尽可能多的索引来获得查询数据。在MYSQL中可以大大减小树的高度。
- 但是Redis 这种内存数据库来说,它对这些并不感冒(减 少IO 存储大量数据) ,因为 Redis 作为内存数据库它不可能存储大量的数据。
- 而且 跳表的 实现和维护也比B+树 简单,不需要像B+树那样维护的时候有分裂合并的操作。
😖Redis 为什么使用 ListPack 替代 ZipList?
- listpack 中每个节点不再包含前一个节点的长度了,压缩列表每个节点正因为需要保存前一个节点的长度字段,就会有连锁更新的隐患。
那epoll网络模型和jdk1.8中解决哈希冲突,为什么用红黑树 不用跳表?
跳表的缺点有什么?
- 红黑树更稳定,跳表其实是不稳定的(跳表插入元素时元素层数是随机选的!!!!!!!!)
- 红黑树占用空间更少,跳表更占用空间(还不确定)
Redis中哈希表如何解决哈希冲突的?
链表、链地址法,根据dictEntry结构体可以判断。
ZipList压缩列表 的连锁更新问题?
- 压缩列表的每个节点中,有个prelen字段记录前一个节点的长度。
- 如果前一个节点的长度小于254,那么prelen为1字节,如果前一个节点长度大于等于254,那么prelen为5字节。
- 这时如果压缩列表中很多节点长度都为250~253,如果改变其中一个节点长度使其大于等于254,那么就会引起后续节点的prelen连锁更新。
说说redis的渐进式rehash?(字节)
hash扩容,创建一个新的数组,将原数组中的key重新计算hash值放入新数组中。
过程:
- 每次进行新增、删除、查找或者更新操作时,顺序将哈希表1中某一个位置是的key value移动到哈希表2
- 扩容期间,新增元素往哈希表2中加,修改和查询元素在哈希表1和2中都找一遍。
sds简单动态字符串
5种sds类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64
这 5 种类型的主要区别就在于,它们数据结构中的 len 和 alloc 成员变量的数据类型不同。
intset整数集合
整数集合的升级操作,升级的时候倒序移动
连续内容,可以根据编码方式 进行数组的随机访问,例如访问第56个元素,根据编码方式快速寻址。
dict哈希表
两个dictht。rehash、渐进式rehash、rehash触发条件
typedef struct dictht {
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩码,用于计算索引值
unsigned long sizemask;
//该哈希表已有的节点数量
unsigned long used;
} dictht;
渐进式rehash扩容
为了避免 rehash 在数据迁移过程中,因拷贝数据的耗时。
- 在 rehash 进行期间,每次哈希表元素进行新增、删除、查找或者更新操作时,Redis 除了会执行对应的操作之外, 还会顺序将「哈希表 1 」中索 引位置上的所有 key-value 迁移到「哈希表 2」 上;
- 这样就巧妙地把一次性大量数据迁移工作的开销,分摊到了多次处理请求的过程中,避免了一次性 rehash 的耗时操作。
比如,查找一个 key 的值的话,先会在「哈希表 1」 里面进行查找,如果没找到,就会继续到哈希表 2 里面进行找到。
另外,在渐进式 rehash 进行期间,新增一个 key-value 时, 会被保存到「哈希表 2 」里面 ,而「哈希表 1」 则不再进行任何添加操作,这样保证了「哈希表 1 」的 key-value 数量只会减少,随着 rehash 操作的完成,最终「哈希表 1 」就会变成空表。
总结:
- 为了避免减少rehash的耗时,每次进行增删改操作时,顺序将哈希表1中某一个位置是的key value移动到哈希表2
- 扩容时,新增元素往哈希表2中加,修改和查询元素在哈希表1和2中都找一遍。
ziplist压缩列表
压缩列表在表头有三个字段:
- zlbytes,记录整个压缩列表占用对内存字节数;
- zltail,记录压缩列表「尾部」节点距离起始地址由多少字节,也就是列表尾的偏移量;
- zllen,记录压缩列表包含的节点数量;
- zlend,标记压缩列表的结束点,固定值 0xFF(十进制255)。
- prevlen,记录了「前一个节点」的长度,目的是为了实现从后向前遍历;(如果前一个节点长度小于254,prelen占1字节,如果前一个节点长度大于等于254,prelen占5字节)
- encoding,记录了当前节点实际数据的「类型和长度」,类型主要有两种:字符串和整数。
- data,记录了当前节点的实际数据,类型和长度都由 encoding ****决定;
ziplist连锁更新
跳表
skipList(跳表)首先是链表,但与传统链表相比有几点差异:
- 元素按照升序排列存储。
- 节点可能包含多个指针,指针跨度不同。
查找一个跳表节点的过程时,跳表会从头节点的最高层开始,逐一遍历每一层。
当数据量很大时,跳表的查找复杂度就是 O(logN)。
redis中的实现
listPack
简单了解
主要包含三个方面内容:
- encoding,定义该元素的编码类型,会对不同长度的整数和字符串进行编码;
- data,实际存放的数据;
- len,encoding+data的总长度;
可以看到,listpack 没有压缩列表中记录前一个节点长度的字段了,listpack 只记录当前节点的长度,当我们向 listpack 加入一个新元素的时候,不会影响其他节点的长度字段的变化,从而避免了压缩列表的连锁更新问题。