问题:redis为什么这么快?
1、redis是内存数据库,所有的操作都在内存上完成;(内存的访问速度本身就很快)
2、归功于redis的数据结构;
Redis键值对中的数据类型:String、List、Hash、Set(集合)、Sorted Set(有序集合);
底层数据结构(6种):简单动态字符串、双向链表、压缩列表、哈希表、跳表、整数数组;
注意:数据类型是指数据的保存形式,而数据结构是指它们的底层实现,注意区分!
与数据类型的关系图如下:
问题:这些数据结构都是值的底层实现,那么键和值本身之间用什么结构组织?
Redis使用了一个全局哈希表保存所有的键值对;
一个哈希表,其实就是一个数组,数组的每个元素称为一个哈希桶;每个哈希桶中保存了键值对数据;
问题:如果值是集合类型,作为数组元素的哈希桶怎么保存?
哈希桶中的元素保存的并不是值本身,而是指向具体值的指针。如下图所示,即使值是一个集合,也可以通过*value指针被查找到:
哈希表的好处:
用O(1)的时间复杂度来快速查找到键值对——我们只需要计算键的哈希值,就可以知道它对应的哈希桶的位置,然后就可以访问相应的entry元素;
潜在风险:往redis中写入大量数据后,可能会发现操作变慢了?
原因:哈希表的冲突问题和rehash可能带来的操作阻塞;
1、哈希冲突:两个key的哈希值和哈希桶计算对应关系时,正好落在了同一个桶中;
- redis解决哈希冲突的方式: 链式哈希(指同一个哈希桶中的多个元素用一个链表来保存,他们之间依次用指针连接)
如下图所示,这也就形成了一个链表,也叫做哈希冲突链:
- 由于哈希冲突链上的元素只能通过指针逐一查找再操作,如果哈希表数据越来越多,哈希冲突也会越来越多,这就导致哈希冲突链过长,进而导致这个链上的元素查找耗时长,效率降低;所以redis会对哈希表做rehash操作。
2、rehash:即增加现有哈希桶的数量,让逐渐增多的entry元素能在更多的桶之间分散保存,减少单个桶中的元素数量,从而减少单个桶中的冲突;
具体怎么做:
redis默认使用了两个全局哈希表:哈希表1和哈希表2;
当你刚插入数据时,默认使用哈希表1,此时哈希表2并没有被分配空间,随着数据逐步增多,redis开始执行rehash
- 给哈希表2分配更大的空间;
- 把哈希表1中的数据重新映射并拷贝到哈希表2中;
- 释放哈希表1的空间;
原来的哈希表1留作下一次rehash扩容备用。
风险:第二步中,如果一次性拷贝大量数据到哈希表2中,会造成redis线程阻塞,无法服务其他请求。
解决:redis采用渐进式rehash;
渐进式rehash:在第二步拷贝数据时,redis仍然正常处理客户端请求,每处理一个请求时,从哈希表1的第一个索引位置开始,顺带将这个索引位置上的所有entries拷贝到哈希表2中;等处理下一个请求时,再顺带拷贝哈希表1中的下一个索引位置的entries。
如下图所示:
渐进式rehash执行时,redis本身还会有一个定时任务在执行rehash,如果没有键值对操作时,这个定时任务会周期性的搬移一些数据到新的哈希表中,这样可以缩短整个rehash的过程;
这样就巧妙的把一次性大量拷贝的开销,分摊到了多次处理请求的过程中,避免了耗时操作,保证了数据的快速访问;
注:文章是总结了极客时间上《redis核心技术与实战》数据结构的一文;仅用于学习,非商业用途!