Apache Avro 能够反序列化为不正确的类型

问题描述 投票:0回答:1

在我的公司,我们在 Kafka 工作流程中使用 Avro。在这个工作流程中,我们最终使用二进制序列化/反序列化来提醒我们的 API 对象是成功的还是失败的。我们发现,在特定数据条件下,Avro 序列化对象能够反序列化为错误的对象类型。在我们的场景中,我们有一个序列化的成功对象,它能够反序列化为失败的对象类型。这会导致我们的 API 出现问题,因为反序列化的类型不正确。

我对使用 avro 比较陌生,在阅读here 的文档时,这似乎是他们的二进制序列化操作方式的结果。

我编写了一个能够重现此问题的示例 dotnet 6 控制台应用程序。

套餐:

  • Apache.Avro 1.11.0
  • Apache.Avro.Tools 1.11.1

Avro 模式

{
  "type": "record",
  "namespace": "BinarySerializationTests.Schema",
  "name": "FailedObject",
  "fields": [

    {
      "name": "Id",
      "doc": "Identifier",
      "type": "string"
    },
    {
      "name": "ErrorCode",
      "doc": "The failure's error code",
      "type": "string"
    },
    {
      "name": "Errors",
      "doc": "Collection of all errors",
      "type": {
        "type": "array",
        "items": "string"
      }
    }
  ]
}
{  
  "type": "record",
  "namespace": "BinarySerializationTests.Schema",
  "name": "SuccessObject",
  "fields": [
    {
      "name": "Id",
      "type": "string"
    },
    {
      "name": "RequiredProperty1",
      "type": "string"
    },
    {
      "name": "RequiredProperty2",
      "type": "string"
    },
    {
      "name": "FirstName",
      "type": "string"
    },
    {
      "name": "LastName",
      "type": "string"
    },
    {
      "name": "Suffix",
      "type": [ "null", "string" ]
    },
    {
      "name": "ExtraProperty1",
      "type": [ "null", "string" ]
    },
    {
      "name": "ExtraProperty2",
      "type": [ "null", "string" ]
    },
    {
      "name": "ExtraProperty3",
      "type": [
        "null",
        {
          "type": "int",
          "logicalType": "date"
        }
      ]
    },
    {
      "name": "Email",
      "type": "string"
    },
    {
      "name": "CellPhone",
      "type": [ "null", "string" ]
    },
    {
      "name": "ExtraProperty4",
      "type": [ "null", "string" ]
    },
    {
      "name": "ExtraProperty5",
      "type": [ "null", "string" ]
    },
    {
      "name": "ExtraProperty6",
      "type": [ "null", "string" ]
    },
    {
      "name": "ExtraProperty7",
      "type": [ "null", "string" ]
    },
    {
      "name": "Address1",
      "type": [ "null", "string" ]
    },
    {
      "name": "Address2",
      "type": [ "null", "string" ]
    },
    {
      "name": "City",
      "type": [ "null", "string" ]
    },
    {
      "name": "State",
      "type": [ "null", "string" ]
    },
    {
      "name": "Zip",
      "type": [ "null", "string" ]
    },
    {
      "name": "OfficePhone",
      "type": [ "null", "string" ]
    },
    {
      "name": "ExtraProperty8",
      "type": [ "null", "string" ]
    },
    {
      "name": "TimeZone",
      "type": "string"
    },
    {
      "name": "ExtraProperty9",
      "type": [ "null", "string" ]
    }
  ]
}

C# 类是使用 avrogen 工具 生成的。如果需要,我可以提供,它们只是从上面的模式生成的。

测试控制台代码:

using Avro.IO;
using Avro.Specific;
using BinarySerializationTests.Schema;

static byte[] Serialize<T>(T thisObj) where T : ISpecificRecord
{
    using (MemoryStream memoryStream = new MemoryStream())
    {
        BinaryEncoder binaryEncoder = new BinaryEncoder((Stream)memoryStream);
        new SpecificDefaultWriter(thisObj.Schema).Write<T>(thisObj, (Encoder)binaryEncoder);
        return memoryStream.ToArray();
    }
}

static T Deserialize<T>(byte[] bytes) where T : ISpecificRecord, new()
{
    using (MemoryStream memoryStream = new MemoryStream(bytes))
    {
        BinaryDecoder binaryDecoder = new BinaryDecoder((Stream)memoryStream);
        T thisObj = new T();
        new SpecificDefaultReader(thisObj.Schema, thisObj.Schema).Read<T>(thisObj, (Decoder)binaryDecoder);
        return thisObj;
    }
}

static bool TryDeserialize<T>(byte[] bytes, out T obj) where T : ISpecificRecord, new()
{
    obj = default(T);
    try
    {
        obj = Deserialize<T>(bytes);
        return true;
    }
    catch (Exception ex)
    {
        return false;
    }
}

var success = new SuccessObject
{
    Id = Guid.NewGuid().ToString(),
    RequiredProperty1 = "RD",
    RequiredProperty2 = "400000",
    FirstName = "FirstName",
    LastName = "LastN",
    Suffix = "ABC",
    ExtraProperty1 = null,
    ExtraProperty2 = null,
    Email = "[email protected]",
    CellPhone = "",
    ExtraProperty4 = null,
    ExtraProperty5 = null,
    ExtraProperty6 = "",
    ExtraProperty7 = "",
    Address1 = "1 Wilson Road",
    Address2 = "",
    City = "Annapolis",
    State = "MD",
    Zip = "21402-0001",
    OfficePhone = "",
    ExtraProperty8 = "random",
    TimeZone = "Eastern Standard Time",
    ExtraProperty9 = null
};

var serializedSuccess = Serialize(success);

if (TryDeserialize(serializedSuccess, out FailedObject failedObject))
{
    Console.WriteLine("Deserialized as FailedObject");
}

if (TryDeserialize(serializedSuccess, out SuccessObject successObject))
{
    Console.WriteLine("Deserialized SuccessObject");
}

我发现上面的“SuccessObject”类型的数据对于什么会导致它被反序列化为“FailedObject”类型非常挑剔。

例如,如果我将属性“后缀”从“ABC”更改为“A”,它将不再反序列化为“FailedObject”类型。

或者,如果我将属性“RequiredProperty2”从“400000”更改为“500000”,它将不再反序列化为“FailedObject”类型。

当它确实将“SuccessObject”反序列化为“FailedObject”类型时,反序列化是根据我认为与 Avro 文档匹配的对象中的顺序将值放入属性中。

例如:

有人知道为什么会出现这个问题吗?或者如果有人有任何潜在的解决方法?

我能想到的唯一解决方案是将类型组合成一个“响应”类型,其中包含一个“成功”布尔值来确定成功或失败。

c# serialization deserialization avro
1个回答
0
投票

除非有人有更好的答案,否则我们使用以下解决方案。

由于 Avro 二进制编码 不包含属性名称信息,只要类型匹配,它只是按顺序分配值。在我们上面的示例中,这意味着我们拥有的列表能够在列表中填充属性,直到它到达第一个不是字符串的属性(ExtraProperty3)。

所以更好地理解这一点,我们的解决方案不是将两个可能的模式作为结果,而是将它们组合成一个模式,该模式将告诉我们它是否成功/失败以及任何相关数据。这样我们只会将二进制数据反序列化为一种类型。

© www.soinside.com 2019 - 2024. All rights reserved.