【问题标题】:Node.js - Read and Write thousands/millions of JSON files in a loopNode.js - 循环读取和写入数千/数百万个 JSON 文件
【发布时间】:2019-06-27 15:18:46
【问题描述】:

我需要以最高效、最快速的方式处理大量文件。

读取 -> 处理 -> 写入(保存到同一位置)。

我的问题是我的实现很慢,至少我是这么认为的。处理600000个文件花了我半个晚上左右的时间。

我故意以同步方式完成,如果异步方式可以做得更好 - 我愿意接受解决方案,我只是不认为处理大量仅重 1-3kb 的文件会花费那么长时间.

文件有简单的 JSON 数据,每个文件大约 1-3kb 大小,就像我已经说过的那样。

这些文件位于单独的文件夹中,每个文件夹包含 300 个文件。我将它们分开是为了使其更高效和可用。

所以我们有大约 2000 个文件夹,每个文件夹有 300 个文件(1-3kb 大小)。

看看我的代码,给我你的想法。谢谢!

function test() {

    /**
     * Get list of folders and loop through
     */
    const folderList = fs.readdirSync(`../db`)

    for (const folder of folderList) {

        /**
         * Get list of files for each folder and loop through
         */
        const fileList = fs.readdirSync(`../db/${ folder }`)

        for (const filePath of fileList) {

            /**
             * try/catch block to handle JSON.parse errors
             */
            try {

                /**
                 * Read file
                 */
                const file = JSON.parse(fs.readFileSync(`../db/${ folder }/${ filePath }`))

                /**
                 * Process file
                 */
                processFile(file)

                /**
                 * Write file
                 */
                fs.writeFileSync(`../db/${ folder }/${ filePath }`, JSON.stringify(file), 'utf8')

            } catch (err) {

                console.log(err)

            }
        }
    }
}

我希望它运行得相当快,实际上这需要一段时间。

【问题讨论】:

  • 所有操作都是同步的。这是故意的吗?
  • 几个想法:您正在使用同步/阻塞调用来读取和写入文件;通过在此处使用异步/非阻塞调用,您可以看到一些改进;但是并行操作的数量会有一个最佳点;例如也许同时运行 10 次迭代会有所帮助,但 10,000 次可能会产生不利影响,因此您必须尝试:github.com/feross/run-parallel-limit。此外,您的 processFile 是不透明的。里面发生了什么?如果这是一个 CPU 密集型任务,您可以在工作线程中运行它。 nodejs.org/api/worker_threads.html
  • 这个问题可能更适合codereview.stackexchange.com,因为您正在寻找工作代码的改进:)
  • 我还注意到,如果我运行一次脚本,如果我停止它并再次运行,它将快速循环遍历已经处理的文件(尽管我们处理它们而不是跳过),当它涉及我们停止的数字 - 它再次开始减速。
  • @Boaz 是的,但我愿意尝试异步方式。我只是没想到处理小 JSON 对象可能需要那么长时间。

标签: javascript node.js fs


【解决方案1】:

伙计们,我想出了这个解决方案作为测试,你能检查一下,让我知道它是否是一个好的实现吗?处理 600k 个文件需要 10 到 15 分钟,而不是几个小时。每个“文件夹”中有 300 个文件,所以我们总是等待 300 个 promise 完成。我这样做是因为文件很小(1-3kb,一个对象,没什么花哨的)。这可以做得更好吗,例如,这可以在一分钟内完成吗? :)

async function test() {

    const folderList = fs.readdirSync(`../db`)

    for (const folder of folderList) {

        console.log(folder)

        const fileList = fs.readdirSync(`../db/${ folder }`)

        let promises = []

        for (const fileName of fileList) {
            promises.push(processFile(site, folder, fileName))
        }

        await Promise.all(promises)

    }
}

async function processFile(folder, fileName) {

    const path = `../db/${ folder }/${ fileName }`

    const file = await readFile(path)

    if (file){
        //do something and write
        await writeFile(path)
    }

}

function readFile(path) {
    return new Promise(function (resolve) {

        fs.readFile(path, function (err, raw) {

            if (err) {
                console.log(err)
                resolve()
                return
            }

            try {
                const file = JSON.parse(raw)
                resolve(file)
            } catch (err) {
                console.log(err)
                resolve()
            }

        })

    })
}

function writeFile(path, object) {
    return new Promise(function (resolve) {

        fs.writeFile(path, JSON.stringify(object), function (err) {
            if (err)
                console.log(err)

            resolve()
        })

    })
}

【讨论】:

  • 是的。这可以做得更好。10-15 分钟是一个巨大的时间。与其等待 300 个承诺完成,只需为每个承诺注册回调并单独处理每个承诺。这样会快得多.我认为它几乎不需要 2 分钟
  • @Sanjay 请看看我的新答案。
【解决方案2】:

所以,在玩了一些东西之后,我想到了这样的事情:

const PromisePool = require('es6-promise-pool')

const list = require('./list.json')

let n = 0

let pool = new PromisePool(promiseProducer, 11)

pool.start()
    .then(function () {
        console.log('Complete')
    })

function promiseProducer(){

    console.log(n)

    if (n < list.length)
        return processFile(list[++n])
    else
        return null
}

这跑得相当快。不过,我还有一些问题。

  1. 任何人都可以编写自己的并发限制实现吗?没有图书馆等
  2. 像以前一样,如果我运行脚本并等待处理 20k(例如)文件,如果我停止脚本并重新运行,它会很快达到 20k(我们停止的地方)然后它会变慢.是什么原因?

【讨论】:

  • 在没有库的情况下,node.js 可以处理数千个并发请求,正如您在互联网上的许多文章中看到的那样。如果您使用库,并发限制将根据特定库而变化,就像大多数库一样用于生成 html-pdf 的并发性仅为 1 以避免更多负载。对于其他库也是如此,并发限制可能会有所不同。同时并发限制也将取决于您的硬件
猜你喜欢
  • 2016-08-22
  • 1970-01-01
  • 1970-01-01
  • 2012-10-05
  • 1970-01-01
  • 2020-10-15
  • 2020-03-16
  • 2016-06-12
  • 1970-01-01
相关资源
最近更新 更多