io多路复用

博客

  • 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)

  1. 程序先获得一个文件描述符,代表一个socket连接
  2. socket通过bind绑定端口号
  3. 通过listen方法监听这个socket
  4. 通过accept方法获取请求,如果accept成功,要进入阻塞状态

一开始是监听套接字5,之后是连接套接字6,连接方法是通过创建一个新的线程,然后阻塞io,不断等待客户端发送消息。

在这里插入图片描述

BIO的问题:

  1. 一个客户端对应一个线程,线程上下文切换开销大,且线程消耗内存资源
  2. 根本问题是会阻塞

NIO

https://tech.meituan.com/2016/11/04/nio.html

不需要多线程了,一个线程里循环全部连接。

比起BIO,如果一个连接不能读写,可以马上切换其他就绪的连接(channel)继续进行读写,不会一直阻塞住。

NIO的问题:C10K

假设有1W个客户端,每循环内会有O(n)的系统调用,如果只有一两个有数据,那剩下的就白调了。

多路复用

select

缺点:

  1. 虽然只用一次系统调用(本来要n次系统调用,现在多路复用了)了,但是系统要遍历全部的连接,看哪些连接有数据,遍历的时间复杂度还是O(n)
  2. 用来标记是哪个socket有事件的bitmap是有长度上限的

在这里插入图片描述

&rset是bitmap,标记哪个客户端有事件发生

while中的select是阻塞的,直到有事件发生,事件发生的时候,更改reset的位,这时select会有个返回值,程序向下执行,遍历所有文件描述符来找到是哪个连接发生了事件。

memset():把原来的1置为0

read()把数据读出来

puts()处理数据

poll

在这里插入图片描述

对比select的改进:

  1. 不是置位bitmap,而是把文件描述符的revents置位,这样就可以重用fds,不用像select一样要把整个bitmap重置。
  2. 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是单线程,还要做别的事。

  1. redis用epoll解决了消息事件。即使有10w个客户端,它也只会读那几个有消息事件的。
  2. redis里对事件的处理是原子的(读、计算、写)和串行化的(一个一个事件串行执行)。
  3. redis 6.x开始是多线程的,但是只是读是并发读写,哪个客户端先读完CPU就计算,计算还是串行执行的,因为redis的瓶颈不在于CPU,而在于网络和内存。

在这里插入图片描述

零拷贝

kafka特征:快,需要持久,要存在磁盘里。

零拷贝最重要的是减少了kafka对kernel的系统调用。

零拷贝的前提:数据不需要加工,比如kafka和nginx,不需要对数据进行加工

在这里插入图片描述

本来读取数据需要几步

  1. 数据到内核
  2. 内核发送给kafka
  3. kafka处理后发给内核
  4. 内核发给客户端

但是因为kafka不需要对数据进行加工,内核提供了一个sendfile函数,kafka调用这个函数只需要

  1. 数据到sendfile
  2. sendfile发给客户端

少了几次拷贝的过程,所以叫零拷贝

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值