Redis 采用的是基于内存的单进程单线程模型的KV数据库,由 C 语言编写。官方提供的数据是可以达到 100000+ 的 qps。这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差。
Redis 快的主要原因是:
- 完全基于内存
- 数据结构简单,对数据操作也简单
- 使用多路 I/O 复用模型
为什么 Redis 中要使用 I/O 多路复用这种技术呢?
Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。
Redis 对 IO 多路复用的封装
以 epoll 为例,了解 Redis 内部是如何封装 IO 多路复用的
为了将所有 IO 复用统一,Redis 为所有 IO 复用统一了类型名 aeApiState,对于 epoll 而言,类型成员就是调用 epoll_wait所需要的参数
接下来就是一些对epoll接口的封装了,包括创建 epoll(epoll_create),注册事件(epoll_ctl),删除事件(epoll_ctl),阻塞监听(epoll_wait)等
创建 epoll 就是简单的为 aeApiState 申请内存空间,然后将返回的指针保存在事件驱动循环中
注册事件和删除事件就是对 epoll_ctl 的封装,根据操作不同选择不同的参数
阻塞监听是对 epoll_wait 的封装,在返回后将激活的事件保存在事件驱动中
事件驱动循环流程
IO 复用的封装实现完成,那么 Redis 是何时调用 IO 复用函数的呢,这就需要从 server.c/main 函数入手,可以猜测到当 main 函数初始化工作完成后,就需要进行事件驱动循环,而在循环中,会调用 IO 复用函数进行监听
在初始化完成后,main 函数调用了 aeMain 函数,传入的参数就是服务器的事件驱动
Redis 对于时间事件是采用链表的形式记录的,这导致每次寻找最早超时的那个事件都需要遍历整个链表,容易造成性能瓶颈。而 libevent 是采用最小堆记录时间事件,寻找最早超时事件只需要 O(1) 的复杂度
如何选择合适的 IO 多路复用函数
Redis 的策略是选择当前平台存在的,效率最高的 IO 复用函数
参考:
Redis 和 I/O 多路复用
Redis 中的事件循环
redis 浅析IO多路复用与事件处理
Redis IO多路复用技术以及epoll实现原理
Redis源码剖析(二)io多路复用函数及事件驱动流程
Redis 为什么使用单进程单线程方式也这么快