欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

如何为asp.net core添加protobuf支持详解

程序员文章站 2022-04-08 11:25:02
前言 在一些性能要求很高的应用中,使用protocol buffer序列化,优于json。而且protocol buffer向后兼容的能力比较好。 由于asp.net...

前言

在一些性能要求很高的应用中,使用protocol buffer序列化,优于json。而且protocol buffer向后兼容的能力比较好。

由于asp.net core 采用了全新的middleware方式,因此使用protobuf序列化,只需要使用protobuf-net修饰需要序列化的对象,并在mvc初始化的时候增加相应的formatter就可以了。

没时间解释了,快上车。

通过nuget获取zaabee.aspnetcoreprotobuf

install-package zaabee.aspnetcoreprotobuf

在startup.cs文件中修改configureservices方法

public void configureservices(iservicecollection services)
{
  services.addmvc(options => { options.addprotobufsupport(); });
}

搞掂……这时候你就可以通过application/x-protobuf的content-type来让asp.net core使用protobuf来进行序列化/反序列化。

测试代码

在asp.net core项目中添加以下dto

[protocontract]
public class testdto
{
  [protomember(1)] public guid id { get; set; }
  [protomember(2)] public string name { get; set; }
  [protomember(3)] public datetime createtime { get; set; }
  [protomember(4)] public list<testdto> kids { get; set; }
  [protomember(5)] public long tag { get; set; }
  [protomember(6)] public testenum enum { get; set; }
}

public enum testenum
{
  apple,
  banana,
  pear
}

新建一个xunit项目,通过nuget引用microsoft.aspnetcore.testhost,建立一个测试类

public class aspnetcoreprotobuftest
{
  private readonly testserver _server;
  private readonly httpclient _client;

  public aspnetcoreprotobuftest()
  {
    _server = new testserver(
      new webhostbuilder()
        .usekestrel()
        .usestartup<startup>());
    _client = _server.createclient();
  }

  [fact]
  public void test()
  {
    // http post with protobuf response body
    _client.defaultrequestheaders.accept.add(new mediatypewithqualityheadervalue("application/x-protobuf"));

    var dtos = getdtos();
    var stream = new memorystream();
    protobuf.serializer.serialize(stream, dtos);

    httpcontent httpcontent = new streamcontent(stream);

    // http post with protobuf request body
    var responseforpost = _client.postasync("api/values", httpcontent);

    var result = protobuf.serializer.deserialize<list<testdto>>(
      responseforpost.result.content.readasstreamasync().result);

    assert.true(comparedtos(dtos,result));
  }

  private static bool comparedtos(list<testdto> lstone, list<testdto> lsttwo)
  {
    lstone = lstone ?? new list<testdto>();
    lsttwo = lsttwo ?? new list<testdto>();

    if (lstone.count != lsttwo.count) return false;

    for (var i = 0; i < lstone.count; i++)
    {
      var dtoone = lstone[i];
      var dtotwo = lsttwo[i];
      if (dtoone.id != dtotwo.id || dtoone.createtime != dtotwo.createtime || dtoone.enum != dtotwo.enum ||
        dtoone.name != dtotwo.name || dtoone.tag != dtotwo.tag || !comparedtos(dtoone.kids, dtotwo.kids))
        return false;
    }

    return true;
  }

  private static list<testdto> getdtos()
  {
    return new list<testdto>
    {
      new testdto
      {
        id = guid.newguid(),
        tag = long.maxvalue,
        createtime = datetime.now,
        name = "0",
        enum = testenum.apple,
        kids = new list<testdto>
        {
          new testdto
          {
            id = guid.newguid(),
            tag = long.maxvalue - 1,
            createtime = datetime.now,
            name = "00",
            enum = testenum.banana
          },
          new testdto
          {
            id = guid.newguid(),
            tag = long.maxvalue - 2,
            createtime = datetime.now,
            name = "01",
            enum = testenum.pear
          }
        }
      },
      new testdto
      {
        id = guid.newguid(),
        tag = long.maxvalue - 3,
        createtime = datetime.now,
        name = "1",
        enum = testenum.apple,
        kids = new list<testdto>
        {
          new testdto
          {
            id = guid.newguid(),
            tag = long.maxvalue - 4,
            createtime = datetime.now,
            name = "10",
            enum = testenum.banana
          },
          new testdto
          {
            id = guid.newguid(),
            tag = long.maxvalue - 5,
            createtime = datetime.now,
            name = "11",
            enum = testenum.pear
          }
        }
      }
    };
  }
}

为什么要用protobuf?

因为快……在我们这边使用业务数据的测试中,protobuf的序列化/反序列化性能大概是json.net的三倍,序列化后的体积大概只有json的二分之一,这可以在相当程度上提高webapi的吞吐性能。

另外就是json对于浮点数的处理存在精度丢失,因为js的number类型的安全整数是53位。当我们使用雪花算法来提供全局递增id时会因为精度丢失导致重复主键。而且情况不仅如此,由于同样原因传递datetime类型也会因为毫秒不一致导致时间匹配错误。一般的解决方法是使用字符串传递,不过这毕竟属于偏方并没有从根源上解决问题,因此我们还是直接使用protobuf来处理。

protobuf的缺点

dto层必须引用protobuf-net来添加特性,这在一定程度上导致了代码的侵入。基本上dto属于poco,依赖第三方包的话总觉得有点不贞洁……另外就是protobuf序列化后的数据不具有可视化,因此如果是使用消息队列或者请求监控的地方,就要综合考虑protobuf是否适合使用场景。

原理

asp.net core是基于中间件方式来实现,其自带默认的jsonformater(基于json.net),asp.net core会根据content type来选择对应的formater来处理对象的序列化,当中包括inputformatter(反序列化)和outputformatter(序列化)。因此除了protobuf,我们还可以添加或者替换其它的序列化方式,例如使用jil来代替json.net来提高json性能。

以上实现以及demo和测试的源代码已放到 github 上。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。