任何一个框架都是由底层数据结构和相应的操作接口编写的,所以要想深入洞悉一个框架,快速找到解决问题的根本,就需要对其底层原理进行剖析。

Redis 之所以快,一方面是因为它是基于内存的,所有的操作都在内存上完成,另一方面要归功于它的数据结构,键值对是按一定的数据结构来组织的,操作键值对最终就是对数据结构进行增删改查操作,所以高效的数据结构是 Redis 快速处理数据的基础

Redis是一个key-value数据库,使用哈希表来保存所有键值对;其中key的类型只能是String,而value的类型可以有以下类型:

  • String
  • List
  • Hash
  • Set
  • Sorted Set(有序集合)

那么这五种数据类型对应的底层数据结构如下图所示:

redis底层的数据结构 redis数据结构底层实现原理_redis

  1. 为什么List、Hash、Sorted Set、Set底层要有两种数据结构实现?
    之所以采用不同的数据结构,其实是在性能和内存使用效率之间进行的平衡
  2. 数据结构
  • 简单动态字符串(Simple Dynamic String,SDS)

buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个“\0”,这就会额外占用 1 个字节的开销。
len:占 4 个字节,表示 buf 的已用长度。
alloc:也占个 4 字节,表示 buf 的实际分配长度,一般大于 len。
len和alloc是额外开销

  • 整数数组

当一个集合只包含整数元素,并且元素不多时,Redis就会使用整数作为集合键的底层实现;保证集合有序不重复

  • 压缩列表
    表头有三个字段zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束
  • redis底层的数据结构 redis数据结构底层实现原理_redis底层的数据结构_02


查找定位第一个元素和最后一个元素,可以通过表头三个字段的长度直接定位,复杂度是O(1)。而查找其他元素时,就没有这么高效了,只能逐个查找,此时的复杂度就是 O(N) 了。
整数数组和压缩列表在查找时间复杂度方面并没有很大的优势,为什么Redis还会把他们作为底层结构?

  • 内存利用率:数组和压缩列表都是非常紧凑的数据结构,比链表占用的内存更少。Redis是内存数据库,大量数据存到内存中,需要做尽可能多的优化,提高内存的利用率;
  • 数组对CPU高速缓存[为了解决CPU计算和内存读取速度不匹配]支持更友好[因为数据以块为单位调入CPU缓存,为什么以块为单位,根据局部性原理,当前使用了某个数据,则其附近的数据也很可能在接下来被访问;数组在物理内存中是连续的,链表不连续,需要多次调入块],所以在集合数据元素较少情况下,Redis默认采用内存紧凑排列的方式存储,同时利用CPU高速缓存不会降低访问速度;当数据元素超过设定阈值后,避免查询时间复杂度太高,转为哈希表和跳表;
  • 跳表
    跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位
    当数据量很大时,跳表的查找复杂度就是 O(logN)