文章目录
1. reactor
1.1 io多路复用
IO多路复用是一种可以同时监听多个网络连接IO事件的技术,使用一个线程就可以管理多个socket。它的主要方法有select,poll,epoll。
-
select:select函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有exception),或者超时。
-
poll:poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备则挂起在设备等待队列中。
-
epoll:相比于select和poll,epoll更加灵活而且没有最大并发连接的限制。epoll使用一个文件描述符管理多个描述符,将用户关心的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
1.2 非阻塞io
非阻塞IO是指在对IO操作进行请求时,如果不能立即完成该操作,系统不会阻塞该进程,而只是立即返回一个错误信息,避免了阻塞IO需要等待的问题。
1.3 one eventloop per thread
每个线程一个事件循环(EventLoop),该设计可以避免线程之间的竞争,同时又可以充分利用多核CPU的优势,提高系统的吞吐量。
1.4 reactor为什么搭配非阻塞io
使用Reactor模型配合非阻塞IO可以更好地处理高并发场景。主要有以下几点理由:
-
多线程环境:可以将一个listenfd放到多个epoll去处理,利用多核CPU,提高并发处理能力。
-
边缘触发:在读事件触发时,read在一次事件循环中把read buffer读空,减少了多次触发IO事件,提高了效率。
-
解决select bug:select在某种情况下存在问题。当某个socket接收缓冲区有新数据分节到达,然后select报告这个socket描述符可读,但随后,协议栈检查到这个新分节检验和错误,然后丢弃这个分节,这时候调用read则无数据可读,如果socket没有被设置nonblocking,此read将阻塞当前线程。
1.5 是不是io多路复用一定要搭配非阻塞io
IO多路复用并不一定要搭配非阻塞IO,但是非阻塞IO可以更好地提高系统的响应能力和处理能力。一些系统或者库在特定的场景下可能选择不使用非阻塞IO。
-
MySQL:MySQL使用select接受链接,每条链接一个线程处理,不需要使用非阻塞IO。
-
libevent:libevent库可以通过加一个系统调用来获取读缓冲区字节数,不需要使用非阻塞IO,但是这种方式效率相对较低。
int n = EVBUFFER_MAX_READ_DEFAULT; if (ioctl(fd, FIONREAD, &n) < 0) return -1; return n;
2. redis
2.1 环境
Redis是一个开源的,基于内存的Key-Value数据库,同时也支持持久化。Redis不仅支持基本的Key-Value类型,还提供了丰富的数据类型,如Lists, Sets, Sorted Sets, HyperLogLogs等。
- Key-Value数据库:Redis是Key-Value存储系统,也即一种内存数据库系统,同时也被认为是一种NoSQL系统。例如存取字符串:
# 存储key-value
127.0.0.1:6379> set mykey somevalue
OK
# 获取value
127.0.0.1:6379> get mykey
"somevalue"
- 单线程命令处理:Redis采用了一种类似于Node.js的事件驱动模型,这意味着它会处理尽可能多的命令,然后返回结果,而不是为每一个连接启动一个线程。
2.2 Redis为什么要使用单Reactor
Redis使用单Reactor主要因为以下两个原因:
-
单线程业务逻辑:Redis本身就是单线程的,在一个时间点只执行一个命令,因此在设计上使用单reactor更符合其内部的运行机制,更为简洁高效。
-
操作具体命令时间复杂度比较低:Redis的大多数命令的时间复杂度都是O(1),即使是最复杂的操作,时间复杂度也是O(logN),这样的设计保证了即使在高并发的环境下,Redis也能保持高性能和快速的响应。
2.3 Redis怎么处理Reactor
Redis使用了一种称为“事件循环”的机制来处理客户端的请求,具体流程如下:
- client -> reactor:客户端向Redis服务器发送命令请求。
- reactor -> acceptor:Redis服务器接收到请求后,将请求转交给适当的处理程序。
- acceptor -> reactor:处理程序处理完请求后,将结果返回给Redis服务器。
- reactor -> read -> decode compute encode:Redis服务器读取请求,对请求解码,执行命令,然后将结果编码。
- reactor -> write:最后,Redis服务器将结果写回给客户端。
2.4 Redis针对Reactor做了哪些优化
Redis针对Reactor模型进行了一系列的优化,以提高处理效率:
-
read, decode:记录日志:在读取和解码请求时,Redis会记录详细的日志,以便在出现问题时进行调试。
-
encode, write:获取排行榜记录:在执行完命令并将结果编码后,Redis不会立即将结果返回给客户端,而是将结果暂存起来,等待其他命令一起处理,这样可以减少系统的IO操作,提高处理效率。例如,使用Redis实现一个排行榜功能:
# 添加排行数据 127.0.0.1:6379> zadd ranking 90 user1 (integer) 1 127.0.0.1:6379> zadd ranking 80 user2 (integer) 1 127.0.0.1:6379> zadd ranking 70 user3 (integer) 1 # 获取排行榜 127.0.0.1:6379> zrevrange ranking 0 -1 withscores 1) "user1" 2) "90" 3) "user2" 4) "80" 5) "user3" 6) "70"