博客
-
https://mp.weixin.qq.com/s?__biz=MzUxODAzNDg4NQ==&mid=2247489558&idx=1&sn=7a96604032d28b8843ca89cb8c129154&scene=21#wechat_redirect
-
https://mp.weixin.qq.com/s/px6-YnPEUCEqYIp_YHhDzg
-
redis:epoll
-
nginx:epoll
-
netty:可选的
程序要调用IO,需要从用户态切换到内核态
linux中追踪socket系统调用
strace
命令启动程序可以追踪进程执行时的系统调用和所接收的信号
启动程序后,系统调用,创建了一个socket服务
linux里,每个进程都在一个目录中:/proc/[进程号]
进到文件夹里,比如进到2878进程中:cd /proc/2878
task目录里装的就是2878进程包含的所有线程
fd目录里是文件描述符
任何一个程序都有IO,且最少有三个基本的IO
- 标准输入0
- 标准输出1
- 错误输出2
3是BootStrapClassloader加载的库
4和5是socket服务,一个是ipv4一个是ipv6
netstat -natp
命令可以看到进程的连接状态和用的协议
可以看到2878进程是用的tcp协议,处于LISTEN(监听)8090端口状态
nc
可以测试连接
用nc连接8090端口后
netstat -natp
查看连接状态
2878进程多了一个ESTABLISHED状态
fd目录里的文件描述符多了一个socket
查看系统调用,增加了一个客户端
socket(BIO)
- 程序先获得一个文件描述符,代表一个socket连接
- socket通过bind绑定端口号
- 通过listen方法监听这个socket
- 通过accept方法获取请求,如果accept成功,要进入阻塞状态
一开始是监听套接字5,之后是连接套接字6,连接方法是通过创建一个新的线程,然后阻塞io,不断等待客户端发送消息。
BIO的问题:
- 一个客户端对应一个线程,线程上下文切换开销大,且线程消耗内存资源
- 根本问题是会阻塞
NIO
https://tech.meituan.com/2016/11/04/nio.html
不需要多线程了,一个线程里循环全部连接。
比起BIO,如果一个连接不能读写,可以马上切换其他就绪的连接(channel)继续进行读写,不会一直阻塞住。
NIO的问题:C10K
假设有1W个客户端,每循环内会有O(n)的系统调用,如果只有一两个有数据,那剩下的就白调了。
多路复用
select
缺点:
- 虽然只用一次系统调用(本来要n次系统调用,现在多路复用了)了,但是系统要遍历全部的连接,看哪些连接有数据,遍历的时间复杂度还是O(n)
- 用来标记是哪个socket有事件的bitmap是有长度上限的
&rset是bitmap,标记哪个客户端有事件发生
while中的select是阻塞的,直到有事件发生,事件发生的时候,更改reset的位,这时select会有个返回值,程序向下执行,遍历所有文件描述符来找到是哪个连接发生了事件。
memset():把原来的1置为0
read()把数据读出来
puts()处理数据
poll
对比select的改进:
- 不是置位bitmap,而是把文件描述符的revents置位,这样就可以重用fds,不用像select一样要把整个bitmap重置。
- bitmap是有大小限制的,而poll使用数组来保存fd的状态,数组的大小可以远远不止1024。
epoll
epoll_create:内核给epoll开辟个空间(上图里的绿色空间),返回个文件描述符。
epoll_ctl:给刚刚开辟的绿色空间内存入所有文件描述符,即需要监控的socket
epoll_waite:返回有事件发生的文件描述符的个数
不需要遍历全部连接了,而是只处理有事件的连接
?里是事件驱动,中断CPU,告诉CPU有消息了,所以CPU在中断前可以执行其他业务代码
小结
select里CPU的大多数时间用来主动遍历
而epoll,CPU在中断前可以做别的事
比如有一万个连接
BIO:会创建一万个线程,但是线程的开销是很大的,如果达到C10K,意味着要维护1万个连接。
NIO:一个线程就可以解决,不会阻塞,会直接返回是否需要读写数据,但是需要执行1万次系统调用,找到需要读写的socket。
- select:不需要1万次系统调用了,只需要1次系统调用,但是在内核里还要遍历1万次,来看哪个连接产生了网络事件。
- poll:和select的区别是,select用一个bitmap来标记哪个连接有事件,而poll是用一个数组来保存fd数据结构,fd里有一个字段标记是否有事件,所以poll没有1024个客户端的限制。
- epoll:不需要遍历所有的客户端来查询哪个有事件,有一个事件驱动的机制,epoll全程是event poll。发生事件的文件描述符会用一个链表串起来,然后有个指针指向这个链表,来一个事件就会中断cpu。
redis epoll
- redis epoll是轮训
- nginx epoll是阻塞
原因是redis是单线程,还要做别的事。
- redis用epoll解决了消息事件。即使有10w个客户端,它也只会读那几个有消息事件的。
- redis里对事件的处理是原子的(读、计算、写)和串行化的(一个一个事件串行执行)。
- redis 6.x开始是多线程的,但是只是读是并发读写,哪个客户端先读完CPU就计算,计算还是串行执行的,因为redis的瓶颈不在于CPU,而在于网络和内存。
零拷贝
kafka特征:快,需要持久,要存在磁盘里。
零拷贝最重要的是减少了kafka对kernel的系统调用。
零拷贝的前提:数据不需要加工,比如kafka和nginx,不需要对数据进行加工
本来读取数据需要几步
- 数据到内核
- 内核发送给kafka
- kafka处理后发给内核
- 内核发给客户端
但是因为kafka不需要对数据进行加工,内核提供了一个sendfile函数,kafka调用这个函数只需要
- 数据到sendfile
- sendfile发给客户端
少了几次拷贝的过程,所以叫零拷贝