跳转至

标记语言与序列化

参考资料

标记语言

概念

  • 描述一个对象, 本质上是对象 / 数组 / key-value 的嵌套结构

XML

<?xml version="1.0" encoding="UTF-8"?>
<!-- 第一行描述, 这是注释 -->
<message>
    <warning>
         Hello World
    </warning>
</message>

YAML

languages:
    - Ruby # 数组
    - Perl
    - Python 
websites:
  YAML: yaml.org # 对象 
  Ruby: ruby-lang.org 
  Python: python.org 
  Perl: use.perl.org

# 纯量
boolean: 
        - TRUE  # true, True 都可以
        - FALSE  # false, False 都可以
float:
        - 3.14
        - 6.8523015e+5  # 可以使用科学计数法
int:
        - 123
        - 0b1010_0111_0100_1010_1110    # 二进制表示
null:
    nodeName: 'node'
    parent: ~  # 使用~表示 null
string:
        - 哈哈
        - 'Hello world'  # 可以使用双引号或者单引号包裹特殊字符
        - newline
      newline2    # 字符串可以拆成多行, 每一行会被转化成一个空格
date:
        - 2018-02-17    # 日期必须使用 ISO 8601 格式, 即 yyyy-MM-dd
datetime: 
        -  2018-02-17T15:02:31+08:00    # 时间使用 ISO 8601 格式, 时间和日期之间使用 T 连接, 最后使用 + 代表时区

TOML

# 这是一个 TOML 文档

var_title = "TOML 示例" # 基本语法, 支持类型同上

[owner] # 表, 直至下一个表头
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00

[database]
enabled = true
ports = [ 8000, 8001, 8002 ] # 数组
data = [ ["delta", "phi"], [3.14] ]
temp_targets = { cpu = 79.5, case = 72.0 } # 内联表

[servers]

[servers.alpha]
ip = "10.0.0.1"
role = "前端"

[servers.beta]
ip = "10.0.0.2"
role = "后端"

INI

; Win 配置语言, 最简单的
[obj1] ;节
x=1
y=2
[obj2]
x=6

JSON

  • json 中有 null 类型
{}对象
[]数组
{
    "sites": [
        { "name":"google", "url":"www.google.com" }, 
        { "name":"微博", "url":"www.weibo.com" }
    ]
}

序列化

  • 将对象转换为字节序列的过程称为对象的序列化
  • 将字节序列恢复为对象的过程称为对象的反序列化
  • 其实就是约定的格式
  • 常用于网络传输, 数据库存储, 进程间通信等场景
  • 有些压缩方法省略结构信息, 所以需要声明 IDL 描述结构

JSON -> BSON

  • MongoDB 用的, 别和 JSONB 混淆, JSONB 是可索引的表达抽象语法树的对象
  • TLV (Tag-Length-Value) 的变体, 实际上更接近 Type-Key-Length-Value-0
    • 先看 Type 决定类型 / 结构 (儿子, 同级, 数组, 数据 (数据类型))
    • 再看 Key 决定字段名
    • 存 Length 的目的是方便跳过
    • 看真实数据
    • 看见 0x00 (双重保险)
  • JSON 只有 String, Number, Boolean, Array, Object, Null, BSON 增加了 Date (64位整数, UNIX 纪元毫秒数), BinData (二进制数据), ObjectId 等类型, 区分 Int32, Int64, Double, Decimal128 (高精度十进制)
  • 不通用

FlatBuffers

  • Google 开发, 最初为了游戏开发和对内存极其敏感的应用场景
  • 零拷贝, 已编码数据与内存中的对象表示几乎一致, 读取时无需解析
  • 偏移量表实现随机访问, 序列化时通常是从叶子节点向根节点构建 (倒序, 确定偏移量放在父的表里), 一旦生成, 很难原地修改数据
    • Varint (变长整数): 使用 ZigZag 编码和 Base128 变长编码, 小整数只占 1 字节
    • 紧凑排列: 所有字段紧挨着存放, 没有 padding
  • 没那么通用 (不压缩是原罪)
  • 需要定义 IDL 描述结构

Protobuf

  • Google 的通用语言, 现代微服务架构的事实标准
  • TLV, Key 被替换成了 field_number (方便增删), Tag = (field_number << 3) | wire_type(wire_type 为编码形式) 因此不传输字段名, 极大节省空间
  • 如果 Tag 中类型为定长类型, 省略 Length
  • Varints (变长整数) + ZigZag 编码 (解决 Varints 编码负数难题) + 去掉未赋值的字段等
  • 需要定义 .proto 文件描述结构, 定义field_number, 事实标准

MessagePack

  • 二进制 JSON
  • TLV 将 JSON -> Key 的 TLV + Value 的 TLV
  • TL 紧密排列

Thrift

  • Facebook 开发, 后贡献给 Apache 通常指代整个 Thrift RPC 框架
  • 双协议
    • TBinaryProtocol: 普通的二进制格式, 类似 Protobuf 但并未极致压缩
    • TCompactProtocol: 使用 Varint 和 ZigZag 压缩, 逻辑与 Protobuf 非常相似
  • 更封闭的 Protobuf

Cap'n Proto

  • MariaDB 之于 MySQL, Cap'n Proto 之于 Protobuf
  • Protobuf 的核心思想是压缩与编码, 需要全量反序列化 + 重建对象
  • Cap'n Proto 的核心思想是数据在内存中的布局 = 数据在传输线路上的布局, 无需反序列化, 直接访问内存 -> 实际场景中 Protobuf 在 CPU 上的反序列化成本较高
  • 由于依赖固定的内存偏移量 (以此换取随机访问能力), 数据中会有大量的 Padding (填充零) 以对齐字节且通常不压缩整数
  • 生成的不是持有数据的对象,而是访问器 (Readers/Builders)
  • IDL 定义主结构体的全部信息, 变长数据用占位指针, 真实数据放在末尾 (实际策略更复杂)
  • 自带 RPC 实现, 支持远程随机访问
  • Promise Pipelining / 时间旅行 / 基于能力
    • 客户端发送请求, 服务端未返回时, 就可以持有 Promise
    • 基于定义的 Promise 类型能力, 可以继续调用
    • 多个调用如果涉及多次请求, 可以一起发送