如何进行高性能编程之Netty学习随记

Netty学习内容个人备忘

1.Java中IO简介

1.BIO

BIO-Blocked IO,亦称同步阻塞IO;是java提供的最早的输入输出实现,主要特点就是在接受请求(accept方法)和读取socket中数据时会发生阻塞。核心组件为SocketChannel,ServerSocketChannel,Socket,ServerSocket,另外包含操作各种数据的InputStream和OutputStream;BIO是线程同步的,一个Socket对应一个Channel,在实现多线程的情况下,则每个线程对应一个请求连接,虽然后期的IO处理可以另外交由自定义的线程池进行处理,但是在面对特别多(如上10W数量)的客户端连接时,线程数量会限制程序的开发;因此BIO目前已不适应当前高并发的互联网开发需求。

2.NIO

NIO-Non-blocking IO,很多时候都是这么理解为非阻塞IO,实际上官方称为New IO,是相对于BIO提出的。NIO在接受客户端请求和读取网络连接数据的时候不会发生阻塞,且NIO提供的selector作为一个类似前台的概念可以同时处理多个channel,也就代表在进行多线程处理的时候,一个线程配合selector可以同时处理多个连接,这样极大的利用了操作系统的线程资源。适用于当前互联网的高并发开发需求。

3.AIO

异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,适用于连接比较长的情况;而且AIO的API使用相对比较复杂,不推荐使用。

2.NIO实现框架-Netty

1.NIO到Netty的转换

NIO是基于操作系统的selector/poll(目前jdk的实现中已经采用epoll替换了poll)实现的,基于多路复用技术实现的非阻塞IO并非异步;
核心类:

NIO具体介绍
1.多路复用器Selctor

多路复用的核心就是通过Selector来轮询注册在其上的Channel,当发现某个或者多个Channel处于就绪状态后(此处的就绪状态包含),从阻塞状态返回就绪的Channel的选择键集合,进行IO操作。

2.通道Channel

Channel是一个通道,可以通过它读取和写入数据,它就像自来水管一样,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道可以用于读、写或者同时用于读写。

因为Channel是全双工的,所以它可以比流更好地映射底层操作系统的API。特别是在UNIX网络编程模型中,底层操作系统的通道都是全双工的,同时支持读写操作。

3.缓冲区ByteBuffer-可复用

在 NIO 库中,所有数据都是用缓冲区进行处理的。在读取数据时,它是直接读到缓冲区中;在写入数据时,它也是写入到缓冲区中。任何时候访问 NIO 中的数据,我们都是通过缓冲区进行读写操作。
缓冲区实质上是一个数组。通常它是一个字节数组(ByteBuffer),也可以使用其它种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问,及维护读写位置(limit)等信息。
Bytebuffer的实现针对不同地数据类型是有多种的但是我们最常使用的是ByteBuffer;ByteBuffer中几个基本的属性为:

Capacity:缓冲区总容量,在对缓冲区进行操作过程中是不变的;
limit:缓冲区中已有数据的容量
position:操作缓冲区读写时当前操作的位置

Netty介绍及为什么选择Netty

NIO的使用需要我们熟练掌握以上所介绍的NIO核心类库,在高并发开发中需要我们自己去实现多线程处理连接的分发和IO处理的分发多线程处理;另外JDK的NIO本身是存在bug的,它会导致Selector空轮询最终导致CPU使用率100%,目前最新版本的JDK中也没有根本解决这个问题。

Netty框架

目前市面上基于NIO的实现框架有Netty,Mina,Grizzly,今天我们不讨论各个框架的区别和对比;主要介绍Netty框架,Netty修复了JDK的bug,简化了JDK的API,使得我们使用NEtty进行网络开发越发简洁。

Netty核心类库

下面通过Netty的一些核心类库来认识一下Netty以及它对NIO的封装:
EventLoopGroup

EventLoopGroup bossGroup = new NioEventLoopGroup(1);

Netty的线程模型采用的是多Reactor模型,在使用过程中我们会初始化一组EventLoopGroup(主Reactor线程组)处理客户端连接请求;另外初始化一组EventLoopGroup(worker Reactor线程组)处理主Reactor线程组分发过来的连接进行数据读取和写入。
在这里插入图片描述
ServerBootstrap是Netty的根启动类,通过它对Netty进行一些配置,ServerBootstrap的使用方法很简单,更接近于简单;我们只需要根据api的规定进行配置就可以配置好Netty;

            // 服务端启动引导工具类
            ServerBootstrap b = new ServerBootstrap();
            // 配置服务端处理的reactor线程组以及服务端的其他配置
            b.group(bossGroup, workerGroup2).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.DEBUG)).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline p = ch.pipeline();
                    p.addLast(new com.study.hc.net.netty.EchoServerHandler());
                }
            });

以上代码示例中,我们通过ServerBootstrap的group()方法,配置了主线程组和worker线程组,配置了channel的类型和连接的处理类,同时覆写了initChannel()方法自定义了ChannelPipeline;Pipeline是Netty的一项重要特性,称为职责链模式,可以对客户端请求进行链式处理。

ChannelPipeline是Netty职责链模式的实现类,基于链表的形式实现。其中职责链的头和尾是,其中每一个元素代表一个处理器,开发过程中我们可以自定义处理器,然后加入到职责链中,从而达到对客户端请求进行处理的目的;实现自定义的处理器需要实现的接口如下:

Netty的处理器处理事件分为入站事件和出站事件,
继承ChannelInboundHandlerAdapter那么处理器只处理入站事件,
继承ChannelOutboundHandlerAdapter那么处理器只处理出站事件,
继承ChannelDuplexHandler那么处理器对出站和入站请求都会进行处理。
public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler {}

public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {}

public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {}

SocketChannel ServerSocketChannel这两个类可以理解为Netty对NIO中的Channel的封装,使用方法也没有太大区别。

ByteBuffer在Netty中的实现跟NIO中略有不同,可以理解为NIO中ByteBuffer的增强版。我们先看一下基础属性:

capacity 缓冲区的容量,在对缓冲区进行处理过程中是不变的,但是Netty的缓冲区是可变的,当缓冲区大小不够的时候,Netty可以进行自动扩充
readIndex 读下标 缓冲区读取数据的位置
writeIndex 写下标 缓冲区写入数据的位置

Netty的ByteBuffer支持同时读写,实现就是因为它不像NIO中只有一个position,通过转换读写模式类操作缓冲区;Netty不需要做读写转换,通过两个读写下标同时进行读和写,而且读下标永远会小于写下标,毕竟要读取数据缓冲区中必定要有数据。Netty的缓冲区虽然不需要读写转换,但是它需要对缓冲区进行一些整理,比如清楚已读数据,整理读写下标等,具体API如下:

ByteBuf buf = Unpooled.buffer(10); 创建不支持内存复用的ByteBuffer,且创建的内存是堆内的
ByteBuf buf = Unpooled.directBuffer(10); 创建不支持内存复用的ByteBuffer,且创建的内存是对外的,也就是说它占用的不是java虚拟机的内存而是java虚拟机内存以外,系统内存部分
ByteBuf buf = Pooled.buffer(10); 创建支持内存复用的ByteBuffer
buf.discardReadBytes(); 丢弃已经读取的内容
buf.clear();  清空读写指针
buf.setZero(0, buf.capacity()); 将ByteBuffer清零

Netty的ByteBuffer具体分为堆外,堆内,可复用和不可服用,安全和不安全;其中安全和不安全在API上的直观体现就是如何操作ByteBuffer,也就是通过是Safe和Unsafe来操作缓冲区。
以上作为自己学习过程中的简要记录整理,肯定还存在缺失的地方,后续会继续补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值