Netty分享

Netty是什么

Netty是一款开发便捷,并且高性能的Java网络开发框架。Netty主要用来开发tcp或udp相关的服务,常被用来开发rpc服务,或被开源框架用作底层的网络库。

其便捷体现在:
1.统一而又简单的API,隐藏了底层细节方便开发,并可以在不同的IO模型间切换,如可以直接从NIO切换到epoll
2.扩展性强并且预置了很多编解码功能,支持主流协议(如protobuf)
3.使用广泛,参考案例多(如zookeeper,elasticSearch等知名项目使用了Netty)

其高性能体现在:
1.优雅的线程模型和无阻塞的api设计(大部分方法直接返回future)
2.使用内存池来分配Buffer,减少gc和反复创建对象的开销
3.零拷贝技术

HelloWorld与主要组件

        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class) // (3)
             .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ch.pipeline().addLast(new DiscardServerHandler());
                 }
             })
                    .option(ChannelOption.SO_BACKLOG, 128)          // (5)
                    .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (7)
    
            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }

主要组件(NIO):

EventLoop : 单线程的一个处理器,负责遍历selector,并处理其中的IO事件
EventLoopGroup:EventLoop池,为新连接的请求或者连结器分配EventLoop(实际上是将新来的连接,jdk中的channel注册到eventLoop中的selector上)
Channel:封装各种IO事件的处理方式,比如read,write,connect,bind
Pipeline:每一个Channel都有自己的Pipeline,它类似与Servlet的拦截器链,及发生一个IO事件时,将这个事件和处理结果依次向下传递处理。
Handler:把Pipeline比作一个拦截器链的化,handler就是一个个拦截器,用户可以自定义Handler来处理编解码,加解密和业务处理

流程图

内存池

在网络读写时,每一次读写都需要创建Buffer来承载数据。这些buffer通常只会被用到一次,之后便等待垃圾回收器将它们回收掉。由于网络服务的读写十分频繁,将产生大量的buffer,这会给GC照成很大的压力。因此Netty实现了一套内存池机制,自己来给buffer分配释放内存。

PoolChunk

为了能够简单的操作内存,必须保证每次分配到的内存时连续的。Netty中底层的内存分配和回收管理主要由PoolChunk实现,其内部维护一棵平衡二叉树memoryMap,所有子节点管理的内存也属于其父节点。



由于是平衡二叉树,那么用数组来存刚好合适。memoryMap 就是一个int数组,它的值代表了它的可分配内存状态。比如512号节点它的值是memoryMap[511] = val
如果val等于9(512节点的层数)那么表明512节点之下的所有节点都可分配。
如果val等于n(n <= 11)那么表明该节点的子节点有被分配了的,最大能分配的节点是n层的
如果val等于12(11 + 1)那么表示该节点下的所有节点都被分配了
那么如何来寻找合适的可分配节点呢?

## req 为被分配的节点的层数
def find(node, req):
    if node.val < req :
        res = find(node.left,req)
        if res == NOT_FOUND:
            res = find(node.right,req)
        return res
    if node.val == req:
        return node.id
    return NOT_FOUND        

PoolSubpage

前面说到pageSize是chunk叶子节点的大小,默认为8K,当要分配的内存大于pageSize时去poolChunk里申请内存,而当内存小于pageSize时则在PoolSubpage中申请。


为什么要有Subpage呢,因为如果很小的内存仍要占一个page,那么会造成严重的内存浪费。而如果把pageSize设的足够小,又会使得分配内存的树(PoolChunk)占据太大的内存,并且对于小内存节点的搜索耗时增加。
那么什么是Subpage呢,Subpage实际上是将Chunk的Page的切分。比如现在要分配一个2K的内存,那么我们先找到一个8K的page,然后把它分成4份,其中一份分配出去,剩下的三分等着之后有要分配2K内存时再分配出去。

PoolChunkList

PoolChunkList就是由PoolChunk组成的双向链表


PoolArena

PoolArena是Netty内存池的基本单元,由两个PoolSubpages数组和6个PoolChunkList组成


PoolSubpage用于分配小于8k的内存;

  • tinySubpagePools:用于分配小于512字节的内存,默认长度为32,因为内存分配最小为16,每次增加16,直到512,区间[16,512)一共有32个不同值;
  • smallSubpagePools:用于分配大于等于512字节的内存,默认长度为4;
  • tinySubpagePools和smallSubpagePools中的元素都是默认subpage。

PoolChunkList用于分配大于8k的内存;

  • qInit:存储内存利用率0-25%的chunk
  • q000:存储内存利用率1-50%的chunk
  • q025:存储内存利用率25-75%的chunk
  • q050:存储内存利用率50-100%的chunk
  • q075:存储内存利用率75-100%的chunk
  • q100:存储内存利用率100%的chunk

为什么ChunkList要分别装在六个不同的链表里呢,其实是为了解决整体利用率的问题。使用这种方法能避免频繁的创建和删除PoolChunk



上图展示了chunk在不同的ChunkList之间的移动规则
可以看到除了qInit和q000没法相互移动,别的相邻的list都是可以相互移动的。
刚创建的PoolChunk被放在qInit里,之后如果该chunk的利用率大于25%则会被放入q000,但chunk却没法重q000回到qInit。
在qInit里的chunk即使利用率等于0也不会被释放,而别的chunkList则会直接被释放。这个特性是的刚被创造出的chunk不会被立即释放,从而避免内存抖动时创建过多的chunk。

private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
    ++allocationsNormal;
    if (q050.allocate(buf, reqCapacity, normCapacity) 
     || q025.allocate(buf, reqCapacity, normCapacity) 
     || q000.allocate(buf, reqCapacity, normCapacity) 
     || qInit.allocate(buf, reqCapacity, normCapacity) 
     || q075.allocate(buf, reqCapacity, normCapacity)
     || q100.allocate(buf, reqCapacity, normCapacity)) {
        return;
    }

    // Add a new chunk.
    PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
    long handle = c.allocate(normCapacity);
    assert handle > 0;
    c.initBuf(buf, handle, reqCapacity);
    qInit.add(c);
}

上述代码展示了不同ChunkList的优先级,可以看见申请内存时是按照:
q050->q025->q000->qInit->q075->q100的顺序申请的。
为什么q000不是在最前面,而是q050在最前面呢?因为如果q000在最前面,那么Chunk将很难被释放掉,会导致整体利用率不高,而如果qInit在最前面的话,会导致刚创建的Chunk没过多久就被释放了,使得Chunk被频繁创建,而当q050在最前时,避免了前面的问题,并且q050的命中率比q075和q100高,是个则中的选择。

由于netty通常应用于高并发系统,不可避免的有多线程进行同时内存分配,可能会极大的影响内存分配的效率,为了缓解线程竞争,可以通过创建多个poolArena细化锁的粒度,提高并发执行的效率。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345