Netty 水位详解

来源:互联网 发布:mac古墓丽影9迅雷下载 编辑:程序博客网 时间:2024/05/16 01:21

Netty 写水位

在启动Netty bootstrap的时候可以设置ChannelOption选项,其中ChannelOption中有一项WRITE_BUFFER_HIGH_WATER_MARK选项和WRITE_BUFFER_LOW_WATER_MARK选项,,此配置写缓冲区(OutbounduBuffer)相关,此配置可以帮助用户监控当前写缓冲区的水位状况,ChannelOutboundBuffer本身是无界的,如果水位控制不当的话就会造成占用大量的内存,今天准备结合代码来看看这个配置究竟是有什么作用。

Option配置

ServerAcceptor在获取新的连接之后,就要执行channel的初始化操作,将handler添加到channel的pipeline上,然后使用Bootstrap的option设置到channel上。

public void channelRead(ChannelHandlerContext ctx, Object msg) {    final Channel child = (Channel) msg;    child.pipeline().addLast(childHandler);    setChannelOptions(child, childOptions, logger); //初始化channel选项    for (Entry<AttributeKey<?>, Object> e: childAttrs) {        child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());    }    ...}

ChannelConfig默认的水位配置为低水位32K,高水位64K,如果用户没有配置就会使用默认配置。

    private volatile WriteBufferWaterMark writeBufferWaterMark = WriteBufferWaterMark.DEFAULT; //设置默认配置

Netty中的channel写数据最终是通过channel的unsafe类实现的,unsafe中有一个ChannelOutboundBuffer属性,每次写操作就是将消息添加到ChannelOutboundBuffer中去,我们看一下ChannelOutboundBuffer的addMessage方法
1、addMessage方法

public void addMessage(Object msg, int size, ChannelPromise promise) {   Entry entry = Entry.newInstance(msg, size, total(msg), promise);   //把消息封装成一个entry,然后塞到一个单链表中   if (tailEntry == null) {       flushedEntry = null;       tailEntry = entry;   } else {       Entry tail = tailEntry;       tail.next = entry;       tailEntry = entry;   }   if (unflushedEntry == null) {       unflushedEntry = entry;   }   incrementPendingOutboundBytes(entry.pendingSize, false); //修改当前缓冲区的水位}

addMessage方法就是将消息封装成Entry然后添加到链表当中,然后更新一下当前buffer的水位。
2、incrementPendingOutboundBytes方法

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); //此处判断如果当前的水位超过了我们之前设置的最高水位,就调用setUnwriteable方法    }}

这里是更新了当前buffer的水位,如果当前的水位高于配置的高水位,那么就要调用setUnwriteable方法
setUnwriteable方法

private void setUnwritable(boolean invokeLater) { //此处调用的时候invokeLater是false    for (;;) {        final int oldValue = unwritable;        final int newValue = oldValue | 1;        if (UNWRITABLE_UPDATER.compareAndSet(this, oldValue, newValue)) { //原子更新一下当前的状态            if (oldValue == 0 && newValue != 0) {                fireChannelWritabilityChanged(invokeLater); //如果之前的状态是可写,现在的状态是不可写,就要调用pipeline上handerd的lWritabilityChanged方法            }            break;        }    }}

高水位的时候就会可以通知到业务handler中的WritabilityChanged方法,并且修改buffer的状态,channel调用isWriteable的时候就会返回false,当前channel处于不可写状态。同样的,低水位应该是缓冲区数据刷到真实channel上的时候用来判断触发事件的。
3、addFlush方法

public void addFlush() {    Entry entry = unflushedEntry;    if (entry != null) {        if (flushedEntry == null) {            flushedEntry = entry;        }        do {            flushed ++;            if (!entry.promise.setUncancellable()) {                int pending = entry.cancel();                decrementPendingOutboundBytes(pending, false, true); //修改当前的水位            }            entry = entry.next;        } while (entry != null);        unflushedEntry = null;    }}

addFlush方法就是把当前的entry链表添加到flushedEntry上,然后开始统计flush的数据的大小,并且相应的修改buffer的水位。
4、decrementPendingOutboundBytes方法

private void decrementPendingOutboundBytes(long size, boolean invokeLater, boolean notifyWritability) {    if (size == 0) {        return;    }    long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, -size);    if (notifyWritability && newWriteBufferSize < channel.config().getWriteBufferLowWaterMark()) {        setWritable(invokeLater);//判断当前水位,如果水位小于    }}

此处修改水位的时候就会判断当前水位是否低于WriteBufferLowWaterMarks,如果低于该水位就会设置当前的channel为可写,然后触发可读时间。

小结

水位配置可以帮助我们监控缓冲区的使用情况,在写数据的时候需要判断当前channel是否可以继续向缓冲区写数据(isWriteable)。在之前的工作中出现过没有正确判断,而使用的编码器默认使用的又是堆外内存,导致在不断写入缓存的时候堆外内存超过jvm配置最大值。