任何一个框架都是由底层数据结构和相应的操作接口编写的,所以要想深入洞悉一个框架,快速找到解决问题的根本,就需要对其底层原理进行剖析。
Redis 之所以快,一方面是因为它是基于内存的,所有的操作都在内存上完成,另一方面要归功于它的数据结构,键值对是按一定的数据结构来组织的,操作键值对最终就是对数据结构进行增删改查操作,所以高效的数据结构是 Redis 快速处理数据的基础
Redis是一个key-value
数据库,使用哈希表
来保存所有键值对;其中key的类型只能是String
,而value的类型可以有以下类型:
- String
- List
- Hash
- Set
- Sorted Set(有序集合)
那么这五种数据类型对应的底层数据结构如下图所示:
- 为什么List、Hash、Sorted Set、Set底层要有两种数据结构实现?
之所以采用不同的数据结构,其实是在性能和内存使用效率之间进行的平衡 - 数据结构
- 简单动态字符串(Simple Dynamic String,SDS)
buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个“\0”,这就会额外占用 1 个字节的开销。
len:占 4 个字节,表示 buf 的已用长度。
alloc:也占个 4 字节,表示 buf 的实际分配长度,一般大于 len。
len和alloc是额外开销
- 整数数组
当一个集合只包含整数元素,并且元素不多时,Redis就会使用整数作为集合键的底层实现;保证集合有序不重复
- 压缩列表
表头有三个字段zlbytes、zltail 和 zllen
,分别表示列表长度、列表尾的偏移量和列表中的 entry 个数
;压缩列表在表尾还有一个 zlend,表示列表结束
查找定位第一个元素和最后一个元素
,可以通过表头三个字段的长度直接定位,复杂度是O(1)
。而查找其他元素
时,就没有这么高效了,只能逐个查找,此时的复杂度就是 O(N)
了。
整数数组和压缩列表在查找时间复杂度方面并没有很大的优势,为什么Redis还会把他们作为底层结构?
- 内存利用率:数组和压缩列表都是非常紧凑的数据结构,比链表占用的内存更少。Redis是内存数据库,大量数据存到内存中,需要做尽可能多的优化,提高内存的利用率;
- 数组对CPU高速缓存[为了解决CPU计算和内存读取速度不匹配]支持更友好[因为数据以块为单位调入CPU缓存,为什么以块为单位,根据局部性原理,当前使用了某个数据,则其附近的数据也很可能在接下来被访问;数组在物理内存中是连续的,链表不连续,需要多次调入块],所以在集合数据元素较少情况下,Redis默认采用内存紧凑排列的方式存储,同时利用CPU高速缓存不会降低访问速度;当数据元素超过设定阈值后,避免查询时间复杂度太高,转为哈希表和跳表;
- 跳表
跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位
当数据量很大时,跳表的查找复杂度就是O(logN)
;