1. 需求背景
在日常开发中经常会遇到大对象或者大文件处理, 比如在nodejs开发中, 一个算法包可能范围了一个长度为好几万长度的一个对象, 这个对象使用Restful API不好传递, 肯定会把这个处理结果保存为文件, 然后通过通过文件传递数据; 相反的业务中也会存在一个很大的文件里面可能是一个JSON结构的数据, 但是做完反序列化后在代码中变成了一个大对象. 所以本文讨论大对象处理的两个实践:
- 大对象如何保存为数据文件
- 大数据文件如何发返序列化小对象
如果上面的实践有解决方案那么我们的代码将对内存的要求变低, 让代码更健壮的处理数据.
2. 分析
通常一碰到大数据的处理, 第一想到的就是流, 第一个想到的数据格式就是csv, 业务数据一般不是数组就是对象, 一个大的数组可以可以通过多行文件来存储, 一行保存数据组中的一个元素;而对象可能需要收抽象一下比如狠一点我们将文件划分为m*n的矩阵而对象数据的值分别存到矩阵的某个位置, 只要不重复就好, 解析数据时将文件数据按行转为内存对象, 在用矩阵位置和对象Key的map关系进行取用就行.
其实不管是CSV和还是矩阵可以都不太通用因为太trick了, 在JS中不管是对象还是数组都是JSON, 是否有一种数据格式可以保存大量JSON数据呢? 答案当然是有, 这就是: JSON Stream.
JSON Stream是一个思路, 通过维基百科的介绍, 其本质是带分隔符的JSON, 它的落地实现方案有:
- Newline-delimited JSON
- Record separator-delimited JSON
- Concatenated JSON
- Length-prefixed JSON
最简单和理解是第一种, 也是经过调研生态圈使用比较多的一种, 这种方案也称为: ndjson, 关于ndjson还有一个议案, 可以从这里查看, 而起本质规定ndjson如何序列化和反序列化, 这里不做太多讨论.
在维基百科中的最后给出了一些应用程序和工具, 常见的比如jq
, 可以读取行分隔的JSON文本. 其中nodejs相关的有:
- ldjson-stream module for Node.js
- ld-jsonstream dependency free module for Node.js
经过查看文档和使用, 最后本文决定采用 ndjson.js, 该包其实是ldjson-stream的持续维护版本, 不选择 ld-jsonstream是因为其没有序列化功能.
3. 代码实践
使用 ndjson 实现本文开头的两个业务场景, 具体来说就是:
- 从一个 ndjson 文件中反序列化话得到小对象
- 将大量小对象序列化并写入文件(ndjson文件)
上面两个实践如果可以跑通, 则我们找到了一种对内存友好的大对象处理方案了.
import * as ndjson from 'ndjson';
import * as fs from 'fs';
(async () => {
const file = 'test/ndjson.txt';
// 将js对象序列化为流进而写入文件
const writable = fs.createWriteStream(file, {
encoding: 'utf8',
highWaterMark: 10,
});
const serialize = ndjson.stringify();
for (let i = 0; i < 10000; i++) {
serialize.write({
id: i,
key: 'change1_0.6995461115147918',
value: { rev: '1-e240bae28c7bb3667f02760f6398d508' },
doc: {
_id: 'change1_0.6995461115147918',
_rev: '1-e240bae28c7bb3667f02760f6398d508',
hello: 1,
},
});
}
serialize.end();
serialize.pipe(writable);
writable.on('finish', () => {
console.log('all data file size: ', fs.statSync(file).size);
});
// 从流水读取数据并parse为js对象
const alldata = [];
fs.createReadStream('test/ndjson.txt', {
encoding: 'utf8',
highWaterMark: 10,
})
.pipe(ndjson.parse())
.on('data', function (obj) {
alldata.push(obj);
})
.on('end', () => {
console.log('data size: ', alldata.length);
});
})();
输出结果:
all data file size: 1968890
data size: 10000
4. 结论
ndjson 是一种当前较为成熟的一种JSON Stream的实现方案, 可以运用于前后端的大对象大数据场景.