目录
1. Redis为什么这么快?
1)纯内存操作
2)单线程,无锁竞争损耗(但是redis即将迎来多线程版本?!!)
3)C语言实现,更接近底层操作
4)多路I/O复用模型,非阻塞IO
5)数据结构简单,底层又做了优化
6)源码精湛、简短
1.1 阻塞I/O
通常的IO操作都是阻塞I/O,也就是调用read的时候,如果数据没有收到,那么线程或者进程会挂起,直到收到数据。
阻塞的意思,就是一直等着。应用的函数长时间处于等待结果的状态,我们就称为阻塞I/O。
1.2 非阻塞I/O
调用read时,如果有数据收到,就返回数据,如果没有数据收到,就立刻返回一个错误,如EWOULDBLOCK。
但是应用设计必须持续过来找服务提供者,知道能够得到数据。
1.3 I/O多路复用
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符(FileDescription,简称FD),如果有一个文件描述符(FileDescription)就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。
就是派一个代表,同时监听多个文件描述符是否有数据到来。等着等着,如有有数据,就告诉某某你的数据来啦!赶紧来处理吧。
2 redis中的多路复用模型?
同样基于内存的别的数据库Memcached,单进程多线程的;
但是redis并不比它差。
redis主要是完全依赖于内存,数据结构简单。而且使用多路复用I/O。
跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。
Redis,为了将所有的IO复用统一,Redis 为所有 IO 复用统一了类型名 aeApiState。
select, poll, epoll 都是I/O多路复用的具体的实现。epoll性能比其他几者要好。redis中的I/O多路复用的所有功能通过包装常见的select、epoll、evport和kqueue这些I/O多路复用函数库来实现的。
划重点,那么redis是包装常见的I/O多路服用库来实现的。
以epoll举例:
对epoll接口的封装了,包括创建 epoll(epoll_create),注册事件(epoll_ctl),删除事件(epoll_ctl),阻塞监听(epoll_wait)等
创建 epoll 就是简单的为 aeApiState 申请内存空间,然后将返回的指针保存在事件驱动循环中
注册事件和删除事件就是对 epoll_ctl 的封装,根据操作不同选择不同的参数
阻塞监听是对 epoll_wait 的封装,在返回后将激活的事件保存在事件驱动中
2.1 什么是事件驱动
IO 复用的封装实现完成,那么 Redis 是何时调用 IO 复用函数的呢,这就需要从 server.c/main 函数入手,可以猜测到当 main 函数初始化工作完成后,就需要进行事件驱动循环,而在循环中,会调用 IO 复用函数进行监听
在初始化完成后,main 函数调用了 aeMain 函数,传入的参数就是服务器的事件驱动
Redis 对于时间事件是采用链表的形式记录的,这导致每次寻找最早超时的那个事件都需要遍历整个链表,容易造成性能瓶颈。而 libevent 是采用最小堆记录时间事件,寻找最早超时事件只需要 O(1) 的复杂度
Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要是用于事件驱动。
2.2 为什么redis不使用Libevent?
Redis 为何不使用 libevent 和 libev 事件库,而是要实现自己的事件库?
1. libevent 太大了,比 Redis 的代码库还大 3 倍左右。
2.Redis 作为一个 NOSQL 数据库,不想引入太大的外部依赖。
3.第三方库如果出现一些意想不到的 bug,会影响到 Redis。
4.方便 Redis 做定制化开发。
5. libev 虽然小,但是它没有提供 HTTP 之类的高级功能。
2.3 总结
I/O 多路复用模型是利用select、poll、epoll可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。
redis内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性。
epoll读音有点类似apple
参考:https://www.jianshu.com/p/603de8774734
https://www.xttblog.com/?p=4594
https://blog.youkuaiyun.com/diweikang/article/details/90346020