通过jdk 指令查找某个java进程: jps -l
然后查找某个进程下的所有文件 : cd /proc/进程号
查看当前进程下的所有线程: cd task
查看当前进程下的 文件描述符: cd fd;
查看内核指令说明 :先安装指令包 :yum install man-pages
man 指令名称
比如: man accept
查看绑定指令:man bind
因为系统调用是2, 所以 查看系统调用(system call)绑定为: man 2 bind
io发展史 BIO(阻塞IO)–> NIO(非阻塞IO或者叫新IO) --> SELECT(多路复用器) --> EPOLL(多路复用器)
BIO: scoket连接最早就是用bio实现的,阻塞式IO,如果服务端接受到一个client的连接没有返回信息,会一直阻塞,其他客户端连不进来,无法读取其他客户端信息。 从java层 解决问题,new了多个线程去执行读取客户端发送信息的操作。
缺点: 资源的浪费: 1对cpu的进程调度浪费(syscall),2对内存栈的浪费(1M),每次连一个客户端,都会新创建一个线程去监听这个客户端连接请求。 线程数会目会一直增加,程序内存可能会撑爆。
NIO: 这这时候的socket有了非阻塞的方法: 如下图:客户端请求的连接都在主线程内执行。
优点:不需要单独创建线程去处理某一个客户端请求,全部在主线程内完成。解决了BIO的问题。
缺点:资源浪费。会引发C10k 问题(单个客户端请求数过大),每次都需要监听处理1W个客户端,但是实际连接的可能只有1个,那剩下的9999个连接都会浪费资源,每循环内会有O(n)系统调用(syscall)。
SELECT: 解决了NIO 的复杂度问题 O(n) ---> O(m) ,n为连接数,m为有效请求连接数,归根结底,是把java内的循环判断,交给了内核处理。 man 2 select, fds:文件描述符
EPOLL:解决了select 的内核层的O(n)复杂度的问题,在内存里,epoll专门开辟了一块空间,用来存储连接对象, 通过事件驱动(硬中断:比如网卡插入,声卡插入等等),把其他服务传给服务器内核的请求拿到,然后返回给内存。减少了内核的工作量。
优点: 充分利用硬件,尽量不浪费CPU,哪些信号来了,我就返回哪些,没有请求来 我就不处理。
nginx: 通过strace发现,nign会开启3个线程,第一个是创建主线程的线程,创建完master线程就销毁了,然后master会创建work线程(可配置work线程数),epoll的创建时在work线程里面执行的,因为master不干活,只管理work线程。
先epoll_create ,在内核中创建一块空间存储对象。 然后通过eoloo_ctl把socket端口的fd(文件描述符)放到epoll对象中,一般会放2个,因为IPV4和IPV6各有一个。然后执行epoll_wait ,等待请求进来。如下图:epoll_create=8,epoll_ctl往文件描述符8里面加入了 socket对应的fd ,6(IPV4)和7(IPV6),然后epoll_wait( 8 。 -------------注意:如果没有请求来,nginx的epoll会一直阻塞。redis的epoll不会阻塞,采用了轮训的策略。
优点:充分发挥了硬件的使用,又没有违反redis的原子串行化的操作。
因为redis只有一个线程,要干很多事情,比如 1.接受客户端读写都是靠epoll,轮训读写的目的 线程还要干其他事情,LUR,LFU(淘汰过滤),根据配置文件属性设置,定期的 rdb后台fork线程,fork子进程,aof的重写等等
nginx就是等着客户端来了干活,就阻塞呗 ,又不用干别的事情。
查看nginx的master进程的fd : cd /proc/3260 , 3260为master进程号。如下图:可以看到fd(文件描述符)有6和7.
无论是SELECT,POLL,EPOLL,他们都是多路复用器,他们返回的都是对象的状态,程序还是要自己发生读写(revieve ,accept,wait),他们是同步的。
异步怎么实现? linux内核还没有完全统一异步的过程。
补充知识点: 内存映射,0拷贝
内存映射: 又叫MMAP,就是把内存和磁盘操作直接打通,不通过内核空间操作,把内存数据直接映射到磁盘上,提高IO效率。通过mmap,主要是未了减少系统调用(syscall),最大的发挥的磁盘的写入效率。