Netty编程笔记

Netty编程笔记

参考资料:

  • Netty权威指南 第二版

I/O模型:阻塞模型、非阻塞模型、IO复用模型(Linux提供了select、poll)、信号驱动IO模型、异步IO模型。

  • 阻塞与非阻塞模型,这两种模型均涉及到用户态与内核台的切换

  • IO复用模型,Linux提供了select、poll和epoll三种方式,其中select、poll是顺序扫描fd是否就绪,切支持的fd数量有限;epoll使用事件驱动方式代替顺序扫描,因此新能更高,当有fd就绪时,立即回调rollback

  • 信号驱动IO模型:先开启套接口信号驱动IO功能,通过系统调用sigaction执行一个信号处理函数(该操作时非阻塞的),当数据准备就绪时,为该进程生成一个SIGIO信号,通过信号回调通知应用程序调用recvfrom来读取数据,并通知主循环函数处理数据。

  • 异步IO模型:告知内核启动某个操作,并让内核在整个操作完成后通知我们。与信号驱动的区别是:信号驱动由内核通知我们何时开始下一个io操作,异步IO是由内核通知我们IO操作何时已经完成。

IO多路复用技术

Linux OS中,支持多路复用技术的有:select、pselect、poll、epoll,首选epoll,其优点如下:

  • 支持一个进程打开的socket描述fd 不受限制(仅受限于操作系统的最大文件句柄数)。

    • select最大的缺陷是单个进程所打开的fd是由FD_SETSIZE设置的,默认是1024个,设置改大后会带来网络效率的下降。

    • epoll没有这个限制,可以通过cat /proc/sys/fs/file-max 查看最大具柄的上线,通常这个值跟系统的内存关闭较大。

  • IO效率不回随着FD数目的增加而线性下降。

    • select/poll每次调用都会进行线性扫描全部的集合,导致效率呈线性下降。

    • epoll只会针对活跃的socket进行操作,在内核实现中epoll是根据每个fd上面的callback函数实现的,只有活跃的socket才会去主动调用callback函数,其他idle状态的socket则不会调用。

  • 使用mmap加速内核与用户空间的消息传递

    • 无论哪种模型,都会涉及到将内核的fd传递到用户空间,epoll是通过内核和用户空间mmap同一块内存实现的。
  • epoll的API更简单

Java在1.4之前只支持BIO,1.4新增了NIO,但有很多不足,尤其是文件系统的处理,不支持异步文件读写操作,切文件操作都是同步阻塞的方式,1.7升级为NIO2.0,提供了AIO功能,支持基于文件的异步IO操作,以及针对网络套接字的异步操作。

BIO主要的问题是:一客户端一个线程,因此诞生了伪异步IO模型,但这种方式只是增加了一个线程池,用于维护一个消息队列和线程,虽然可以避免资源耗尽导致的宕机,但由于其底层依然是采用同步阻塞模型,所以无法从根本上解决问题。

伪异步IO弊端:

  • 读取输入流是阻塞的

  • 线程池采用阻塞队列实现,当队列挤满后,后续如队列的操作将被阻塞

  • 前面只有一个accptor线程接收客户端的接入,当线程池的同步阻塞队列阻塞后,新的客户端请求消息将被拒绝,客户端连接超时。

NIO包含:缓冲区Buffer、通道Channel、多路复用器Selector

Java序列化的缺点

  1. 无法跨语言,即别的语言无法进行反序列化操作

  2. 序列化后的码流太大

  3. 序列化性能太低

业界主流的编解码框架

  • Google的Protobuf

    • 特点:

      • 结构化数据存储格式

      • 搞笑的编解码性能

      • 语言无关,平台无关,扩展性好

      • 官方支持java、c++、python三种语言

    • 代码示例

private void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast(new ProtobufVarint32FrameDecoder())
                                    .addLast(new ProtobufVarint32LengthFieldPrepender())
                                    .addLast(new ProtobufEncoder());
                        }
                    });
            ChannelFuture f = b.connect().sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }
  • Facebook的Thrift

  • JBoss的Marshalling框架

    • 可插拔的类解析器

    • 可插拔的对象替换技术,不需要通过继承的方式

    • 可插拔的预定义类缓存表,可以减少序列化的字节数组长度,提升常用类型的对象序列化性能。

    • 无需实现Serializable接口,即可实现java序列化

    • 通过缓存技术提升对象的序列化性能

  • MessagePack

    • 特点:

      • 编解码高效,性能高

      • 序列化之后的码流小

      • 支持跨语言

    • 使用方式

      • 引入maven依赖
<dependency>
    <groupId>org.msgpack</groupId>
    <artifactId>msgpack</artifactId>
    <version>0.6.12</version>
</dependency>
/**
 * MessagePack 编码器
 */
public class MsgPackEncoder extends MessageToByteEncoder<Object> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
        MessagePack pack = new MessagePack();
        byte[] write = pack.write(o);
        channelHandlerContext.write(write);
    }
}
/**
 * MessagePack 解码器
 */
public class MsgPackDecoder extends MessageToMessageDecoder<ByteBuf> {

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        final int length = byteBuf.readableBytes();
        final byte[] array = new byte[length];
        byteBuf.getBytes(byteBuf.readerIndex(), array, 0, length);
        MessagePack pack = new MessagePack();
        list.add(pack.read(array));
    }
}
private void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast("msgpack decoder", new MsgPackDecoder())
                                    .addLast("msgpack encoder", new MsgPackEncoder())
                                    // 以下解决粘包/半包问题
                                    .addLast("frame encoder", new LengthFieldPrepender(2))
                                    .addLast("frame decoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
                        }
                    });
            ChannelFuture f = b.connect().sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }

Netty的架构设计是如何实现高性能的

  1. 采用异步非阻塞的IO类库,基于Reactor模式实现,解决了传统同步阻塞的IO模式下一个服务端无法平滑的处理线性增长的客户端的问题。

  2. TCP接收和发送缓冲区使用直接内存代替堆内存,避免了内存复制,提升了IO读取和写入的性能。

  3. 支持通过内存池的方式循环利用ByteBuf,避免了频繁创建和销毁ByteBuf带来的性能损耗。

  4. 可配置的IO线程数、TCP参数等,为不同的用户场景提供定制化的调优参数,满足不同的性能场景。

  5. 采用环形数组缓冲区实现无锁化并发编程,代替传统的线程安全容器或者锁。

  6. 合理地使用线程安全容器、原子类等,提升系统的并发处理能力。

  7. 关键资源的处理使用单线程串行化的方式,避免多线程并发访问带来的锁竞争和额外的CPU资源消耗问题。

  8. 通过引用计数器及时的申请释放不再被引用的对象,细粒度的内存管理降低了GC的频率,减少了频繁的GC带来的时延增大和CPU损耗。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值