繁体   English   中英

如何有效地将入站netty.io ByteBuf消息分发到ChannelGroup?

[英]How to efficiently distribute inbound netty.io ByteBuf messages to a ChannelGroup?

我创建了一个netty.io BootStrap,它从旧服务器接收流数据。 服务器使用ISO-8859-1字符集发送数据。 内部还有一个使用不同分隔符字节的内部“协议”:

private static final byte GS = 29;  // FS ASCII char 1D (group separator)
private static final byte RS = 30;  // FS ASCII char 1E (record separator)
private static final byte US = 31;  // FS ASCII char 1F (unit separator)

private static final ByteProcessor GROUP_SEPARATOR_LOCATOR = value -> value != GS;
private static final ByteProcessor RECORD_SEPARATOR_LOCATOR = value -> value != RS;
private static final ByteProcessor UNIT_SEPARATOR_LOCATOR = value -> value != US;

这些ByteProcessor实例用于拆分消息。 最终,每条消息都转换为其对应的对象表示形式,而keyValueMapping包含原始消息中的主要内容:

public class Update {
    private final String id;    
    private final UpdateType updateType;
    private final Map<String, byte[]> keyValueMapping;
    // SOME OTHER STUFF
}

随后,所有更新都转发到所有连接的Web套接字客户端,这些客户端由单独的ServerBootStrap处理:

public void distribute(ChannelGroup recipients, Object msg) {
    Update updateMsg = (Update) msg;
    recipients.writeAndFlush(updateMsg);
}

当我激活Java Flight Recording并执行一些负载测试时,我意识到主要的分配热点是将初始入站消息中的值转换为ISO-8859-1字节数组的方法:

private byte[] translateValue(ByteBuf in) {
    byte [] result;

    if (!in.hasArray()) {
        result = new byte[in.readableBytes()];
        in.getBytes(in.readerIndex(), result);
    } else {
        result = in.array();
    }
    return result;
}

最初,我没有翻译ByteBuf,而是直接将它们存储在Update的keyValueMapping映射中。 由于ByteBuf对象维护着一些不受保护的内部索引(读取器,写入器,标记等)-通过设计,我担心只将这些ByteBufs包装并转发到不同的通道(请参见上面的收件人channelGroup),并决定使用此方法。用byte []表示。

检查Java Flight Recording的结果, 我想知道是否有任何建议如何将未更改的入站数据分配到一组不同的通道而又不会过多地限制GC? 从结果中学到,直接缓冲区被用于给定的通道,因为创建了许多新的字节数组。

为了提供更多的上下文,我还添加了执行剩余消息翻译的代码:

while (in.readableBytes() > 0) {
    ByteBuf keyAsByteBuf = nextToken(in, UNIT_SEPARATOR_LOCATOR);
    String key = translateKey(keyAsByteBuf);

    if (key != null) {
        ByteBuf valueAsByteBuf = nextToken(in, RECORD_SEPARATOR_LOCATOR);
        byte[] value = translateValue(valueAsByteBuf);

        if (value.length > 0) {
            mapping.put(key, value); 
        }
    }
}

private ByteBuf nextToken(ByteBuf in, ByteProcessor locator) {
    int separatorIdx = in.forEachByte(in.readerIndex(), in.readableBytes(), locator);

    if (separatorIdx >= 0) {
        ByteBuf token = in.readSlice(separatorIdx - in.readerIndex());
        in.skipBytes(1);
        return token;
    }
    return in.readSlice(in.readableBytes());
}

private String translateKey(ByteBuf in) {
    return keyTranslator.translate(in);
}

嗯...实际上,您的问题不是那么简单。 我将简要回答。

如果您的应用程序不需要,则无需将ByteBuf转换为byte[] 因此,我假设您具有下一个结构:

public class Update {
    private final String id;    
    private final UpdateType updateType;
    private final Map<String, ByteBuf> keyValueMapping;
}

这里的问题是您部分解析了ByteBuf 因此,您在此Java对象中具有Java对象+ ByteBuf

很好,您可以进一步使用ByteBuf's进行操作。 您的类Update应该实现ReferenceCounted接口。 因此,当您执行recipients.writeAndFlush(updateMsg) (假设接收者是DefaultChannelGroup )时,netty DefaultChannelGroup将处理对这些缓冲区的引用。

那么会发生什么:

recipients.writeAndFlush(updateMsg)DefaultChannelGroup循环使用channel.writeAndFlush(safeDuplicate(message))将您的updateMsg发送到列表中的每个通道。 safeDuplicate是一种特殊方法,用于处理对ByteBuf引用,因此您可以将同一缓冲区发送给多个接收者(它实际上是使用ByteBuf retainedDuplicate()复制缓冲区)。 但是,您的对象不是ByteBuf ,而是java对象。 这是该方法的代码:

private static Object safeDuplicate(Object message) {
    if (message instanceof ByteBuf) {
        return ((ByteBuf) message).retainedDuplicate();
    } else if (message instanceof ByteBufHolder) {
        return ((ByteBufHolder) message).retainedDuplicate();
    } else {
        return ReferenceCountUtil.retain(message);
    }
}

因此,为了正确处理ByteBuf引用,您需要为ReferenceCountUtil.retain(message)实现ReferenceCounted 像这样:

public class Update implements ReferenceCounted {
    @Override
    public final Update retain() {
        return new Update(id, updateType, makeRetainedBuffers());
    }  

    private Map makeRetainedBuffers() {
       Map newMap = new HashMap();
       for (Entry entry : keyValueMapping) {
           newMap.put(entry.key, entry.value.duplicate().retain())
       }
       return newMap;
    }
}

这只是一个伪代码。 但是你应该明白这个想法。 您还必须在Update类中实现release()方法,并确保它始终释放其持有的缓冲区。 并释放其中的所有缓冲区。 我假设您已经在管道中为该Update类调用了release()编码器。

另一种选择是实现自己的DefaultChannelGroup 在这种情况下,您不必依赖safeDuplicate方法。 因此,您不需要实现ReferenceCounted ,但是仍然需要处理该类中的保留,手动释放。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM