149

最近我发现了MessagePack,这是一种替代谷歌协议缓冲区JSON的二进制序列化格式,它也优于两者。

还有MongoDB 用于存储数据的BSON序列化格式。

有人可以详细说明BSON 与 MessagePack 的区别和劣势吗?


只是为了完成高性能二进制序列化格式的列表:还有Gobs 将成为 Google 的 Protocol Buffers 的继任者。然而,与所有其他提到的格式相比,这些格式与语言无关并且依赖于Go 的内置反射,还有 Gobs 库至少适用于 Go 以外的其他语言。

4

6 回答 6

213

// 请注意,我是 MessagePack 的作者。这个答案可能有偏见。

格式设计

  1. 与 JSON 的兼容性

    尽管它的名字,BSON 与 JSON 的兼容性与 MessagePack 相比并没有那么好。

    BSON 具有特殊类型,如“ObjectId”、“Min key”、“UUID”或“MD5”(我认为这些类型是 MongoDB 所必需的)。这些类型与 JSON 不兼容。这意味着当您将对象从 BSON 转换为 JSON 时,可能会丢失一些类型信息,但当然只有当这些特殊类型位于 BSON 源中时。在单个服务中同时使用 JSON 和 BSON 可能是一个缺点。

    MessagePack 旨在透明地从/到 JSON 转换。

  2. MessagePack 比 BSON 小

    MessagePack 的格式不如 BSON 冗长。因此,MessagePack 可以序列化小于 BSON 的对象。

    例如,一个简单的映射 {"a":1, "b":2} 使用 MessagePack 序列化为 7 个字节,而 BSON 使用 19 个字节。

  3. BSON 支持就地更新

    使用 BSON,您可以修改部分存储的对象,而无需重新序列化整个对象。假设地图 {"a":1, "b":2} 存储在一个文件中,并且您希望将 "a" 的值从 1 更新为 2000。

    对于 MessagePack,1 仅使用 1 个字节,但 2000 使用 3 个字节。所以“b”必须向后移动2个字节,而“b”不被修改。

    使用 BSON,1 和 2000 都使用 5 个字节。由于这种冗长,您不必移动“b”。

  4. MessagePack 有 RPC

    MessagePack、Protocol Buffers、Thrift 和 Avro 支持 RPC。但 BSON 没有。

这些差异意味着 MessagePack 最初是为网络通信而设计的,而 BSON 是为存储而设计的。

实现和 API 设计

  1. MessagePack 具有类型检查 API(Java、C++ 和 D)

    MessagePack 支持静态类型。

    与 JSON 或 BSON 一起使用的动态类型对于 Ruby、Python 或 JavaScript 等动态语言很有用。但是对于静态语言来说很麻烦。你必须编写无聊的类型检查代码。

    MessagePack 提供类型检查 API。它将动态类型的对象转换为静态类型的对象。这是一个简单的例子(C++):

    #include <msgpack.hpp>

    class myclass {
    private:
        std::string str;
        std::vector<int> vec;
    public:
        // This macro enables this class to be serialized/deserialized
        MSGPACK_DEFINE(str, vec);
    };

    int main(void) {
        // serialize
        myclass m1 = ...;

        msgpack::sbuffer buffer;
        msgpack::pack(&buffer, m1);

        // deserialize
        msgpack::unpacked result;
        msgpack::unpack(&result, buffer.data(), buffer.size());

        // you get dynamically-typed object
        msgpack::object obj = result.get();

        // convert it to statically-typed object
        myclass m2 = obj.as<myclass>();
    }
  1. MessagePack 有 IDL

    它与类型检查 API 有关,MessagePack 支持 IDL。(规范可从:http ://wiki.msgpack.org/display/MSGPACK/Design+of+IDL 获得)

    Protocol Buffers 和 Thrift 需要 IDL(不支持动态类型)并提供更成熟的 IDL 实现。

  2. MessagePack 具有流式 API(Ruby、Python、Java、C++、...)

    MessagePack 支持流式反序列化器。此功能对网络通信很有用。这是一个例子(红宝石):

    require 'msgpack'

    # write objects to stdout
    $stdout.write [1,2,3].to_msgpack
    $stdout.write [1,2,3].to_msgpack

    # read objects from stdin using streaming deserializer
    unpacker = MessagePack::Unpacker.new($stdin)
    # use iterator
    unpacker.each {|obj|
      p obj
    }
于 2011-06-15T11:31:27.037 回答
17

我认为非常重要的是要提到它取决于您的客户端/服务器环境是什么样的。

如果您在未经检查的情况下多次传递字节,例如使用消息队列系统或将日志条目流式传输到磁盘,那么您可能更喜欢二进制编码来强调紧凑的大小。否则,这是不同环境的个案问题。

某些环境可以对 msgpack/protobuf 进行非常快速的序列化和反序列化,而其他环境则不然。一般来说,语言/环境越低级,二进制序列化效果越好。在更高级的语言(node.js、.Net、JVM)中,您经常会看到 JSON 序列化实际上更快。那么问题就变成了,您的网络开销是否比您的内存/cpu 受到更多或更少的限制?

关于 msgpack 与 bson 与协议缓冲区... msgpack 是该组中最少的字节,协议缓冲区大致相同。BSON 定义了比其他两个更广泛的本机类型,并且可能更适合您的对象模型,但这使其更加冗长。协议缓冲区具有被设计为流式传输的优势......这使其成为二进制传输/存储格式的更自然格式。

就个人而言,我倾向于 JSON 直接提供的透明度,除非明确需要更轻的流量。通过带有 gzip 压缩数据的 HTTP,网络开销的差异在格式之间甚至不是问题。

于 2013-07-30T21:34:29.163 回答
4

快速测试显示缩小的 JSON 比二进制 MessagePack 更快地反序列化。在测试中,Article.json 是 550kb 的压缩 JSON,Article.mpack 是 420kb 的 MP 版本。当然可能是实施问题。

消息包:

//test_mp.js
var msg = require('msgpack');
var fs = require('fs');

var article = fs.readFileSync('Article.mpack');

for (var i = 0; i < 10000; i++) {
    msg.unpack(article);    
}

JSON:

// test_json.js
var msg = require('msgpack');
var fs = require('fs');

var article = fs.readFileSync('Article.json', 'utf-8');

for (var i = 0; i < 10000; i++) {
    JSON.parse(article);
}

所以时间是:

Anarki:Downloads oleksii$ time node test_mp.js 

real    2m45.042s
user    2m44.662s
sys     0m2.034s

Anarki:Downloads oleksii$ time node test_json.js 

real    2m15.497s
user    2m15.458s
sys     0m0.824s

所以节省了空间,但更快?不。

测试版本:

Anarki:Downloads oleksii$ node --version
v0.8.12
Anarki:Downloads oleksii$ npm list msgpack
/Users/oleksii
└── msgpack@0.1.7  
于 2012-11-05T22:34:20.800 回答
2

尚未提及的一个关键区别是 BSON 包含整个文档和进一步嵌套的子文档的大小信息(以字节为单位)。

document    ::=     int32 e_list

这对于尺寸和性能很重要的受限环境(例如嵌入式)有两个主要好处。

  1. 您可以立即检查您要解析的数据是否代表一个完整的文档,或者您是否需要在某个时候请求更多(无论是来自某个连接还是存储)。由于这很可能是一个异步操作,您可能在解析之前已经发送了一个新请求。
  2. 您的数据可能包含整个子文档,其中包含与您无关的信息。BSON 允许您通过使用子文档的大小信息跳过它,轻松地遍历到子文档之后的下一个对象。另一方面,msgpack 包含所谓的地图中的元素数量(类似于 BSON 的子文档)。虽然这无疑是有用的信息,但它对解析器没有帮助。您仍然必须解析地图中的每个对象,并且不能跳过它。根据数据的结构,这可能会对性能产生巨大影响。
于 2019-04-03T07:36:29.000 回答
1

我做了快速基准测试来比较 MessagePack 与 BSON 的编码和解码速度。至少如果您有大型二进制数组,BSON 会更快:

BSON writer: 2296 ms (243487 bytes)
BSON reader: 435 ms
MESSAGEPACK writer: 5472 ms (243510 bytes)
MESSAGEPACK reader: 1364 ms

使用 neuecc 的 C# Newtonsoft.Json 和 MessagePack:

    public class TestData
    {
        public byte[] buffer;
        public bool foobar;
        public int x, y, w, h;
    }

    static void Main(string[] args)
    {
        try
        {
            int loop = 10000;

            var buffer = new TestData();
            TestData data2;
            byte[] data = null;
            int val = 0, val2 = 0, val3 = 0;

            buffer.buffer = new byte[243432];

            var sw = new Stopwatch();

            sw.Start();
            for (int i = 0; i < loop; i++)
            {
                data = SerializeBson(buffer);
                val2 = data.Length;
            }

            var rc1 = sw.ElapsedMilliseconds;

            sw.Restart();
            for (int i = 0; i < loop; i++)
            {
                data2 = DeserializeBson(data);
                val += data2.buffer[0];
            }
            var rc2 = sw.ElapsedMilliseconds;

            sw.Restart();
            for (int i = 0; i < loop; i++)
            {
                data = SerializeMP(buffer);
                val3 = data.Length;
                val += data[0];
            }

            var rc3 = sw.ElapsedMilliseconds;

            sw.Restart();
            for (int i = 0; i < loop; i++)
            {
                data2 = DeserializeMP(data);
                val += data2.buffer[0];
            }
            var rc4 = sw.ElapsedMilliseconds;

            Console.WriteLine("Results:", val);
            Console.WriteLine("BSON writer: {0} ms ({1} bytes)", rc1, val2);
            Console.WriteLine("BSON reader: {0} ms", rc2);
            Console.WriteLine("MESSAGEPACK writer: {0} ms ({1} bytes)", rc3, val3);
            Console.WriteLine("MESSAGEPACK reader: {0} ms", rc4);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

        Console.ReadLine();
    }

    static private byte[] SerializeBson(TestData data)
    {
        var ms = new MemoryStream();

        using (var writer = new Newtonsoft.Json.Bson.BsonWriter(ms))
        {
            var s = new Newtonsoft.Json.JsonSerializer();
            s.Serialize(writer, data);
            return ms.ToArray();
        }
    }

    static private TestData DeserializeBson(byte[] data)
    {
        var ms = new MemoryStream(data);

        using (var reader = new Newtonsoft.Json.Bson.BsonReader(ms))
        {
            var s = new Newtonsoft.Json.JsonSerializer();
            return s.Deserialize<TestData>(reader);
        }
    }

    static private byte[] SerializeMP(TestData data)
    {
        return MessagePackSerializer.Typeless.Serialize(data);
    }

    static private TestData DeserializeMP(byte[] data)
    {
        return (TestData)MessagePackSerializer.Typeless.Deserialize(data);
    }
于 2019-04-29T12:55:48.540 回答
1

嗯,正如作者所说,MessagePack 最初是为网络通信而设计的,而 BSON 是为存储而设计的。

MessagePack 是紧凑的,而 BSON 是冗长的。MessagePack 旨在节省空间,而 BSON 设计用于 CURD(节省时间)。

最重要的是MessagePack的类型系统(前缀)遵循Huffman编码,这里我画了MessagePack的Huffman树(点击链接看图):</p>

MessagePack 的霍夫曼树

于 2019-08-19T03:36:59.183 回答