簡體   English   中英

Avro 消息 - InvalidNumberEncodingException 反序列化邏輯類型日期

[英]Avro Message - InvalidNumberEncodingException deserializing logicalType date

當我反序列化具有定義為邏輯類型日期的字段的消息時出現異常。 作為文檔,該字段定義為:

{"name": "startDate", "type": {"type": "int", "logicalType": "date"}}

我使用“avro-maven-plugin”(1.9.2)生成 java 類,我可以將字段 startDate 設置為java.time.LocalDate.now() avro object 將消息序列化並將其發送到 kafka 主題。 到目前為止,一切都很好。

但是,當我閱讀該消息時,我得到了異常:

Caused by: org.apache.avro.InvalidNumberEncodingException: Invalid int encoding
    at org.apache.avro.io.BinaryDecoder.readInt(BinaryDecoder.java:166)
    at org.apache.avro.io.ValidatingDecoder.readInt(ValidatingDecoder.java:83)
    at org.apache.avro.generic.GenericDatumReader.readInt(GenericDatumReader.java:551)
    at org.apache.avro.generic.GenericDatumReader.readWithoutConversion(GenericDatumReader.java:195)
    at org.apache.avro.generic.GenericDatumReader.readWithConversion(GenericDatumReader.java:173)
    at org.apache.avro.specific.SpecificDatumReader.readField(SpecificDatumReader.java:134)
    at org.apache.avro.generic.GenericDatumReader.readRecord(GenericDatumReader.java:247)
    at org.apache.avro.specific.SpecificDatumReader.readRecord(SpecificDatumReader.java:123)
    at org.apache.avro.generic.GenericDatumReader.readWithoutConversion(GenericDatumReader.java:179)
    at org.apache.avro.generic.GenericDatumReader.read(GenericDatumReader.java:160)
    at org.apache.avro.generic.GenericDatumReader.read(GenericDatumReader.java:153)

讓一切變得更加奇怪的是,如果我設置一個不同的日期,比如LocalDate.of(1970, 1, 1) ,就不會發生錯誤。

換句話說,如果表示自 01/01/1970 以來的天數的序列化 int 值足夠小,則一切正常。 在查看引發異常的代碼后,我嘗試了該測試,這讓我認為如果 int day 低於 127,則可以避免錯誤:

   public int readInt() throws IOException {
        this.ensureBounds(5);
        int len = 1;
        int b = this.buf[this.pos] & 255;
        int n = b & 127;
        if (b > 127) {
            b = this.buf[this.pos + len++] & 255;
            n ^= (b & 127) << 7;
            if (b > 127) {
                b = this.buf[this.pos + len++] & 255;
                n ^= (b & 127) << 14;
                if (b > 127) {
                    b = this.buf[this.pos + len++] & 255;
                    n ^= (b & 127) << 21;
                    if (b > 127) {
                        b = this.buf[this.pos + len++] & 255;
                        n ^= (b & 127) << 28;
                        if (b > 127) {
                            throw new InvalidNumberEncodingException("Invalid int encoding");
                        }
                    }
                }
            }
        }
....

當然,我不能只在生產中使用接近 01/01/1970 的日期。 歡迎任何幫助:-)

TL:博士

您發布的代碼不僅可以反序列化最多 127 的數字,還可以反序列化 Java int的全部范圍,因此對應於 1970 年之后幾百萬年的日期最多可達幾十億。

細節

Apache Avro 中的BinaryDecoder.readInt方法將 1 到 5 個字節反序列化為 Java int 它使用int每個字節的最后 7 位,而不是符號位。 相反,符號位用於確定要讀取的字節數。 符號位 0 表示這是最后一個字節。 符號位 1 表示在此之后還有更多字節。 如果讀取了 5 個字節並且它們的符號位設置為 1,則會引發異常。5 個字節可以提供 35 位,而int可以容納 32 位,因此將超過 5 個字節視為錯誤是公平的。

因此,從您發布的代碼中,沒有我合理期望在應用程序中使用的日期會造成任何問題。

測試代碼

我將您的方法放在TestBinaryDecoder class 中進行嘗試(最后的完整代碼)。 讓我們首先看看異常是如何來自 5 個字節的符號位都設置為 1 的:

    try {
        System.out.println(new TestBinaryDecoder(-1, -1, -1, -1, -1).readInt());
    } catch (IOException ioe) {
        System.out.println(ioe);
    }

Output:

 ovv.so.binary.misc.InvalidNumberEncodingException: Invalid int encoding

同樣如您所說, 127 沒有問題:

    System.out.println(new TestBinaryDecoder(127, -1, -1, -1, -1).readInt());
 127

有趣的部分是當我們將更多字節用於保存我們想要的int位時。 這里第一個字節的符號位為 1,下一個字節為 0,所以我希望使用這兩個字節:

    System.out.println(new TestBinaryDecoder(255, 127, -1, -1, -1).readInt());
 16383

我們已經接近今天所需的人數。 今天是我所在時區的 2021 年 6 月 4 日,紀元之后的第 18782 天,或者二進制:100100101011110。所以讓我們嘗試將這 15 個二進制數字放入解碼器的三個字節中:

    int epochDay = new TestBinaryDecoder(0b11011110, 0b10010010, 0b1, -1, -1).readInt();
    System.out.println(epochDay);
    System.out.println(LocalDate.ofEpochDay(epochDay));
 18782 2021-06-04

所以你是怎么得到你的例外的,我不知道。 源肯定不僅僅是一個大的int值。 問題一定出在其他地方。

完整代碼

public class TestBinaryDecoder {
    
    private byte[] buf;
    private int pos;
    
    /** Convenience constructor */
    public TestBinaryDecoder(int... buf) {
        this(toByteArray(buf));
    }
    
    private static byte[] toByteArray(int[] intArray) {
        byte[] byteArray = new byte[intArray.length];
        IntStream.range(0, intArray.length).forEach(ix -> byteArray[ix] = (byte) intArray[ix]);
        return byteArray;
    }

    public TestBinaryDecoder(byte[] buf) {
        this.buf = buf;
        pos = 0;
    }

    public int readInt() throws IOException {
        this.ensureBounds(5);
        int len = 1;
        int b = this.buf[this.pos] & 255;
        int n = b & 127;
        if (b > 127) {
            b = this.buf[this.pos + len++] & 255;
            n ^= (b & 127) << 7;
            if (b > 127) {
                b = this.buf[this.pos + len++] & 255;
                n ^= (b & 127) << 14;
                if (b > 127) {
                    b = this.buf[this.pos + len++] & 255;
                    n ^= (b & 127) << 21;
                    if (b > 127) {
                        b = this.buf[this.pos + len++] & 255;
                        n ^= (b & 127) << 28;
                        if (b > 127) {
                            throw new InvalidNumberEncodingException("Invalid int encoding");
                        }
                    }
                }
            }
        }
        return n;
    }
    
    private void ensureBounds(int bounds) {
        System.out.println("Ensuring bounds " + bounds);
    }

}

class InvalidNumberEncodingException extends IOException {

    public InvalidNumberEncodingException(String message) {
        super(message);
    }
    
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM