NETTY详解

参考文献

https://www.jianshu.com/p/b9f3f6a16911

java 原生NIO的用法请参考
https://blog.csdn.net/define_us/article/details/79971640

概述

如果没有netty,远古时代我们会使用java.net + java.io,在近代,我们会使用java.nio,感谢这个时代,我们最终有了netty。当然,在java以外的C世界,我们还有Zero MQ这种立志于做成一个linux网络库的“消息队列”。但是我们java程序员天生不喜欢和操作系统太过耦合的环境,也不能接受自己的代码在自己不知道的情况下申请JVM外部的内存。

这里写图片描述

而NIO中,当一个Socket建立好之后,Thread并不会阻塞去接受这个Socket,而是将这个请求交给Selector,Selector会不断的去遍历所有的Socket,一旦有一个Socket建立完成,他会通知Thread,然后Thread处理完数据再返回给客户端——这个过程是阻塞的,这样就能让一个Thread处理更多的请求了。netty的用途很广,从阿里的jstorm到大众点评的cat,都使用netty作为底层通信。

另外,netty采用零拷贝技术,当他需要接收数据的时候,他会在堆内存之外开辟一块内存,数据就直接从IO读到了那块内存中去,在netty里面通过ByteBuf可以直接对这些数据进行直接操作,从而加快了传输速度。

传统的读外部系统内容,应用程序会给调用内核,然后内核下达命令让内容以DMA方式填入到内核缓冲区。然后CPU负责把内核缓冲区的内容copy到应用程序缓冲区。这样,应用代码才能最终看到。在 OS 层面上的 Zero-copy 通常指避免在 用户态(User-space) 与 内核态(Kernel-space) 之间来回拷贝数据. 例如 Linux 提供的 mmap 系统调用, 它可以将一段用户空间内存映射到内核空间, 当映射成功后, 用户对这段内存区域的修改可以直接反映到内核空间; 同样地, 内核空间对这段区域的修改也直接反映用户空间. 正因为有这样的映射关系, 我们就不需要在用户空间与内核空间之间拷贝数据, 提高了数据传输的效率。

而在netty里免没有上面这么复杂。指的更多是一种优化数据操作
比如说如下代码。需要copy两次。

ByteBuf allBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes());
allBuf.writeBytes(header);
allBuf.writeBytes(body);

实际的写法应该如下

CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
compositeByteBuf.addComponents(true, header, body);

在netty内部,他将两个ByteBuf在逻辑上合并为一个ByteBuf,从而避免了拷贝。

线程模型

Boss线程:

每个server服务器都会有一个boss线程,每绑定一个InetSocketAddress都会产生一个boss线程,比如:我们开启了两个服务器端口80和443,则我们会有两个boss线程。一个boss线程在端口绑定后,会接收传进来的连接,一旦连接接收成功,boss线程会指派一个worker线程处理连接。在boss线程接受了socket连接求后,会产生一个channel(一个打开的socket对应一个打开的channel),并把这个channel交给ServerBootstrap初始化时指定的ServerSocketChannelFactory来处理,boss线程则继续处理socket的请求。

worker线程:

ServerSocketChannelFactory则会从worker线程池中找出一个worker线程来继续处理这个请求。一般而言,我们使用NioServerSocketChannelFactory,每个worker可以服务不同的socket或者说channel,worker线程和channel不再有一一对应的关系。当然netty也支持一个worker线程对应一个channel的模式。

每个NioEventLoopd对应一个单线程执行的事件循环器。
在服务端程序中有两类 NioEventLoop。boss线程对应第类。这一类 NioEventLoop 一般也就只有一个,专门负责发现客户端连接。
在这里插入图片描述

另一类worker对应的,这一类 NioEventLoop 要设置有限的若干个,默认的个数只是为 CPU 核数的 2 倍,它负责处理客户端连接的信息读写。在客户端的程序中只有第一类。

一个NioEventLoopGroup可以包含一个到多个NioEventLoop(默认是平台CPU可用核心数的两倍)。

Channel

Channel,表示一个连接,可以理解为每一个请求,就是一个Channel。
ChannelHandler,核心处理业务就在这里,用于处理业务请求。
ChannelHandlerContext,用于传输业务数据。
ChannelPipeline,用于保存处理过程需要用到的ChannelHandler和ChannelHandlerContext。

bytebuf

DirectByteBuffer是Java NIO提供的堆外内存实现。

Netty中的UnpooledHeapByteBuf的byte[],可以由JVM自动GC。UnpooledDirectByteBuf的底层是DirectByteBuffer,只有在没有堆内对象引用的情况下才会被回收。设想一种危险的情况,堆内内存非常充足,但是堆外内存则已经濒临枯竭,所以堆内对象一直存活,堆外内存就不会被回收。所以,这种内存也建议手动回收。

PooledHeapByteBuf 和 PooledDirectByteBuf,则必须要主动将用完的byte[]/ByteBuffer放回池里,否则内存就要爆掉。这些实际上是JVM向操作系统申请的堆外内存。为什么要使用堆外内存呢?这是因为JVM的垃圾回收会移动对象,

简单的IM DEMO

SERVER端核心代码。ServerBootstrap负责初始化netty服务器。

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup);
            b.channel(NioServerSocketChannel.class);
            b.childHandler(new HelloServerInitializer());

            // 服务器绑定端口监听
            ChannelFuture f = b.bind(portNumber).sync();
            // 监听服务器关闭监听
            f.channel().closeFuture().sync();

            // 可以简写为
            /* b.bind(portNumber).sync().channel().closeFuture().sync(); */
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

NioEventLoopGroup可以理解为一个线程池,内部维护了一组线程,每个线程负责处理多个Channel上的事件,而一个Channel只对应于一个线程。

public class HelloServerInitializer extends ChannelInitializer<SocketChannel> {
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        // 以("\n")为结尾分割的 解码器
        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));

        // 字符串解码 和 编码
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());

        // 自己的逻辑Handler
        pipeline.addLast("handler", new HelloServerHandler());
    }
}

每个Channel都有自己的pipeline,用于存储处理该Channel的Handler

public class HelloServerHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        // 收到消息直接打印输出
        System.out.println(ctx.channel().remoteAddress() + " Say : " + msg);

        // 返回客户端消息 - 我已经接收到了你的消息
        ctx.writeAndFlush("Received your message !\n");
    }

    //首次连接
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        System.out.println("RamoteAddress : " + ctx.channel().remoteAddress() + " active !");

        ctx.writeAndFlush("Welcome to " + InetAddress.getLocalHost().getHostName() + " service!\n");

        super.channelActive(ctx);
    }
}

实际进行业务处理的Handler。

应用

  • Storm 0.0.9以上版本(以下版本使用的是Zero MQ)
  • redis访问工具包redisson
  • 大众点评开源的cat

典型堆栈

"tair-boss-share-pool-1-tair-netty-nio-thread-1" Id=965 RUNNABLE
	at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	-  locked sun.nio.ch.Util$3@2feea88d
	-  locked java.util.Collections$UnmodifiableSet@1bbd1e61
	-  locked sun.nio.ch.EPollSelectorImpl@51be5a96
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at org.jboss.netty.channel.socket.nio.SelectorUtil.select(SelectorUtil.java:68)
	at org.jboss.netty.channel.socket.nio.AbstractNioSelector.select(AbstractNioSelector.java:409)
	at org.jboss.netty.channel.socket.nio.AbstractNioSelector.run(AbstractNioSelector.java:206)
	...

	Number of locked synchronizers = 1
	- java.util.concurrent.ThreadPoolExecutor$Worker@629a2bfc

可以看到这就是netty底层也是用了NIO的技术。NIO底层会根据不同平台来实现。我这个是台mac电脑,安装的JDK里根本没有EPollSelectorImpl这个类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值