什么是Netty?
- 在学习Netty之前,首先要明白Netty是什么?
- 是什么?不如去搞清楚它是用来干嘛的
- 干嘛的?来了,Netty是用来提高通信性能的一个开源的基于java的通信框架
- 想要提高分布式系统下各个服务器之前的通信性能,Netty是一个不错的选择
初识Netty高性能
- 在IO编程过程中,如果要处理多个请求的时候。可以使用多线程或者IO多路复用技术进行处理
- java提供Selector选择器可以把多个IO阻塞到同一个select上实现多路复用
- 那么Netty是怎么做的或者Netty是如何实现高性能的
Netty基本通信架构
-
Netty架构按照Reactor模式来设计实现
-
Reactor模式可以认为是一种设计模式——即反应器模式
-
服务端通信序列
- 图片引用自咕泡学院Tom老师的课堂笔记
- 图片引用自咕泡学院Tom老师的课堂笔记
-
客户端通信序列
- 图片引用自咕泡学院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老师的课堂笔记
- 图片引用自咕泡学院Tom老师的课堂笔记
-
Reactor多线程模型提供一组NIO线程处理IO操作
- 图片引用自咕泡学院Tom老师的课堂笔记
- 图片引用自咕泡学院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参数,能够满足各种不同的业务场景