JSON Stream

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相關的有:

經過查看文檔和使用, 最後本文決定採用 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的實現方案, 可以運用於前後端的大對象大數據場景.

5. 其他參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章