什么是零拷贝?
DMA技术:使用一个DMA控制器将数据从硬盘传输到内存,除了一开始调用DMA控制器发起传输,数据搬运全称不需要CPU参与。
发送一段数据到网上如下所示
4次状态切换4次拷贝。
为了加快速度就要减少上下文切换,减少拷贝次数。
mmap+write()
共发生3次拷贝,4次上下文切换。
sendfile
发生两次上下文切换和两次拷贝。
IO多路复用——select/ poll / epoll
基本socket模型
socket是进程通信的一种方式,服务端的一个socket会被绑定一个ip+端口。
然后等待客户端的connect()。TCP连接的具体去看网络的内容。
如何服务更多的用户?
服务器理论上能服务的客户端的TCP数量=ip*端口数。ip有2^32,端口有2^16,所以最多有2^48个客户端。
这也只是理论。
这受限于两个条件:
系统内存:每个TCP连接都是要占用一定内存的。
文件描述符:socket在系统里面也是一个文件,对应一个文件描述符。在linux中,能同时打开的文件描述符数量是有限的。
多进程模型
基于原始的阻塞网络IO,如果要支持多个客户端,可以使用多进程模型,为每个客户端分配一个进程。
服务器的主进程负责监听socket,与客户端连接完成之后调用accept()得到一个“已连接socket”,然后就可以直接fork一个子进程去使用已连接socket和客户端通信。
弊端:客户端数量越多,进程越多,上下文切换也越久。
多线程模型
为了解决多进程上下文切换重的问题,可以使用多线程模型。为了防止创建和销毁线程的开销,使用线程池技术,有一个客户端连接就取一个线程出来去拿一个已连接socket.
弊端:如果要应对1万个客户端就要有1万个线程,操作系统还是抗不住。
I/O 多路复用
一个请求分配一个进程/线程不可行,那就一个进程/线程负责多个socket.
Redis里面也是用到了这个。
为了实现多路复用,需要用到内核提供给用户态的多路复用系统调用select/poll/epoll ,进程可以通过一个系统调用函数从内核中获取多个事件。
select/poll/epoll 是如何获取网络事件的呢?在获取事件时,先把所有连接(文件描述符)传给内核,再由内核返回产生了事件的连接,然后在用户态中再处理这些连接对应的请求即可。
select/poll/epoll
select是直接将所有已连接socket放到一个文件描述符集合,然后调用select将其拷贝到内核,让内核检查是否有事件发生,有的话标记socket为可读或可写。然后将其拷贝到用户态,用户态遍历对可读可写的socket进行操作。在linux里面最多只能监听1024个文件描述符。
poll采用链表的形式组织socket,不受1024的数量限制,受文件描述符的数量限制。本质上和select没有区别。操作方法雷同。
epoll
边缘触发和水平触发
高性能网络模式——Reactor
redis,nginx,netty中都有采用这个模式。
redis高性能的原因有IO多路复用。Reactor模式是对IO多路复用的一个封装。
Reactor 模式主要由 Reactor 和处理资源池这两个核心部分组成,它俩负责的事情如下:
- Reactor 负责监听和分发事件,事件类型包含连接事件、读写事件;
- 处理资源池负责处理事件,如 read -> 业务逻辑 -> send;
单Reactor 单进程/线程
Redis6.0之前里面用的就是这个模式,redis性能的瓶颈不在CPU上,所以6.0之前引入了多线程去处理网络IO。 6.0之前是一个线程还要进行IO数据的拷贝,6.0开始主线程就只负责执行命令了。引入了多线程去处理IO数据。