前言
前面介绍了EventLoopGroup和ChannelPipeline的源码,这一期ChannelOutboundBuffer出站缓存。这篇文章结束后,有关于Channel周边的组建就分析完成,将这些实现类组合在一起,就形成了完整的netty处理流程。
正文
ChannelOutboundBuffer是Netty的发送缓存,当Netty调用write时数据不会真正的去发送而是写入到ChannelOutboundBuffer中,这样做的目的是为了减少TCP缓存的压力,提高系统吞吐率。
成员
ChannelOutboundBuffer绑定了一个Channel,并且内部有3个Entry他们之间的关系是:
- fulshedEntry到unflushedEntry:是待发送的数据
- unflushedEntry到tailEntry:是暂存还没有准备好发送的数据
看一下Entry源码
Entry使用了之前介绍的对象回收池,由于系统会不断发送数据从而创建很多Entry,重复利用Entry对象减少GC压力。msg存放实际对象,将对象的信息一并存储后续调用。
public void addMessage(Object msg, int size, ChannelPromise promise) {
Entry entry = Entry.newInstance(msg, size, total(msg), promise);
//队列为空
if (tailEntry == null) {
flushedEntry = null;
} else {
Entry tail = tailEntry;
tail.next = entry;
}
//添加给尾节点
tailEntry = entry;
if (unflushedEntry == null) {
unflushedEntry = entry;
}
incrementPendingOutboundBytes(entry.pendingSize, false);
}
调用addMessage方法会创建一个节点,然后把它加入到Entry队列的尾部,然后跟待外发的字节数量
private void incrementPendingOutboundBytes(long size, boolean invokeLater) {
if (size == 0) {
return;
}
long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, size);
if (newWriteBufferSize > channel.config().getWriteBufferHighWaterMark()) {
setUnwritable(invokeLater);
}
}
private void setUnwritable(boolean invokeLater) {
for (;;) {
final int oldValue = unwritable;
final int newValue = oldValue | 1;
if (UNWRITABLE_UPDATER.compareAndSet(this, oldValue, newValue)) {
if (oldValue == 0) {
fireChannelWritabilityChanged(invokeLater);
}
break;
}
}
}
private void fireChannelWritabilityChanged(boolean invokeLater) {
final ChannelPipeline pipeline = channel.pipeline();
//是否延迟
if (invokeLater) {
Runnable task = fireChannelWritabilityChangedTask;
if (task == null) {
fireChannelWritabilityChangedTask = task = new Runnable() {
@Override
public void run() {
pipeline.fireChannelWritabilityChanged();
}
};
}
channel.eventLoop().execute(task);
} else {
pipeline.fireChannelWritabilityChanged();
}
}
因为出站缓存是可以无限制添加的,所以可以配置一个HighWaterMark来做一定的限制,当待发量累积超过的时候,会将这个缓存改为不可写状态,并且调用pipeline的fireChannelWritabilityChanged方法来发送一个通知,通知每一个Handler这个事件以便自行处理。
public void addFlush() {
//暂存数据
Entry entry = unflushedEntry;
if (entry != null) {
if (flushedEntry == null) {
//还没有flush条目 将flushed指向unflushedEntry
flushedEntry = entry;
}
do {
flushed ++;
//锁定当前字段 防止取消
if (!entry.promise.setUncancellable()) {
//锁定失败自动取消
int pending = entry.cancel();
decrementPendingOutboundBytes(pending, false, true);
}
entry = entry.next;
} while (entry != null);
// All flushed so reset unflushedEntry
unflushedEntry = null;
}
}
前面已知当系统调用write的时候会调用addMessage添加一个节点到缓存,当系统调用flush的时候会调用addFlush,将unflushedEntry以及以后的节点改为待发送节点,在这过程中会将节点进行锁定,如果锁定过程中已经取消,则将节点回收并且减少出站字节量。
nioBuffers是调用addFlush后调用,将待发送的数据转换为原生的ByteBuffer数组
ByteBuffer数组存放于FastThreadLocal,经过之前的介绍,通过Map的get方法,保证了不同的线程取出的实例都是不同的。
步骤如下:
- 从flushedEntry节点开始,msg类型必须是ByteBuf,直到遍历到unflushedEntry或者累计超过maxBytes停止
- 跳过被关闭的节点
- 计算出需要的空间,如果nioBuffers空间不够需要扩容,每次扩容为之前的两倍,直到大于需要的空间。
- 将ByteBuf转换为nioBuffers,如果entry的count大于1,则使用循环遍历放入nioBffers
当调用nioBuffers后获取到了待发送信息,当消息发送后应该删除掉已发送的信息,这个时应调用removeBytes方法
通过current获取flushedEntry节点,然后判断是否可以删除或者增加readerIndex来减少待发送数据大小,中间会调用progress方法来给Promise通知当前的发送情况。
remove方法会调用removeEntry来删除节点,并且减少总量,最后回收entry对象
将链表的头往后设置
总结
ChannelOutboundBuffer提供了一个出站缓存队列,当有新消息加入的时候会放在队列尾部,最后将消息批量转换为ByteBuffer数组返回,提高了系统吞吐率。同时ChannelOutboundBuffer没有限制添加容量,而是设置了一个水位线来预防极端情况导致的OOM,利用pipeline的ChannelWritabilityChanged方法,当容量超过水位线的时候会通知,用户可以自定义handler来接受该消息同时自定策略。