在NodeJS服务器上处理大量数据检索

时间:2018-02-21 09:47:18

标签: node.js server google-cloud-datastore gzip

关于如何降低从我的NodeJS后端服务器上的数据库中获取和处理大量iot时间序列数据的延迟的一般性问题。有时即使我将超时设置为15秒,我也会超时。

我目前的设置使用Google Datastore作为流数据的数据库,NodeJS后端服务器用于在将数据传递到前端之前与数据存储进行交互。我还在后端服务器上本地托管的MongoDB作为缓存层。

1个请求的数据检索工作流程大致如下:从MySQL数据库中查询设备mac地址,并用于查询所请求时间范围的缓存,然后从时间间隔(未被缓存覆盖)中检索数据数据存储区,并聚合到请求的时间分辨率,并根据需要对某些类型的数据进行额外的解析。

我能想到的一些提高性能的策略包括。欢迎对以下策略提出任何其他建议/意见。

  1. 异步 - 使用async从缓存中获取数据以及数据存储(已实现)

  2. Streams - 使用fs返回流中的数据以提高内存利用率

  3. 压缩 - 使用压缩等库来减少数据大小 发送到前端

  4. 群集 - 使用群集来使用多核处理器(更多用于提供多个请求,而不是减少每个请求的延迟)

  5. 增加服务器CPU / RAM - 提供更强大的服务器(如何知道要配置的内存/处理器速度/内核数量?)

2 个答案:

答案 0 :(得分:1)

您希望专注于减少延迟,但是从我的角度来看,您使用的是太复杂的系统,涉及无法有效使用的技术。你说你的工作流程是这样的:

MySQL - >缓存(使用MongoDB)/数据存储 - > NodeJS - >前端

首先,看看您使用的是三种不同的数据库解决方案。我知道它们是用于不同的任务,但它似乎并不是最有效的方法。我会说Datastore + MongoDB的组合似乎也不自然。你为什么不使用下列之一?:

  1. Datastore + Redis的组合(用于缓存功能)。在这里,您有关于如何Cache Application Data Using Redis的官方文档。在这里,您有一个更详细的教程,介绍如何Connect to Redis from Node.js on Google App Engine Flexible,完成所有的交互,包括部署Redis on Google Cloud using Deploy Launcher
  2. 直接使用Mongodb。这里有关于如何Connect to MongoDB from Node.js on Google App Engine Flexible environment
  3. 的教程

    无论你选择第一个还是第二个选项:你不能使用MySQL吗?

    如果您使用Google App Engine使用其中任何一种解决方案,您可以更轻松地回答与群集相关的问题。增加服务器CPU / RAM。您可以尝试使用different configurations of your app.yaml来检查最适合您的内核和内存量。

    我不介意与异步和压缩策略有关。根据Streams,您可以使用@MichałCzapracki关于使用scramjet的建议。

答案 1 :(得分:0)

在我的应用程序中,我需要提供一个选项,以连接多个mongodb集合(所有数据)并导出到一个CSV文件中,并附带导出进度百分比

exports.getExportData = (req, res) => {
    req.setTimeout(2400000);
    console.log('------------------------------------------ getExportData ----------------------------------------------')
    let url = urls.baseUrl + urls.endpoints.getData;
    url = postParamsToGet(url, req.query);
    let postContents = {
        headers: {
            'content-type': 'application/json'
        },
        url: url,
        form: req.body,
        timeout: 2400000
    }, 
    receivedData = '', totalData = 0, column = false, lastEntry = 0, 
    successResponse = {
        status: {
            success: true,
            code: 200,
            message: 'processing...'
        },
        data: {
            url: [],
            processed: 0
        }
    }, 
    errorResponse = {
        status: {
            success: false,
            code: 400,
            message: 'something went wrong'
        },
        data: {
            url: ''
        }
    }, 
    exitChunk = false, filesRows = 0, fileCount = 0, pending = true;

    console.log(JSON.stringify(postContents))
    try {
        req.on("close", function() {
            console.log("close the connection!");
            errorResponse.status.message = 'connection broke'
            res.end(JSON.stringify(errorResponse));
            exitChunk = true
        });

        let fileName = `${md5((new Date()).getTime())}.csv`
        let email = 'abc@example.com'
        if (req.query.emailId) {
            fileName = md5((new Date()).getTime() + req.query.emailId) + '.csv';
            email = req.query.emailId
        }
        let tempFile = path.resolve('./modules/utils/temp/' + fileName);
        if (!fs.existsSync(path.resolve('./modules/utils/temp')))
            fs.mkdirSync(path.resolve('./modules/utils/temp'))

        if (!fs.existsSync(path.resolve(tempFile))) {
            fs.appendFileSync(tempFile, '');
        }
        const uploadChunkFile = (tf, fn) => {
            console.log('uploading to S3...', totalData, tf)
            return new Promise(async (resolve, reject) => {
                try {
                    if (!fs.existsSync(path.resolve(tf)))
                        return reject('unable to create file')
                    await exportCsv.uploadToS3(tf, fn, async function(err, status, url) {
                        if (err) {
                            console.log(err)
                            return resolve(fileName)
                        }
                        if (!status) {
                            console.log(status)
                            return resolve(fileName)
                        }
                        return resolve(url)
                    });
                }
                catch(err) {
                    console.log(err.message)
                    return reject(err.message)
                }
            })
        }

        // chunk process start from here
        request.post(postContents)
        .on('response', async function(response) {
            if (response.statusCode != 200) {
                errorResponse.status.message = response.statusMessage
                errorResponse.status.code = response.statusCode
                res.statusMessage = response.statusMessage
                console.log(errorResponse)
                return res.status(response.statusCode).send(errorResponse)
            }
        })
        .on('data', async function(d) {
            if (exitChunk) {
                res.removeAllListeners('data');
                return res.destroy();
            }

            let receive = d.toString()
            if (receive.indexOf("END_OF_STRING") > -1) {
                let receiveString = receivedData, pendingString = ''
                receivedData = ''
                receiveString += receive
                receiveString = pendingString + receiveString
                let splitedStringChunk = receiveString.split("END_OF_STRING")
                if (splitedStringChunk.length > 1) {
                    for (let str = 1; str < splitedStringChunk.length; str++) {
                        pendingString += splitedStringChunk[str]
                    }
                }
                let substrReceiveString = splitedStringChunk[0].split("START_OF_STRING")[1]
                let result = JSON.parse(substrReceiveString)
                let jsonData = []
                totalData = result.total
                lastEntry = result.o + result.l
                filesRows += result.l
                if (!column) {
                    column = Object.keys(result.data[0]);
                    jsonData.push(`"${column.join('","')}"\n`)
                }
                result.data.forEach(d => {
                    let joinRows = [];
                    column.forEach(c => {
                        if (d[c] !== undefined) {
                            joinRows.push(d[c].toString())
                        } else {
                            joinRows.push('-');
                        }
                    });
                    joinRows = joinRows.join('","')
                    jsonData.push(`"${joinRows.substring(0, joinRows.length)}"\n`)
                });

                await fs.appendFileSync(tempFile, jsonData.join(''));

                pending = true
                if (filesRows >= 1000000) {
                    filesRows = 0
                    try {
                        let uploadedUrl = await uploadChunkFile(tempFile, fileName)
                        successResponse.data.url.push(uploadedUrl)
                    }
                    catch(err) {
                        console.log(err.message)
                        console.log('chunk file upload error')
                    }
                    fileName = `${fileName.split('.')[0]}_${++fileCount}.csv`
                    tempFile = path.resolve('./modules/utils/temp/' + fileName);
                    if (!fs.existsSync(path.resolve(tempFile))) {
                        fs.appendFileSync(tempFile, '');
                    }
                    pending = false
                }

                console.log(`${result.processed}% completed${'.'.repeat(Math.round(result.processed/2))}, ${lastEntry}, ${result.o}, ${result.l}-- ${totalData}`)
                successResponse.data.processed = (parseInt(result.processed) == 100) ? '99': result.processed.toString()
                await res.write(JSON.stringify(successResponse))
                await res.flush()

                if (totalData > 0 && totalData == lastEntry) {
                    console.log('entered ....')
                    if (pending) {
                        try {
                            let uploadedUrl = await uploadChunkFile(tempFile, fileName)
                            successResponse.data.url.push(uploadedUrl)
                        }
                        catch(err) {
                            console.log(err.message)
                            console.log('chunk file upload error last time')
                        }
                    }
                    console.log('upload completed')
                    successResponse.data.processed = '100'
                    successResponse.status.message = 'url created successfully'
                    await res.write(JSON.stringify(successResponse))
                    await res.flush()
                    return await res.end()
                }
            }
            else {
                receivedData += d.toString()
            }
        })
        .on('end', async function() {
            if (totalData > 0 && totalData == lastEntry) {
               // response already sent
            }
            else {
                successResponse.data.processed = '-1'
                successResponse.status.message = 'no data found'
                await res.write(JSON.stringify(successResponse))
                await res.flush()
                return await res.end()
            }
        })
        .on('error', async function(err) {
            console.log('on error', err.Error)
            errorResponse.status.message = err
            return res.status(errorResponse.status.code).send(errorResponse)
        })
    }
    catch(err) {
        console.log('on try catch', err.message)
        errorResponse.status.message = err.message
        return res.status(errorResponse.status.code).send(errorResponse)
    }
}