完全基于内存
Redis的数据存在内存中,相比较于非内存数据库。数据访问少了访问磁盘这一大开销。所以说redis快,这点虽然最明显,好像没啥说头,但这个是最直接的原因。相必大家也有自己成熟的见解,咱们不在此多说,主要分析下另外两个点。
高效的键值结构
Redis中使用一个全局哈希表来存储所有的键值对。全局表键值对中,key跟value都是指针。分别指向当时我们设置的key和value的数据结构。我们知道redis中的key是string类型的。而value有常用的五种,整体结构如下所示:
这样无论有多少个键值对存在redis里面,只需一次计算就能找到相应键值对。查询时间复杂度为O(1)
除此之外,以上五种常用的value数据类型的底层数据结构也是非常耐人寻味的。小伙伴们可能有这样的感受,当面试官问你常用数据类型时大家都能不假思索,脱口而出。而当问这五种类型的底层数据结构时,却无言以对。以下为五种数据类型对应的数据结构,各位看官请看:
可以看到除了String类型只对应一种数据结构外,其余四种数据类型每种对应两种数据结构。
其中哈希表不必多说,查询时间复杂度为O(1)。整数数组查询复杂度也为O(1)但是其余像是删除或者增加操作,其复杂度跟链表基本都是O(n)。我们重点说下大家不常见的压缩列表跟跳表。
压缩列表:
压缩列表实际类似于一个数组,其表头有三个字段zlbytes,zltail、zllen,分别表示列表长度、列表尾偏移量、列表中entry数目。列表尾部还有一个zlend表示列表结束。如下示:
这样的结构下,第一跟最后一个元素可根据前三元素直接查找,时间复杂度为O(1),其余元素查找复杂度为O(n)。
跳表:
顾名思义,跳表就是跳着查询。相比于有序列表,增加了多级索引。如下图示:
原理也是显而易见,每隔几个元素抽出来作为一层索引的元素,在查找时先拿当前级别索引元素跟要查的元素进行比较。这样便能跳过很多没有必要进行比较的元素。索引级别越高,跳过的元素越多,查找次数越少。但是这样存储索引的空间也越多,可谓用空间换时间。
当数据量很大时,跳表的查询时间复杂度为O(logN)。
高效的网络模型
我们可以从官网还有他人文章上看到redis单机qps支持10万+,那什么样的网络模型才能达到这么好的性能呢?接下来我们分析下redis支持并发度这么好的原因。
首先老生常谈下,redis是单线程吗?其实严格意义上这句话是错误的。Redis支持很多异步操作,比如持久化、主从同步等等。这些操作都是在主线程异步开辟的子线程中进行的,目的就是为了避免阻塞主线程。而主线程用来干嘛呢?主线程用来干最重要的事情,处理客户端网络连接以及响应客户端的各种命令响应。
Redis网络处理这块说到底就是对epoll的很好的应用。无论是客户端的已连接套接字还是redis服务端的监听套接字均是由epoll来进行管理。当epoll监听到连接上有相应事件时,如果是连接请求事件,则创建新的客户端连接并将此连接放置epoll中进行管理。如果是其他客户端上的请求连接,则读取并处理命令,并把响应结果放入写任务队列。以上这些操作都是在一个死循环中周而复始地执行。每次循环中都会对写任务队列进行处理。如果对某一客户端的写缓冲区满了,无法一次性发送过去,就会在epoll上新注册一个写事件处理函数。等之后循环中epoll发现该客户端连接可写时,再把之前剩下的给发送过去。整体的结构图如下示:
以上便是我粗略的见解,之后会详细讲解下I/O多路复用跟epoll这块儿,请大家拭目以待。