美文网首页
(6)Java NIO浅析(未完)

(6)Java NIO浅析(未完)

作者: hedgehog1112 | 来源:发表于2020-11-01 12:38 被阅读0次

    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)切换成本高。要保留线程上下文,再执行。如线程数过高,切换>执行时间,这时系统loadCPU 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

    相关文章

      网友评论

          本文标题:(6)Java NIO浅析(未完)

          本文链接:https://www.haomeiwen.com/subject/tbntvktx.html