<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          netty性能調(diào)優(yōu)

          共 5490字,需瀏覽 11分鐘

           ·

          2021-01-30 10:44

          點擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時間送達(dá)

          ? 作者?|? tinysakurac

          來源 |? urlify.cn/vueqqu

          76套java從入門到精通實戰(zhàn)課程分享

          調(diào)優(yōu)思路

          關(guān)于netty的學(xué)習(xí)和介紹,可以去github看官方文檔,這里良心推薦《netty實戰(zhàn)》和《netty權(quán)威指南》兩本書,前者對于新手更友好,原理和應(yīng)用都有講到,多讀讀會發(fā)現(xiàn)很多高性能的優(yōu)化點。

          netty高性能優(yōu)化點
          最近參加了阿里中間價性能比賽,為了提升netty寫的servive mesh的網(wǎng)絡(luò)通信的性能,最近幾天查了書、博客(這里強力推薦netty作者的博客,干貨真的很多),自己總結(jié)了如下一下優(yōu)化點。如果有錯誤希望能指正。

          注:這里所討論的對應(yīng)的netty版本為netty4

          首先要明確要netty優(yōu)化的幾個主要的關(guān)注點。

          1. 減少線程切換的開銷。

          2. 復(fù)用channel,可以選擇可共享的channel(@sharable,減少堆內(nèi)存開銷)

          3. zero copy的應(yīng)用

          4. 減少并發(fā)下的競態(tài)情況


          1 盡可能的復(fù)用EventLoopGroup。

          這里就要涉及netty的線程模型了。netty實戰(zhàn)的第七章里有很細(xì)致的闡釋。簡單說EventLoopGroup包含了指定數(shù)量(如果沒有指定,默認(rèn)是cpu核數(shù)的兩倍,可以從源碼中看到)的EvenetLoop,Eve netLoop和channel的關(guān)系是一對多,一個channel被分配給一個EventLoop,它生命周期中都會使用這個EventLoop,而EventLoop背后就是線程。見下圖。

          因此如果需要使用ThreadLocal保存上下文,那么許多channel就會共享同一個上下文。

          因此不需要每次都new出一個EventLoopGroup,其本質(zhì)上是線程分配,可以復(fù)用同一個EventLoopGroup,減少資源的使用和線程的切換。特別是在服務(wù)端引導(dǎo)一個客戶端連接的時候。如下:

          ServerBootstrap?bootstrap?=?new?ServerBootstrap();
          bootstrap.group(new?NioEventLoopGroup(),?new?NioEventLoopGroup())
          ????????.channel(NioServerSocketChannel.class)
          ????????.childHandler(new?SimpleChannelInboundHandler()?{
          ????????????@Override
          ????????????protected?void?channelRead0(ChannelHandlerContext?ctx,?ByteBuf?byteBuf)?
          ????????????????throws?Exception?{
          ????????????????Bootstrap?bootstrap?=?new?Bootstrap();
          ????????????????bootstrap.channel(NioSocketChannel.class)
          ????????????????????.group(ctx.channel().eventLoop())
          ????????????????????.handler(new?SimpleChannelInboundHandler()?{
          ????????????????????????????@Override
          ????????????????????????????protected?void?channelRead0(ChannelHandlerContext?ctx,?ByteBuf?in)?
          ????????????????????????????????throws?Exception?{
          ????????????????????????????????System.out.println("Received?data");
          ????????????????????????????}
          ????????????????????????});
          ????????????????ChannelFuture?future?=??bootstrap.connect(new?InetSocketAddress(xxx,?80));
          ????????????????future.addListener(new?ChannelFutureListener()?{
          ????????????????????@Override
          ????????????????????public?void?operationComplete(ChannelFuture?future)?throws?Exception?{
          ????????????????????????//?do?something
          ????????????????????}
          ????????????????});
          ????????????}
          ????????});
          bootstrap.bind(new?InetSocketAddress(8080)).sync();

          2 使用EventLoop的任務(wù)調(diào)度

          在EventLoop的支持線程外使用channel,用

          channel.eventLoop().execute(new?Runnable()?{
          ???@Override
          ????public?void?run()?{
          ?????channel.writeAndFlush(data)
          ????}
          });

          而不是直接使用channel.writeAndFlush(data);

          前者會直接放入channel所對應(yīng)的EventLoop的執(zhí)行隊列,而后者會導(dǎo)致線程的切換。

          3 減少ChannelPipline的調(diào)用長度

          public?class?YourHandler?extends?ChannelInboundHandlerAdapter?{
          ??@Override
          ??public?void?channelActive(ChannelHandlerContext?ctx)?{
          ????//?BAD?(most?of?the?times)
          ????ctx.channel().writeAndFlush(msg);?
          ????//?GOOD
          ????ctx.writeAndFlush(msg);?
          ???}
          }

          前者是將msg從整個ChannelPipline中走一遍,所有的handler都要經(jīng)過,而后者是從當(dāng)前handler一直到pipline的尾部,調(diào)用更短。

          同樣,為了減少pipline的長度,如果一個handler只需要使用一次,那么可以在使用過之后,將其從pipline中remove。

          4 減少ChannelHandler的創(chuàng)建

          如果channelhandler是無狀態(tài)的(即不需要保存任何狀態(tài)參數(shù)),那么使用Sharable注解,并在bootstrap時只創(chuàng)建一個實例,減少GC。否則每次連接都會new出handler對象。

          @ChannelHandler.Shareable?
          public?class?StatelessHandler?extends?ChannelInboundHandlerAdapter?{
          ???@Override
          ???public?void?channelActive(ChannelHandlerContext?ctx)?{}
          }

          public?class?MyInitializer?extends?ChannelInitializer?{
          ???private?static?final?ChannelHandler?INSTANCE?=?new?StatelessHandler();
          ???@Override
          ???public?void?initChannel(Channel?ch)?{
          ?????ch.pipeline().addLast(INSTANCE);
          ???}
          }

          同時需要注意ByteToMessageDecoder之類的編解碼器是有狀態(tài)的,不能使用Sharable注解。

          5 減少系統(tǒng)調(diào)用(Flush)的調(diào)用

          flush操作是將消息發(fā)送出去,會引起系統(tǒng)調(diào)用,應(yīng)該盡量減少flush操作,減少系統(tǒng)調(diào)用的開銷。

          同時也要減少write的操作, 因為這樣消息會流過整個ChannelPipline。

          6 使用單鏈接

          對于兩個指定的端點可以使用單一的channel,在第一次創(chuàng)建之后保存channel,然后下次對于同一個IP地址可以復(fù)用該channel而不需要重新建立。

          你可能需要一個map來保存對于不同ip的channel,但是在初始化時這可能會有一些線程并發(fā)的問題。

          7 利用netty零拷貝

          在IO操作時使用池化的DirectBuffer, 在bootstrap配置參數(shù)的時候,使用.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)來指定一個池化的Allocator,并且使用ByteBuf buf = allocator.directBuffer();來獲取Bytebuf。

          PooledByteBufAllocator,netty會幫你復(fù)用(無需release,除非你后面還需要用到同一個bytebuf)而不是每次都重新分配ByteBuf。在IO操作中,分配直接內(nèi)存而不是JVM的堆空間,就避免了在發(fā)送數(shù)據(jù)時,從JVM到直接內(nèi)存的拷貝過程,這也就是zero copy的含義。

          8 一些配置參數(shù)的設(shè)置

          ServerBootstrap啟動時,通常 bossGroup 只需要設(shè)置為 1 即可,因為 ServerSocketChannel 在初始化階段,只會注冊到某一個 eventLoop 上,而這個 eventLoop 只會有一個線程在運行,所以沒有必要設(shè)置為多線程。而 IO 線程,為了充分利用 CPU,同時考慮減少線上下文切換的開銷,通常設(shè)置為 CPU 核數(shù)的兩倍,這也是 Netty 提供的默認(rèn)值。

          在對于響應(yīng)時間有高要求的場景,使用.childOption(ChannelOption.TCP_NODELAY, true)和.option(ChannelOption.TCP_NODELAY, true)來禁用nagle算法,不等待,立即發(fā)送。

          9 小心的使用并發(fā)編程技巧

          千萬不要阻塞EventLoop!包括了Thead.sleep() CountDownLatch 和一些耗時的操作等等,盡量使用netty中的各種future。如果必須盡量減少重量級的鎖的的使用。

          在使用volatile時,
          壞的:

          private?volatile?Selector?selector;

          public?void?method()?{
          ??selector.select();
          ??....
          ??selector.selectNow();
          }

          好的:先將volatile變量保存到方法棧中,jdk源碼中大量的使用了這種技巧。

          private?volatile?Selector?selector;

          public?void?method()?{
          ??Selector?selector?=?this.selector;
          ??selector.select();
          ??....
          ??selector.selectNow();
          }


          使用AtomicFieldUpdater替換Atomic。關(guān)于這個可以參考http://normanmaurer.me/blog/2013/10/28/Lesser-known-concurrent-classes-Part-1/。簡單說,如果使用Atomic*,對于每個連接都會創(chuàng)建一個對象,而如果使用Atomic*FieldUpdater則會省去這部分的開銷,只有一個static final變量。

          private?static?final?AtomicLongFieldUpdater?ATOMIC_UPDATER?=
          ????????AtomicLongFieldUpdater.newUpdater(TheDeclaringClass.class,?"atomic");

          private?volatile?long?atomic;

          public?void?yourMethod()?{
          ????ATOMIC_UPDATER.compareAndSet(this,?0,?1);
          }


          10 響應(yīng)順序的處理

          當(dāng)使用了單鏈接,就有一個必須要解決的問題,將請求和響應(yīng)順序?qū)?yīng)起來。因為所有的操作都是異步的,TCP是基于字節(jié)流的,所以channel接收到的數(shù)據(jù)無法保證和發(fā)送順序一致。這個的解決方案就是,對于每個請求指定一個id,對于響應(yīng)也攜帶該id。如果后發(fā)的請求的響應(yīng)先到,則將其緩存起來(可以使用一個并發(fā)的隊列),然后等待該id之前的所有響應(yīng)全部接收到,再按序返回。
          很多rpc框架(如dubbo)都是有了這個技巧。




          鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布

          ??????

          ??長按上方微信二維碼?2 秒






          感謝點贊支持下哈?

          瀏覽 65
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91人妻无码成人精品一区91 | 国产女18毛片多18精品 | 亚洲狼友 | 黄色一级一片 | 啪一啪天天 |