02 快速的Redis有哪些慢操作

Redis 在查找数据速率方面特别突出,通常是微秒级别的查找操作。其中一方面的原因就得益于 Redis 属于的是内存数据库,内存的访问速率本身就很快;另一方面的原因在于其 Redis 中数据结构,对键值对的操作最终可以看作是对数据结构的增删改查操作。

一、Redis 中数据结构

Redis 中 Value 的数据类型有 String, List, Hash, Sort Set, Set 五种,但是它们仅仅是数据类型,直接影响到对这些数据操作的性能是这些类型的底层数据结构,以下除了 String 类型采用的底层数据结构只有一种实现以外,其他的集合类型的底层的数据结构的实现都有两个。

  1. 键和值之间采用什么结构组织的?
    采用的数据结构是哈希表,哈希表实则是数组,每一个存储位置都称之为哈希桶,其中存储的键值对 Entry 对象是根据其 key 经过哈希计算得出的,而 Entry 对象中存储了 (Key*) 和 (Value*) 键和值的指针,由于 Entry 数据的存取都是采用哈希计算获得哈希桶位置来进行操作的,其时间复杂度为O(1),无关其哈希桶的个数。Redis 中存储所有的键值对的哈希表称之为全局哈希表。
  2. 为什么有时候 Redis 操作很慢?
    哈希冲突&rehash操作。由于 Redis 中的键值对存储在哈希表中,通过哈希计算获取哈希桶的位置,随之哈希表中存储的 Entry 个数变多,发生哈希冲突的可能性变高。在 Redis 中一旦发生了哈希冲突,其解决冲突的方法就是采用拉链法,将发生冲突的 Entry 构成链表结构(哈希链)。一旦链表过长,那么就会导致当查询该链表上的数据的时间复杂度是 O(N),因此一旦发生的冲突过多,那么 Redis 就会采用 rehash 操作,其中 Redis 中含有两个全局哈希表,首先将另一个哈希表的大小变为正在使用的哈希表的两倍,然后为了避免一次性将大量的 entry 重新进行哈希计算得到在新哈希表的存储位置再进行拷贝造成 Redis 主线程被阻塞,使得 Redis 的响应时间变长,无法快速的访问数据,因此 Redis 中采用的是渐进式 rehash 操作,每当在 Redis 接收用户的查询请求(rehash 过程中不能执行写操作)处理后,从哈希表的第一个索引开始将该哈希桶位置上的所有 entries rehash 到新的哈希表上。直到将旧的哈希表中的所有哈希桶上的数据都 rehash 拷贝到新的哈希表上后,就将新的哈希表作为主哈希表,旧的哈希表进行内存释放,变成空的哈希表,供下一次进行 rehash 使用。
数据类型 数据结构
String 简单动态数组
List 压缩列表 / 双向链表
Hash 压缩列表 / 哈希表
Sort Set 压缩列表 / 跳表
Set 哈希表 / 整型数组

二、集合数据的操作效率

和 String 类型不一样的是,集合类型首先经过 hash 计算找到哈希表上指定的哈希桶,然后在对集合数据结构进行增删改操作。因此集合的数据的操作的效率由两部分原因影响:① 集合的底层由什么数据结构实现的;② 操作本身的特性。

(1)数据结构查询复杂度

  1. 双向链表和整型数组都是顺序的链接的,因此其查询数据的只能从头到尾的进行查询,其时间复杂度为 O(N)
  2. 哈希表是通过 key 的哈希计算获取其哈希桶的位置然后根据内部的指针获取数据,其时间复杂度为 O(1)
  3. 压缩列表其实本质就是数组结构,但是不同的是其压缩列表的表头含有三个字段:zlbytes(列表长度), zltail(表头与表尾的偏移量), zllen(列表的 entry 元素个数);表尾中含有字段 zlend(表示列表结束)。对于压缩列表而言,由于表头有 zltail 表头有表尾的偏移量的值,因此这种数据结构在获取第一个和最后一个元素的时间复杂度为 O(1),但是获取其他的 entry 元素的时间复杂度为 O(N),因此平均计算下来其复杂的时间复杂度为 O(N)
  4. 跳表实则就是在链表上添加了多级索引,因此在其查询时间复杂度为 O(logN)。
数据结构 查询时间复杂度
双向链表 O(N)
整型数组 O(N)
压缩列表 O(N)
跳表 O(logN)
哈希表 O(1)

集合操作的复杂度

  1. 单元素操作:由集合的底层数据结构的特性来决定
  2. 多元素操作:由集合的底层的数据结构的特性来决定以及其操作的元素的个数。
  3. 范围操作:一般来说进行范围的遍历操作的时间复杂度为 O(N),因此这里是不建议使用的,可以换成其他的命令,例如 Redis 中提供了 SCAN 操作,渐进式遍历,每次只返回有限个元素,避免一次性遍历大量的元素导致 Redis 阻塞。
  4. 统计操作:统计操作就是返回集合中的元素个数,由于集合类型采用的是压缩列表,整型数组这种数据结构,因此在内部已经保存了元素的个数,进行统计操作的时候直接返回数据即可,因此时间复杂度为O(1)。
  5. 某些数据结构的特殊操作:例如压缩列表中的表头存储了表头表尾的偏移量,因此在执行 LPOP, RPOP, LPUSH, RPUSH 操作的时候可以快速的定位到第一个和最后一个元素的位置,因此其类似这种的操作的时间复杂度为 O(1)。

(3)因地制宜的使用 List 数据类型

由于其 List 数据类型采用的底层数据结构是双向链表或者是压缩列表,而这两种数据结构的查询的时间复杂度均为 O(N),因此要谨慎,因地制宜的使用该类型,例如 List 类型的 POP / PUSH 操作的效率比较高,可以用于其 FIFO 先进先出队列的场景,而不是一个随机的读写操作场景。​若有收获,就点个赞吧

你可能感兴趣的:(Redis,核心技术与实战,redis)