NIO同步非阻塞的I/O模型,多路复用基础
对比常见I/O模型,NIO怎么利用事件模型处理I/O,解决线程池瓶颈处理海量连接,包括利用面向事件的方式编写服务端/客户端程序。最后延展到一些高级主题,
Reactor与Proactor模型的对比、Selector的唤醒、Buffer的选择等。
NIO作用:
1、事件驱动模型;2、避免多线程;3、单线程处理多任务;3、更高级的IO函数,zero-copy;
4、基于block传输,比基于流传输更高效
5、非阻塞I/O,I/O读写不再阻塞,而是返回0;
6、IO多路复用大大提高Java网络应用可伸缩性和实用性
一、传统BIO模型
同步阻塞I/O处理
用多线程在于socket.accept()、socket.read()、socket.write()同步阻塞,连接处理I/O时,系统阻塞,单线程的挂死;但CPU被释放,开启多线程,就可让CPU处理更多。
1.多线程本质:利用多核,当I/O阻塞系统,多线程用CPU(空闲)资源。一般用线程池(小于单机1000 ok),让每个连接专注于自己I/O且模型简单,不用过多考虑过载、限流等问题,可缓冲处理不了请求。
2.问题:严重依赖线程。但线程是很"贵"资源,主要表现在:
1)创建和销毁成本很高,Linux中本质上就是进程,创建、销毁都是重量级系统函数
2)占用较大内存,Java栈512K~1M,线程数过千,JVM内存被吃掉一半。
3)切换成本高。要保留线程上下文,再执行。如线程数过高,切换>执行时间,这时系统load偏高、CPU sy使用率特别高(20%以上),导致系统不可用
4)系统负载不稳定。因系统负载是用活动线程数或CPU核心数,线程数量高但外部网络不稳定,造成大量请求同时返回,大量线程阻塞,系统负载大
3.十万甚至百万级连接时,传统BIO模型无能为力
二、常见I/O模型对比
I/O两个段:1)等待就绪(不用CPU)、操作(用CPU)。读函数,等系统可读和真正读
操作过程非常快,属于memory copy,带宽通常1GB/s以上,基本不耗时
socket.read()为例:BIO用户最关心“我要读”,NIO关心"我可以读了",AIO关注“读完了”
BIO:如TCP RecvBuffer没数据,阻塞直到数据,返回
NIO:没有返回0,有则把数据从网卡读到内存,返回给用户
AIO(Async I/O):不等待就是非阻塞,异步处理数据
NIO特点:socket主要的读、写、注册和接收函数,等待就绪阶段非阻塞,真正I/O操作是同步阻塞的(消耗CPU但性能非常高)。
三、结合事件模型使 用NIO同步非阻塞
同步非阻塞:阻塞:不能读写(网卡满写/网卡空读),I/O 操作阻塞;同步:数据ready同步读写。两者阶段不同
NIO读写函数立刻返回,给不开线程用CPU最好机会:如连接不能读写(返回0),记下来(在Selector上注册标记位),切到其它就绪连接(channel)继续读写
事件模型单线程处理所有I/O请求:读、写就绪、新连接来
1、注册当事件到时对应处理器。合适时机告诉事件选择器:对这个事件感兴趣。1)写操作:就是写不出去时对写事件感兴趣;2)读操作:完成连接和系统没法承载新读数据时;对于accept,服务器启动时;对于connect,connect失败需要重连或异步调connect时。
2、其次死循环选择就绪事件,执行系统调用(Linux 2.6前select、poll,2.6后epoll,Windows是IOCP),阻塞等待新事件到来,到来时在selector上注册标记位:可读、可写或连接来
ps:select是阻塞,无论操作系统的通知(epoll)还是不停的轮询(select,poll),函数阻塞的。放心在while(true)里调用,不用担心CPU空转
四、优化线程模型
上面示例总结出NIO是怎么解决掉线程瓶颈,处理海量连接的:
由原来阻塞读写(占线程)变成单线程轮询事件,进行读写网络描述符进行读写。除事件轮询阻塞(没可干事必须要阻塞),其他I/O操作都是纯CPU操作,没必要开启多线程。
连接数大时因线程切换带来问题也解决:没线程切换,只拼命读、写、选择事件。如用多核心I/O,效率更高。需要的线程,主要包括以下几种:
1、事件分发器,单线程选就绪事件
2、I/O处理器,包括connect、read、write等,这种纯CPU操作,一般开启CPU核心个线程就可以。
3、业务线程,处理完I/O后,自己的业务逻辑,有的有其他阻塞I/O,如DB,RPC。只要有阻塞,就要单独线程
Java的Selector对于Linux致命限制:同channel的select不能并发调用。如多个I/O线程保证:一个socket只属于一个IoThread,一个IoThread可管多个socket。
连接和读写可分开。虽read()和write()高效无阻塞,但占用CPU,不支持高并发。
五、NIO客户端的魔力
客户端BIO+连接池,建立n个连接,某个被I/O占用,用其他连接来提高性能
和服务端相同:指望增加连接数提高性能,连接数又受制于线程数、线程很贵、无法建立很多线程,性能遇瓶颈
六、每连接顺序请求Redis
Redis服务端全局串行,保证同连接所有请求与返回顺序一致。可用单线程+队列,把请求数据缓冲,pipeline发送,返回future,channel可读时,直接在队列中把future取回来,done()
七、常见的RPC框架,如Thrift,Dubbo
这种框架内部一般维护了请求的协议和请求号,可以维护一个以请求号为key,结果的result为future的map,结合NIO+长连接,获取非常不错的性能。
https://zhuanlan.zhihu.com/p/23488863
网友评论