一、Redis存储的数据的数据结构
=================
我们都只到Redis常用的数据结构为String,List,Hash,Set,Sorted Set。但这只是我们在用的时候键值对的表现形式,他们底层真正使用的数据结构为简单动态字符串,双向链表,压缩列表,哈希表,调表和整数数组
可以看到,String 类型的底层实现只有一种数据结构,也就是简单动态字符串。而 List、Hash、Set 和 Sorted Set 这四种数据类型,都有两种底层实现结构。通常情况下,我们会把这四种类型称为集合类型,它们的特点是一个键对应了一个集合的数据
二、Redis中键和值得数据结构
====================
1、redis键值的数据结构
因为redis本身要求获取速度快,那么时间复杂度肯定是O1,为了实现从键到值的快速访问,Redis 使用了一个哈希表来保存所有键值对。数据结构如下
redis是由一个全局hash表存储所有键和值得关系。值存的为对象的地址。
2、hash冲突
既然使用hash表,那么就肯定会有hash冲突。Redis 解决哈希冲突的方式,和JAVA中的hash表解决方式一样,也是链式哈希。链式哈希也很容易理解,就是指同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接。
因此当Key特别大的时候,存在着链式查找的一个时间消耗。特别是当我们存在几千万key的时候,那这个时间消耗也是非常多的
3、rehash阻塞
为了解决hash冲突带来的链表长度太长,因此redis引入对hash的rehash操作
rehash 也就是增加现有的哈希桶数量,让逐渐增多的 entry 元素能在更多的桶之间分散保存,减少单个桶中的元素数量,从而减少单个桶中的冲突。
具体操作
Redis 默认使用了两个全局哈希表:哈希表 1 和哈希表 2。一开始,当你刚插入数据时,默认使用哈希表 1,此时的哈希表 2 并没有被分配空间。随着数据逐步增多,Redis 开始执行 rehash,这个过程分为三步:
1、给哈希表 2 分配更大的空间,例如是当前哈希表 1 大小的两倍;
2、把哈希表 1 中的数据重新进行打散映射到hash表2中;
3、释放哈希表 1 的空间。
我们就可以从哈希表 1 切换到哈希表 2,用增大的哈希表 2 保存更多数据,而原来的哈希表 1 留作下一次 rehash 扩容备用
这个过程看似简单,但是第二步涉及大量的数据拷贝,如果一次性把哈希表 1 中的数据都迁移完,会造成 Redis 线程阻塞,无法服务其他请求。此时,Redis 就无法快速访问数据了。
为了避免这个问题,Redis 采用了渐进式 rehash。
4、渐进式rehash
简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries。如下图所示:
这样就巧妙地把一次性大量拷贝的开销,分摊到了多次处理请求的过程中,避免了耗时操作,保证了数据的快速访问。
二、压缩列表
======
之前也说了,集合类型的底层数据结构主要有 5 种:整数数组、双向链表、哈希表、压缩列表和跳表。其中,哈希表刚才已经讲过,整数数组、双向链表也比较常见。那么我们具体看下压缩列表和跳表
zlbytes:记录压缩列表占用的内存字节数,对压缩列表进行内存重分配或者计算zlen位置时使用,占用4个字节
Zltail:记录也锁列表未节点距离压缩列表的起始节点有多少字节,通过这个偏移量,程序无需便利整个压缩列表就可以确定表尾节点的位置 占用4个字节
最后
按照上面的过程,4个月的时间刚刚好。当然Java的体系是很庞大的,还有很多更高级的技能需要掌握,但不要着急,这些完全可以放到以后工作中边用别学。
学习编程就是一个由混沌到有序的过程,所以你在学习过程中,如果一时碰到理解不了的知识点,大可不必沮丧,更不要气馁,这都是正常的不能再正常的事情了,不过是“人同此心,心同此理”的暂时而已。
“道路是曲折的,前途是光明的!”
混沌到有序的过程,所以你在学习过程中,如果一时碰到理解不了的知识点,大可不必沮丧,更不要气馁,这都是正常的不能再正常的事情了,不过是“人同此心,心同此理”的暂时而已。
“道路是曲折的,前途是光明的!”
[外链图片转存中…(img-4ls4PvGS-1718910018322)]
[外链图片转存中…(img-emqdFgZl-1718910018323)]