Netty(一)之初识Netty

Netty是一个基于Java的高性能通信框架,适用于分布式系统中提高服务器间通信性能。它采用Reactor模式设计,支持NIO,实现零拷贝和内存池机制,提供高效的序列化和反序列化,以及无锁串行化设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是Netty?

  • 在学习Netty之前,首先要明白Netty是什么?
  • 是什么?不如去搞清楚它是用来干嘛的
  • 干嘛的?来了,Netty是用来提高通信性能的一个开源的基于java的通信框架
  • 想要提高分布式系统下各个服务器之前的通信性能,Netty是一个不错的选择

初识Netty高性能

  • 在IO编程过程中,如果要处理多个请求的时候。可以使用多线程或者IO多路复用技术进行处理
    • java提供Selector选择器可以把多个IO阻塞到同一个select上实现多路复用
  • 那么Netty是怎么做的或者Netty是如何实现高性能的

Netty基本通信架构

  • Netty架构按照Reactor模式来设计实现

  • Reactor模式可以认为是一种设计模式——即反应器模式

  • 服务端通信序列

    • 图片引用自咕泡学院Tom老师的课堂笔记
      在这里插入图片描述
  • 客户端通信序列

    • 图片引用自咕泡学院Tom老师的课堂笔记
      在这里插入图片描述
  • Java NIO通信实现与Netty相比较

    • Java通过选择器Selector实现NIO通信
//  java 实现
private Selector selector;
while(true) {
    this.selector.select();
    // to do...
}
    • Netty中提供了NioEventLoop聚合了Java的Selector
    • 可同时创建多个channel并发处理多个客户端请求
    • 实现了非阻塞 读/写

Netty—零拷贝

  • 通常在进行IO读写的时候,数据需要经过缓冲区才最终到达内存
  • Netty 提供 Direct Buffers 跳过缓冲区,直接使用堆外直接内存进行Socket读写
  • Netty 提供 组合Buffer 对象,可以聚合多个 Buffer 对象进行操作。优于通过内存拷贝合并Buffer
  • Netty 提供 transferTo()方法,直接将文件缓冲区数据发送至目标Channel。优于循环Write()方式
Netty实现零拷贝的源码分析
直接读写
  • 找到Netty的一个类 AbstractNioByteChannel,定位其read()方法

  • 为什么找这个类?————————站在巨人的肩膀上学习

  • 在read()方法中有如下代码
    在这里插入图片描述

  • 通过调用 allocHandle.allocate(allocator) 方法创建了一个 ByteBuf 对象

  • 跟进调用到 DefaultMaxBytesRecvByteBufAllocator 类的 allocate 方法

// 通过 allocate() 方法直接创建 ByteBuf 对象,避免了从堆内存中拷贝对象
public ByteBuf allocate(ByteBufAllocator alloc) {
    return alloc.ioBuffer(this.guess());
}
public int guess() {
    return Math.min(this.individualReadMax, this.bytesToRead);
}
ByteBuf聚合
  • CompositeByteBuf 类提供了ByteBuf聚合的方式,可以理解为一个 ByteBuf 的包装器
    在这里插入图片描述

  • 不需要做内存拷贝的代码体现

  • 注意观察注释 No need to consolidate - just add a component to the list

    • 不需要合并,只需要向列表中添加一个组件
   private int addComponent0(boolean increaseWriterIndex, int cIndex, ByteBuf buffer) {
        assert buffer != null;
        boolean wasAdded = false;
        try {
            checkComponentIndex(cIndex);

            int readableBytes = buffer.readableBytes();

            // No need to consolidate - just add a component to the list.
            @SuppressWarnings("deprecation")
            Component c = new Component(buffer.order(ByteOrder.BIG_ENDIAN).slice());
            if (cIndex == components.size()) {
                wasAdded = components.add(c);
                if (cIndex == 0) {
                    c.endOffset = readableBytes;
                } else {
                    Component prev = components.get(cIndex - 1);
                    c.offset = prev.endOffset;
                    c.endOffset = c.offset + readableBytes;
                }
            } else {
                components.add(cIndex, c);
                wasAdded = true;
                if (readableBytes != 0) {
                    updateComponentOffsets(cIndex);
                }
            }
            if (increaseWriterIndex) {
                writerIndex(writerIndex() + buffer.readableBytes());
            }
            return cIndex;
        } finally {
            if (!wasAdded) {
                buffer.release();
            }
        }
    }
文件传输零拷贝
  • 直接看 transferTo 方法,这里该方法属于Netty,不属于Spring
//    DefaultFileRegion.java
long written = file.transferTo(this.position + position, count, target);

//  抽象方法  transferTo  来自 FileChannel.java
public abstract long transferTo(long position, long count,WritableByteChannel target) throws IOException;
  • 该方法有一段 API DOC是这样的
    在这里插入图片描述

  • 从该通道中向指定的可写通道写入数据,不存在拷贝操作

Netty—内存池

  • Netty支持多种内存管理策略,实现了基于内存池的缓冲区重用机制。避免了不必要的回收操作。
  • Netty 的ByteBuf类提供了很多实现,通过在相关配置可以实现差异化定制
    在这里插入图片描述
  • 通过效率对比,Netty很牛
public class NettyPool {
    private static int loop = 1800000;
    private final byte[] CONTENT = new byte[1024];
    public void usePool() {
        long startTime = System.currentTimeMillis();
        ByteBuf poolBuffer = null;
        for (int i = 0; i < loop; i++) {
            poolBuffer = PooledByteBufAllocator.DEFAULT.directBuffer(1024);
            poolBuffer.writeBytes(CONTENT);
            poolBuffer.release();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("使用内存池耗时" + (endTime - startTime) + "ms.");
    }
    public void unUsePool() {
        long startTime = System.currentTimeMillis();
        ByteBuf buffer = null;
        for (int i = 0; i < loop; i++) {
            buffer = Unpooled.directBuffer(1024);
            buffer.writeBytes(CONTENT);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("不使用内存池耗时" + (endTime - startTime) + "ms.");
    }
}
//运行结果
使用内存池耗时499ms.
不使用内存池耗时1409ms.
源码分析学习
  • 首先是 directBuffer 方法 AbstractByteBufAllocator类
public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
    if (initialCapacity == 0 && maxCapacity == 0) {
        return emptyBuf;
    }
    validate(initialCapacity, maxCapacity);
    return newDirectBuffer(initialCapacity, maxCapacity);
}
  • newDirectBuffer方法
    在这里插入图片描述

  • 跳转到 PooledByteBufAllocator 类

protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
    PoolThreadCache cache = threadCache.get();
    //  从 cache  中获取 PoolArena 内存区域
    PoolArena<ByteBuffer> directArena = cache.directArena;

    ByteBuf buf;
    if (directArena != null) {
        buf = directArena.allocate(cache, initialCapacity, maxCapacity);
    } else {
        if (PlatformDependent.hasUnsafe()) {
            buf = UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
        } else {
            buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
        }
    }
    return toLeakAwareBuffer(buf);
}
  • PoolArena 类
PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
    PooledByteBuf<T> buf = newByteBuf(maxCapacity);
    allocate(cache, buf, reqCapacity);
    return buf;
}
  • newByteBuf 实现
    在这里插入图片描述

  • DirectArena.java 顾名思义,直接内存区域

protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) {
    if (HAS_UNSAFE) {
        return PooledUnsafeDirectByteBuf.newInstance(maxCapacity);
    } else {
        return PooledDirectByteBuf.newInstance(maxCapacity);
    }
}
//  到这里可以发现,使用内存池,之后其实就是循环调用,体现在 RECYCLER.get()方法
static PooledDirectByteBuf newInstance(int maxCapacity) {
    PooledDirectByteBuf buf = RECYCLER.get();
    buf.reuse(maxCapacity);
    return buf;
}

Netty—高效Reactor线程模型

  • Reactor单线程模型

    • 图片引用自咕泡学院Tom老师的课堂笔记
      在这里插入图片描述
  • Reactor多线程模型提供一组NIO线程处理IO操作

    • 图片引用自咕泡学院Tom老师的课堂笔记
      在这里插入图片描述
  • Reactor模型的特点

    • 单独提供一个使用NIO线程实现的Acceptor线程用于监听服务端,接收客户端的TCP连接请求;
    • 由一个单独的NIO线程池负责IO操作、消息的读取、解码、编码和发送
    • 一个NIO线程可同时处理N条链路,但是一个链路只能对应一个NIO线程,防止发生并发操作问题
主从Reactor模型
  • 一个NIO线程负责监听和处理所有客户端连接,当并发连接数量过大,性能堪忧

  • 使用主从Reactor模型

    • 服务端用于接收请求的也是一个独立的NIO线程池

    • Acceptor仅用于处理客户端登陆,认证等操作

    • 认证完成后,将会创建新的SocketChannel注册到IO线程池(sub reactor)中某个线程

    • 由该线程完成SocketChannel的读写和编码

    • 图片引用自咕泡学院Tom老师的课堂笔记

在这里插入图片描述

  • 可以通过创建不同的EventLoopGroup实例和参数配置灵活配置Reactor线程模型

Netty—无锁串行化

  • 通常我们的认知中单线程串行化执行是一种效率不高的方式

  • Netty采用穿行无锁化设计配合NIO线程池的参数设置

  • 可同时启动多个串行化的线程并同时运行—避免了并发场景下的锁竞争

  • 图片引用自咕泡学院Tom老师的课堂笔记
    在这里插入图片描述

  • 从上图可以发现从NioEventLoop读取到消息之后,直到返回,都是采用单线程执行

  • 避免了锁竞争

总结:Netty—高效的并发编程

  • 回归本质,Netty高效并发编程
  • 其实也就是灵活并正确使用了volatile、CAS和原子类。通过源码学习可探知其冰山一角
  • 从Netty提供的NIO模型中可以学习到其对线程容器的高明使用
  • 合理使用读写锁提供IO操作性能
  • 同时,Netty提供了对 Google Protobuf (高性能序列化框架)的支持
  • Netty扩展接口也可实现其它高性能序列化框架
    • 不同序列化和反序列化性能对比

​ 图片引用自咕泡学院Tom老师的课堂笔记
在这里插入图片描述

  • 在Netty启动辅助类中可以灵活配置TCP参数,能够满足各种不同的业务场景
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值