簡體   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