说到redis我们都知道,redis查询很快,常常被用来做缓存。在查询key值时,能以微秒级别的速度查询到数据。
redis查询为什么这么快?

  1. 内存数据库,我们知道内存的访问速度都是很快的,而redis就是内存数据库,所有的操作都在内存中完成的。
  2. redis的数据结构有一定的优势,redis操作键值对就是对数据结构进行增删改查。

redis的数据类型有哪些,我们都知道有5种:String、List、Hash、Set、Sorted Set。但是我们仔细研究一下他们底层的数据结构就会发现,他们一共有6种:简单动态字符串、双向链表、压缩列表、哈希表、跳表和整数数组。

他们的时间复杂度分别是:

名称

时间复杂度

哈希表

O(1)

跳表

O(logN)

双向链表

O(N)

压缩列表

O(N)

整数数组

O(N)

redis database Redis数据库索引 redis索引怎么实现的_数据


我们可以看出String的底层实现也是一种数据结构,而List、Hash、Set、Sorted Set都有两种底层实现,他们的特点都是一个键对应了一个集合的数据。

在redis中为了实现键和值的快速访问,使用一个哈希表来保存所有的键值对。

redis database Redis数据库索引 redis索引怎么实现的_数据结构_02


在上图中可以看到哈希桶中存放的并不是值本身,而是指针来指向具体值,这个哈希表保存了所有的键值对,也称为全局哈希表。这个哈希表的优势是我们查找键值对的时间复杂度是O(1)。但是也存在一个风险就是当数据也来越多时,由于哈希冲突和rehash操作会带来阻塞问题。

哈希冲突是指,两个不同的key通过哈希运算,正好同时落在了衣一个哈希桶里。redis的解决方法就是使用链式哈希,这也是比较容易理解的,在同一个哈希桶的多个元素使用一个链表来保存,他们之间依次使用指针连接。这也比较常见的思路,在很多开源代码中都使用了这种思想,比如JDK1.7中HashMap等的实现都使用链式哈希来解决哈希冲突。

redis之所以对哈希表做rehash操作,就是因为但一个哈希桶中的链表越来越长时,导致该链表上元素查找耗时长,效率降低。rehash操作就可以将逐渐增多的entry元素在更多的桶之间分散保存,减少单个桶的元素数量,减少单个桶的冲突。为了rehash的操作更高效,redis默认使用两个全局哈希表:一开始插入数据时默认使用哈希表1,此时哈希表2没有被分配空间,随着数据增多,redis执行rehash:

  1. 给哈希表2分配更大的空间,可以是哈希表1的2倍;
  2. 把哈希表1的数据重新映射并拷贝到哈希表2
  3. 释放哈希表1的空间

此时,我们就可以将哈希表1切换到哈希表2;用增大的哈希表2保存更多的数据,原来的哈希表1留作下一次rehash备用。
但是这又会造成另一个问题,就是大面积的拷贝数据,必然会造成Redis的线程阻塞,无法处理其他请求,造成访问数据变慢。
为了避免这个问题,Redis使用渐进式rehash。

简单而言,就是在拷贝数据时,Redis依然正常处理客户端的请求,每处理一个请求时,从哈希表1的第一个索引位置开始,将索引上所有的entry拷贝到哈希表2;等待处理下一个请求时,顺带拷贝哈希表1中的下一个索引位置的entry。可以把一次性大量的拷贝开销分配到多次请求过程找那个,避免长时间耗时操作。

redis database Redis数据库索引 redis索引怎么实现的_数据结构_03