数据编码格式
数据一般有两种表示方式:
1、在内存中,数据保存在对象、结构体、列表等结构中,这些数据结构针对CPU的高效访问和操作进行了优化(通常都使用了指针)
2、当将数据写入文件或者通过网络发送是,必须将其编码为某种子包含的字节序列,如JSON文档,这种就不在使用特定的指针。
这两种表示形式之间的转化为序列化和反序列化。
简单来说序列化、反序列化的意义就是从各种系统当中将结构化数据转化成一种便于传输的形式的数据,这种数据一般是“规范化“”的,无论是什么系统,什么语言,希望转化成的这种模式数据是一致的,并且能将这种数据再度转化成系统中的结构化数据。
比较有名的序列库
语言 | java | ruby | python |
Serializable\Kryo | Marashal | pickle | |
序列化的一些问题:
- 序列化与其编程语言一般绑定在一起,难以解耦
- 反序列化需要能实例化任意的类,这经常会导致一些安全问题,如果反序列化过程中能够实例化任意的类,就可以远程执行一些危险代码
- 这些序列化工具的版本兼容性问题
- Java内置的反序列化工具性能较差
JSONvsXML
JSON | XML | CSV | |
流行原因 | web浏览器内置支持,JavaScript的一个子集 | 与语言无关 | |
对于数字编码 | 可以区分数字和数字字符串,但是不区分整数和浮点数,并且不指定精度 | 无法区分数字和数字字符串 | 无法区分数字和数字字符串 |
对于模式(特定数据格式的支持) | 支持 | 支持 | 不支持,需要定义每一行每一列的具体含义 |
在上述模式编码的基础上,还衍生出占用空间较少、解析速度更快的二进制编码,二进制编码并非取代前者,而是为了解决一些性能上瓶颈衍生出的一些方案。
指标 | JSON/XML | 二进制编码(如MessagePack,BJSON,Thift,Protobuf等) |
---|---|---|
空间占用 | 高(含冗余字符) | 低(仅必要数据) |
解析速度 | 慢(需逐字符解析) | 快(直接内存映射) |
CPU/内存消耗 | 高 | 低 |
Thift原本是FaceBook研发的(2007左右开源)、Protocol Buffers 是Google研发的,Avro是Hadoop 的一个子项目。其原理本质上就是对于模式的预定义,这种预定义像是SQL中的DDL语句。与MessagePack等二进制编码的区别就是少了字段名,不用每一条记录重复字段名了。
字段标签与模式演化
对于Protobuf、Thrift 的模式兼容性方案,向前向后兼容主要靠标识字段名的一个 字段标识(filed tag) 唯一定位,如果这个字段名如果添加了新的字段到模式只需要给每个字段新加一个标识号码即可,旧版本读取到新的字段标识 会忽略。
对于数据类型的变更,各个语言的兼容性方案各异,
方案 | 适用变更类型 | 向前兼容性 | 向后兼容性 | 复杂度 |
---|---|---|---|---|
Protobuf(新增字段) | 任意类型变更 | ✅ | ✅ | 中 |
Thrift(Union类型) | 有限类型变更 | ✅ | ✅ | 高 |
Avro(联合类型) | 任意类型变更 | ❌(需升级Reader) | ✅ | 低 |
直接类型提升(int32→64) | 数值范围扩展 | ⚠️(可能截断) | ✅ | 低 |