安庆大理运城常德铜陵江西
投稿投诉
江西南阳
嘉兴昆明
铜陵滨州
广东西昌
常德梅州
兰州阳江
运城金华
广西萍乡
大理重庆
诸暨泉州
安庆南充
武汉辽宁

java性能优化实战从BIO到NIO,再到netty高性能架

3月27日 终离去投稿
  Netty的高性能架构,是基于一个网络编程设计模式Reactor进行设计的。现在,大多数与IO相关的组件,都会使用Reactor模型,比如Tomcat、Redis、Nginx等,可见Reactor应用的广泛性。
  Reactor是NIO的基础。为什么NIO的性能就能够比传统的阻塞IO性能高呢?我们首先来看一下传统阻塞式IO的一些特点。阻塞IO模型
  如上图,是典型的BIO模型,每当有一个连接到来,经过协调器的处理,就开启一个对应的线程进行接管。如果连接有1000条,那就需要1000个线程。
  线程资源是非常昂贵的,除了占用大量的内存,还会占用非常多的CPU调度时间,所以BIO在连接非常多的情况下,效率会变得非常低。
  下面的代码是使用ServerSocket实现的一个简单Socket服务器,监听在8888端口。publicclassBIO{publicstaticvoidmain(String〔〕args)throwsException{intconnectionNum0;intport8888;ExecutorServiceserviceExecutors。newCachedThreadPool();ServerSocketserverSocketnewServerSocket(port);while(!stop){if(10connectionNum){}SocketsocketserverSocket。accept();service。execute((){try{ScannerscannernewScanner(socket。getInputStream());PrintStreamprintStreamnewPrintStream(socket。getOutputStream());while(!stop){Stringsscanner。next()。trim();printStream。println(PONG:s);}}catch(Exceptionex){ex。printStackTrace();}});connectionN}service。shutdown();serverSocket。close();}}
  启动之后,使用nc命令进行连接测试,结果如下。ncvlocalhost8888Connectiontolocalhostport8888〔tcpdditcp1〕succeeded!helloPONG:hellonicePONG:nice
  使用JMC工具,在录制期间发起多个连接,能够发现有多个线程在运行,和连接数是一一对应的。
  可以看到,BIO的读写操作是阻塞的,线程的整个生命周期和连接的生命周期是一样的,而且不能够被复用。
  就单个阻塞IO来说,它的效率并不比NIO慢。但是当服务的连接增多,考虑到整个服务器的资源调度和资源利用率等因素,NIO就有了显著的效果,NIO非常适合高并发场景。非阻塞IO模型
  其实,在处理IO动作时,有大部分时间是在等待。比如,socket连接要花费很长时间进行连接操作,在完成连接的这段时间内,它并没有占用额外的系统资源,但它只能阻塞等待在线程中。这种情况下,系统资源并不能被合理利用。
  Java的NIO,在Linux上底层是使用epoll实现的。epoll是一个高性能的多路复用IO工具,改进了select和poll等工具的一些功能。在网络编程中,对epoll概念的一些理解,几乎是面试中必问的问题。
  epoll的数据结构是直接在内核上进行支持的,通过epollcreate和epollctl等函数的操作,可以构造描述符(FD)相关的事件组合(event)。
  这里有两个比较重要的概念:fd每条连接、每个文件,都对应着一个描述符,比如端口号。内核在定位到这些连接的时候,就是通过fd进行寻址的。event当fd对应的资源,有状态或者数据变动,就会更新epollitem结构。在没有事件变更的时候,epoll就阻塞等待,也不会占用系统资源;一旦有新的事件到来,epoll就会被激活,将事件通知到应用方。
  关于epoll还会有一个面试题,相对于select,epoll有哪些改进?
  你可以这样回答:epoll不再需要像select一样对fd集合进行轮询,也不需要在调用时将fd集合在用户态和内核态进行交换;应用程序获得就绪fd的事件复杂度,epoll是O(1),select是O(n);select最大支持约1024个fd,epoll支持65535个;select使用轮询模式检测就绪事件,epoll采用通知方式,更加高效。
  我们还是以Java中的NIO代码为例,来看一下NIO的具体概念。publicclassNIO{publicstaticvoidmain(String〔〕args)throwsException{intconnectionNum0;intport8888;ExecutorServiceserviceExecutors。newCachedThreadPool();ServerSocketChannelsscServerSocketChannel。open();ssc。configureBlocking(false);ssc。socket()。bind(newInetSocketAddress(localhost,port));SelectorselectorSelector。open();ssc。register(selector,ssc。validOps());while(!stop){if(10connectionNum){}intnumselector。select();if(num0){}IteratorSelectionKeyeventsselector。selectedKeys()。iterator();while(events。hasNext()){SelectionKeyeventevents。next();if(event。isAcceptable()){SocketChannelscssc。accept();sc。configureBlocking(false);sc。register(selector,SelectionKey。OPREAD);connectionN}elseif(event。isReadable()){try{SocketChannelsc(SocketChannel)event。channel();ByteBufferbufByteBuffer。allocate(1024);intsizesc。read(buf);if(1size){sc。close();}StringresultnewString(buf。array())。trim();ByteBufferwrapByteBuffer。wrap((PONG:result)。getBytes());sc。write(wrap);}catch(Exceptionex){ex。printStackTrace();}}elseif(event。isWritable()){SocketChannelsc(SocketChannel)event。channel();}events。remove();}}service。shutdown();ssc。close();}}
  上面这段代码比较长,是使用NIO实现的和BIO相同的功能。从它的API设计上,我们就能够看到epoll的一些影子。
  首先,我们创建了一个服务端ssc,并开启一个新的事件选择器,监听它的OPACCEPT事件。ServerSocketChannelsscServerSocketChannel。open();SelectorselectorSelector。open();ssc。register(selector,ssc。validOps());
  共有4种事件类型,分别是:新连接事件(OPACCEPT);连接就绪事件(OPCONNECT);读就绪事件(OPREAD);写就绪事件(OPWRITE)。
  任何网络和文件操作,都可以抽象成这四个事件。
  接下来,在while循环里,使用select函数,阻塞在主线程里。所谓阻塞,就是操作系统不再分配CPU时间片到当前线程中,所以select函数是几乎不占用任何系统资源的。intnumselector。select();
  一旦有新的事件到达,比如有新的连接到来,主线程就能够被调度到,程序就能够向下执行。这时候,就能够根据订阅的事件通知,持续获取订阅的事件。由于注册到selector的连接和事件可能会有多个,所以这些事件也会有多个。我们使用安全的迭代器循环进行处理,在处理完毕之后,将它删除。
  这里留一个思考题:如果事件不删除的话,或者漏掉了某个事件的处理,会有什么后果?IteratorSelectionKeyeventsselector。selectedKeys()。iterator();while(events。hasNext()){SelectionKeyeventevents。next();。。。events。remove();}}
  有新的连接到达时,我们订阅了更多的事件。对于我们的数据读取来说,对应的事件就是OPREAD。和BIO编程面向流的方式不同,NIO操作的对象是抽象的概念Channel,通过缓冲区进行数据交换。SocketChannelscssc。accept();sc。configureBlocking(false);sc。register(selector,SelectionKey。OPREAD);
  值得注意的是:服务端和客户端的实现方式,可以是不同的。比如,服务端是NIO,客户端可以是BIO,它们并没有什么强制要求。
  另外一个面试时候经常问到的事件就是OPWRITE。我们上面提到过,这个事件是表示写就绪的,当底层的缓冲区有空闲,这个事件就会一直发生,浪费占用CPU资源。所以,我们一般是不注册OPWRITE的。
  这里还有一个细节,在读取数据的时候,并没有像BIO的方式一样使用循环来获取数据。
  如下面的代码,我们创建了一个1024字节的缓冲区,用于数据的读取。如果连接中的数据,大于1024字节怎么办?SocketChannelsc(SocketChannel)event。channel();ByteBufferbufByteBuffer。allocate(1024);intsizesc。read(buf);
  这涉及两种事件的通知机制:水平触发(leveltriggered)称作LT模式。只要缓冲区有数据,事件就会一直发生边缘触发(edgetriggered)称作ET模式。缓冲区有数据,仅会触发一次。事件想要再次触发,必须先将fd中的数据读完才行
  可以看到,Java的NIO采用的就是水平触发的方式。LT模式频繁环唤醒线程,效率相比较ET模式低,所以Netty使用JNI的方式,实现了ET模式,效率上更高一些。Reactor模式
  了解了BIO和NIO的一些使用方式,Reactor模式就呼之欲出了。
  NIO是基于事件机制的,有一个叫作Selector的选择器,阻塞获取关注的事件列表。获取到事件列表后,可以通过分发器,进行真正的数据操作。
  你可以回看下我在上文举例的Java中的NIO代码,对比分析一下,你会发现Reactor
  模型里面有四个主要元素:Acceptor处理client的连接,并绑定具体的事件处理器;Event具体发生的事件,比如图中s的read、send等;Handler执行具体事件的处理者,比如处理读写事件的具体逻辑;Reactor将具体的事件分配(dispatch)给Handler。
  我们可以对上面的模型进行进一步细化,如下图所示,将Reactor分为mainReactor和subReactor两部分。
  mainReactor负责监听处理新的连接,然后将后续的事件处理交给subRsubReactor对事件处理的方式,也由阻塞模式变成了多线程处理,引入了任务队列的模式。
  熟悉Netty的同学可以看到,这个Reactor模型就是Netty设计的基础。在Netty中,Boss线程对应着对连接的处理和分派,相当于mainRWorker线程对应着subReactor,使用多线程负责读写事件的分发和处理。
  这种模式将每个组件的职责分得更细,耦合度也更低,能有效解决C10k问题。AIO
  关于NIO的概念,误解还是比较多的。
  面试官可能会问你:为什么我在使用NIO时,使用Channel进行读写,socket的操作依然是阻塞的?NIO的作用主要体现在哪里?这行代码是阻塞的intsizesc。read(buf);
  这时你可以回答:NIO只负责对发生在fd描述符上的事件进行通知。事件的获取和通知部分是非阻塞的,但收到通知之后的操作,却是阻塞的,即使使用多线程去处理这些事件,它依然是阻塞的。
  AIO更近一步,将这些对事件的操作也变成非阻塞的。下面是一段典型的AIO代码,它通过注册CompletionHandler回调函数进行事件处理。这里的事件是隐藏的,比如read函数,它不仅仅代表Channel可读了,而且会把数据自动的读取到ByteBuffer中。等完成了读取,就会通过回调函数通知你,进行后续的操作。publicclassAIO{publicstaticvoidmain(String〔〕args)throwsException{intport8888;AsynchronousServerSocketChannelsscAsynchronousServerSocketChannel。open();ssc。bind(newInetSocketAddress(localhost,port));ssc。accept(null,newCompletionHandler(){voidjob(finalAsynchronousSocketChannelsc){ByteBufferbufferByteBuffer。allocate(1024);sc。read(buffer,buffer,newCompletionHandlerInteger,ByteBuffer(){Overridepublicvoidcompleted(Integerresult,ByteBufferattachment){StringstrnewString(attachment。array())。trim();ByteBufferwrapByteBuffer。wrap((PONG:str)。getBytes());sc。write(wrap,null,newCompletionHandlerInteger,Object(){Overridepublicvoidcompleted(Integerresult,Objectattachment){job(sc);}Overridepublicvoidfailed(Throwableexc,Objectattachment){System。out。println(error);}});}Overridepublicvoidfailed(Throwableexc,ByteBufferattachment){System。out。println(error);}});}Overridepublicvoidcompleted(AsynchronousSocketChannelsc,Objectattachment){ssc。accept(null,this);job(sc);}Overridepublicvoidfailed(Throwableexc,Objectattachment){exc。printStackTrace();System。out。println(error);}});Thread。sleep(Integer。MAXVALUE);}}
  AIO是Java1。7加入的,理论上性能会有提升,但实际测试并不理想。这是因为,AIO主要处理对数据的自动读写操作。这些操作的具体逻辑,假如不放在框架中,也要放在内核中,并没有节省操作步骤,对性能的影响有限。而Netty的NIO模型加上多线程处理,在这方面已经做得很好,编程模式也比AIO简单。
  所以,市面上对AIO的实践并不多,在采用技术选型的时候,一定要谨慎。响应式编程
  你可能听说过Spring5。0的WebFlux,WebFlux是可以替代SpringMVC的一套解决方案,可以编写响应式的应用,两者之间的关系如下图所示:
  SpringWebFlux的底层使用的是Netty,所以操作是异步非阻塞的,类似的组件还有vert。x、akka、rxjava等。
  WebFlux是运行在projectreactor之上的一个封装,其根本特性是后者提供的,至于再底层的非阻塞模型,就是由Netty保证的了。
  非阻塞的特性我们可以理解,那响应式又是什么概念呢?
  响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值,通过数据流进行传播。
  这段话很晦涩,在编程方面,它表达的意思就是:把生产者消费者模式,使用简单的API表示出来,并自动处理背压(Backpressure)问题。
  背压,指的是生产者与消费者之间的流量控制,通过将操作全面异步化,来减少无效的等待和资源消耗。
  Java的Lambda表达式可以让编程模型变得非常简单,Java9更是引入了响应式流(ReactiveStream),方便了我们的操作。
  比如,下面是SpringCloudGateWay的FluentAPI写法,响应式编程的API都是类似的。publicRouteLocatorcustomerRouteLocator(RouteLocatorBuilderbuilder){returnbuilder。routes()。route(rr。path(market)。filters(ff。filter(newRequestTimeFilter())。addResponseHeader(XResponseDefaultFoo,DefaultBar))。uri(http:localhost:8080marketlist)。order(0)。id(customerfilterrouter))。build();}
  从传统的开发模式过渡到Reactor的开发模式,是有一定成本的,不过它确实能够提高我们应用程序的性能,至于是否采用,这取决于你在编程难度和性能之间的取舍。小结
  我们系统地学习了BIO、NIO、AIO等概念和基本的编程模型Reactor,我们了解到:BIO的线程模型是一个连接对应一个线程的,非常浪费资源;NIO通过对关键事件的监听,通过主动通知的方式完成非阻塞操作,但它对事件本身的处理依然是非阻塞的;AIO完全是异步非阻塞的,但现实中使用很少。
  使用Netty的多Acceptor模式和多线程模式,我们能够方便地完成类似AIO这样的操作。Netty的事件触发机制使用了高效的ET模式,使得支持的连接更多,性能更高。
  使用Netty,能够构建响应式编程的基础,加上类似Lambda表达式这样的书写风格,能够完成类似WebFlux这样的响应式框架。响应式编程是一个趋势,现在有越来越多的框架和底层的数据库支持响应式编程,我们的应用响应也会更加迅速。
投诉 评论 转载

阳了以后该吃什么?每个阶段的饮食要注意什么?感染新冠怎么办?饮食方面怎么注意?很多人都在问一旦感染新冠病毒,居家养病期间适合吃哪些食物?感染之后前期中期后期各个阶段的饮食又该注意什么?居家期间,阳性感染者应平衡膳食……稳超7600C38!宏碁掠夺者VestaII炫光星舰RGBD前言之前宏碁掠夺者推出了高性能、高颜值的VestaII炫光星舰RGBDD5600016G2C30,它在DDR56000中以较高的性价比以及出色的散热能力,出色的超频潜力,……火箭惨败开拓者看不到希望!塞拉斯还是下课吧?火箭队遭遇开拓者,任何人都会想到是输球,但是输的一点价值也没有。最关键的是攻防两端没有任何亮点,一点希望也看不到。此役,火箭队也不是没有想法,想利用努尔基奇防守端出不来的……51个娃设53个班干部岗位班主任用心良苦家长纷纷点赞四川有家长分享这么个有趣的事情:自家孩子读小学,孩子的班主任老师设置了53个班干部岗位,而全班一共都只有51名同学,也就是所有孩子都是班干部。除了我们常见的班长、学习委员……普尔的使用说明书被找到后!22岁352天,他在总决赛上创造了两年挣扎期,勇士队收获了意外之喜,成王朝未来的希望2019年的休赛期,勇士队以首轮第28顺位选中了后卫普尔,试图将其培养成库里的替补,可寒冰射手的属性还是无法改变;恰恰好……一个人一生能赚多少钱,吃多少粮食,其实早就注定了导语:每个人赤条条的来到这个世界,赤条条的离开这个世界,短短几十年,每个人为了生活,在这个世界上奔忙不停,为了多赚一些钱,改善生活,每个人命运是不同的,有些人,拼尽全身的……西藏那些事儿(十)林芝的最后一天返回拉萨,途径卡定沟,虽然此景点不大,也没有特别的壮美和开阔如草原般的景色,但它就好像是个世外桃源,进到深处才发现,它美在那份宁静、那份安逸、那份精致。。。。。。……柳市灵溪创新高,塘下鳌江创新低,温州强镇开始大洗牌?最新的千强镇排名又发布了,温州16个镇上榜,数量和去年持平。其中8个镇排名上升,8个镇排名下降,下降幅度大于上升幅度。从百强镇来看,温州的名额在逐渐减少,高峰时有7个,现……商票逾期拒付加剧,诉讼是最有效的途径一。荣盛地产除了股价的跌跌不休,荣盛理财逾期,商票拒付,供货商及中小债权人更是亏损严重。自去年10月25日开始,荣盛发展的商票就全面逾期拒付了。跟相关数据显示,荣盛发展已……母爱不需要理由母爱不需要理由作者张伟群朗诵张伟群快到母亲节了。在小区门口,看到张贴的告示,说是要征集母亲节的爱母一句话,心头一动,便思索起来。我想起了亲眼见的一幕,在小区乐园的竹……java性能优化实战从BIO到NIO,再到netty高性能架Netty的高性能架构,是基于一个网络编程设计模式Reactor进行设计的。现在,大多数与IO相关的组件,都会使用Reactor模型,比如Tomcat、Redis、Nginx等……CMF报告以修复和改革重振中国经济内生动力文丨郭泽涵中国网记者9月18日,中国宏观经济论坛(CMF)宏观经济月度数据分析会(2022年9月)在线举办,本次会议聚焦转折适应期中的中国宏观经济,毛振华、伍戈、刘元春等……
梅花香自苦寒来,王健林用极致自律换来璀璨人生路肠胃不好的人,建议做好这5件事,或能修复好肠胃健康,需了解互战老东家,利夫绝平上篮,几乎拯救球队,书豪却成看客中介圈在传存量房房贷利率降息到4。1,5。88以上会成功解放代码HHBG收藏13万份意想不到的美和珍贵汝州市寄料镇平王宋村借势康养旅游助力乡村振兴售价28。86万!续航610km,比亚迪汉EV算高性价比的新免费的WinNas会不会成为轻NAS市场的主流2022中国企业500强出炉!云南7家企业入围1味中药,代茶喝,滋阴降燥,降血糖男人到中年,别舍不得花钱,这5种食物经常吃,精力充沛显年轻春季穿衣在精不在多!用这些基础款,打造优雅文静的气质摩拜单车背后的人工智能二三事;新型广告媒介但凡出现仙人指路的征兆,都是主力连板的标志药物中毒会引起发烧吗钓鱼岛领海内巡航简洁干练的描述,代表的是中国坚定的意志李长歌是真实存在的吗:李长歌无历史原型(杜撰角色)烫发后怎么用精油打理卷发学生会招新的工作总结您有一份阅读书单,请查收上班族如何克服焦虑症造就属于自己天空档案员工作述职报告调皮的雾

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找七猫云易事利