【问题标题】:Tcp socket server freezes after some timeTcp 套接字服务器在一段时间后冻结
【发布时间】:2021-03-13 07:34:43
【问题描述】:

我有来自不同 GPS 跟踪器设备的数据。它是一个单向数据,这意味着我正在接收数据并将其推送到 MySQL DB 和 firebase。我总共连接了 300 台设备,它们每 10 秒向我的服务器发送数据。

我的服务器规格是

AWS t2.xlarge
CPU: 4
Ram: 16GB

发生的情况是 3 天后,它停止将数据发送到数据库。它不会停止服务器。它只是冻结。如果我这样做

sudo netstat -tulnap | grep :8050

它确实显示了过程和所有内容,但我没有看到任何数据推送到数据库中。它只是冻结。我必须重新启动服务器,否则我必须永远停止使用它并重新启动它

forever stop  --minUptime 36000000000  server.js

当我转到我的PHPMyAdmin 并检查监视器屏幕时,我可以看到我的可用内存非常少,并且缓存内存为 GB。似乎所有内存都进入了缓存内存,这让我的服务器冻结了。我不知道我在哪里做错了导致它冻结。例如,在我发布问题时,这是我的服务器当前状态

正如您在上面看到的,在 19 小时内,缓存已经增加并且还在不断增长。下面是代码

if (cluster.isMaster) {
    // Fork workers.
    for (var i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    cluster.on('death', function(worker) {
       // console.log('worker ' + worker.pid + ' died');
        cluster.fork();
    });
} else {
    net.createServer(function(socket) {
       // console.log('received connection...');

        socket.on("error", function(err) {
         //   console.log("socket error: ")
           // console.log(err.stack);
            socket.destroy();
        });

        socket.on('data', function(data) {
        });
    });
}

最近我对代码进行了一次更改,但仍然没有成功,即每 5 秒接收一次数据后关闭套接字

 socket.on('data', function(data) {

    //parse data and  push data into db and firebase
    socket.end();
 
 });

这就是我做 MySQL 查询的方式

database.js

var mysql = require('mysql');


var pool  = mysql.createPool({
    connectionLimit    : 8,
    waitForConnections : true,
    queueLimit         : 300,
    host     : 'localhost',
    user     : 'username',
    password : '123456',
    database : 'dummy'


});


module.exports = pool;

Server.js 文件(仅我粘贴为完整代码的数据库代码有 1400 行代码)

    const db = require('./database');
    function getCarDetails(car_id,callback) {
    db.getConnection((err, connection) => {
        if(err) throw err;
    console.log('connected as id ' + connection.threadId);
    let selectQuery = 'SELECT * FROM ?? join user_info ON car.user_id = user_info.user_id WHERE ?? = ?';
    let query = mysql.format(selectQuery, ["car", "id", car_id]);
    // query = SELECT * FROM `todo` where `user` = 'shahid'
    connection.query(query, (err, data) => {
        connection.release();
    if(err) {
        console.error(err);
        //throw err;
        return;
    }
    // rows fetch
    if(data.length > 0){

        return callback(data[0]);


    }else{

        return callback(false);

    }


});
});
}


function updateIgnitionNotification(car_id,acc_on,acc_off,acc,speed,updated,callback) {
    db.getConnection((err, connection) => {
        let updateQuery =  'UPDATE car SET notification_acc_on = ?,notification_acc_off = ?,acc = ?,speed = ?,updated = ? Where id = ?';
    let query = mysql.format(updateQuery, [acc_on, acc_off,acc,speed,updated, car_id]);
    // query = UPDATE `todo` SET `notes`='Hello' WHERE `name`='shahid'
    connection.query(query, (err, data) => {
        connection.release();
    if(err) {
        console.error(err);
        //throw err;
        return;
    }else{

        return callback(data);
    }


});
});
}

function updateLastUpdatedData(car_id,current_datetime,status,acc,monitoring,max_speed,callback){
    db.getConnection((err, connection) => {
        let updateQuery =  'UPDATE car SET updated = ?,status = ?,acc = ?,monitoring = ?, max_speed = ? Where id = ?';
    let query = mysql.format(updateQuery, [current_datetime,status,acc.toUpperCase(),monitoring, max_speed, car_id]);
    // query = UPDATE `todo` SET `notes`='Hello' WHERE `name`='shahid'
    connection.query(query, (err, data) => {
        connection.release();
    if(err) {
        console.error(err);
        //throw err;
        return;
    }else{

        return callback(data);
    }


});
});
}


function updateCommand(car_id,command,callback){
    db.getConnection((err, connection) => {
        let updateQuery =   'UPDATE car SET command = ? Where id = ?';
    let query = mysql.format(updateQuery, [command, car_id]);
    // query = UPDATE `todo` SET `notes`='Hello' WHERE `name`='shahid'
    connection.query(query, (err, data) => {
        connection.release();
    if(err) {
        console.error(err);
        //throw err;
        return;
    }else{

        return callback(data);
    }


});
});
}



function updateCarLockNotification(car_id,lock_notification,callback){
    db.getConnection((err, connection) => {
        let updateQuery =   'UPDATE car SET lock_notification = ? Where id = ?';
    let query = mysql.format(updateQuery, [lock_notification, car_id]);
    // query = UPDATE `todo` SET `notes`='Hello' WHERE `name`='shahid'
    connection.query(query, (err, data) => {
        connection.release();
    if(err) {
        console.error(err);
        //throw err;
        return;
    }else{

        return callback(data);
    }


});
});
}


function updateOverSpeedNotification(car_id,notification_over_speed,callback){

    db.getConnection((err, connection) => {
        let updateQuery =     'UPDATE car SET notification_over_speed = ? Where id = ?';
    let query = mysql.format(updateQuery, [notification_over_speed, car_id]);
    // query = UPDATE `todo` SET `notes`='Hello' WHERE `name`='shahid'
    connection.query(query, (err, data) => {
        connection.release();
    if(err) {
        console.error(err);
        //throw err;
        return;
    }else{

        return callback(data);
    }


});
});
}



function updateGeoFenceOutsideAttempt(car_id,geofence_attempt,callback){

    db.getConnection((err, connection) => {
        let updateQuery = 'UPDATE car SET geofence_outside_attempt = ? Where id = ?';
    let query = mysql.format(updateQuery,  [geofence_attempt, car_id]);
    // query = UPDATE `todo` SET `notes`='Hello' WHERE `name`='shahid'
    connection.query(query, (err, data) => {
        connection.release();
    if(err) {
        console.error(err);
        //throw err;
        return;
    }else{

        return callback(data);
    }


});
});
}



function updateGeoFenceInsideAttempt(car_id,geofence_attempt,callback){

    db.getConnection((err, connection) => {
        let updateQuery = 'UPDATE car SET geofence_inside_attempt = ? Where id = ?';
    let query = mysql.format(updateQuery, [geofence_attempt, car_id]);
    // query = UPDATE `todo` SET `notes`='Hello' WHERE `name`='shahid'
    connection.query(query, (err, data) => {
        connection.release();
    if(err) {
        console.error(err);
        //throw err;
        return;
    }else{

        return callback(data);
    }


});
});
}

function updateBatteryNotification(car_id,battery,callback){

    db.getConnection((err, connection) => {
        let updateQuery = 'UPDATE car SET notification_battery = ? Where id = ?';
    let query = mysql.format(updateQuery, [battery, car_id]);
    // query = UPDATE `todo` SET `notes`='Hello' WHERE `name`='shahid'
    connection.query(query, (err, data) => {
        connection.release();
    if(err) {
        console.error(err);
        //throw err;
        return;
    }else{

        return callback(data);
    }


});
});
}



function saveNotificationLog(log,callback){

    db.getConnection((err, connection) => {
        //   let insertQuery = 'INSERT INTO ?? (??,??) VALUES (?,?)';
        //let query = mysql.format(insertQuery,["log","user","notes",data.user,data.value]);

        // query = UPDATE `todo` SET `notes`='Hello' WHERE `name`='shahid'
        connection.query('INSERT INTO log SET ?', log, (err, res) => {
        connection.release();
    if(err) {
        return callback(res);
    }else{

        return callback(res);
    }


});
});
}


function saveHistory(history,callback){

    db.getConnection((err, connection) => {
        //   let insertQuery = 'INSERT INTO ?? (??,??) VALUES (?,?)';
        //let query = mysql.format(insertQuery,["log","user","notes",data.user,data.value]);

        // query = UPDATE `todo` SET `notes`='Hello' WHERE `name`='shahid'
        connection.query('INSERT INTO car_history SET ?', history, (err, res) => {
        connection.release();
    if(err) {
        return callback(res);
    }else{

        return callback(res);
    }


});
});
}


function updateGeoFenceNotification(car_id,notification_inside,notification_outside,callback){

    db.getConnection((err, connection) => {
        let updateQuery = 'UPDATE car SET notification_inside = ?,notification_outside = ? Where id = ?';
    let query = mysql.format(updateQuery, [notification_inside, notification_outside, car_id]);
    // query = UPDATE `todo` SET `notes`='Hello' WHERE `name`='shahid'
    connection.query(query, (err, data) => {
        connection.release();
    if(err) {
        console.error(err);
        //throw err;
        return;
    }else{

        return callback(data);
    }


});
});
}

请帮我解决这个问题

【问题讨论】:

  • 是否有很多打开的套接字连接,也就是说,根据活动客户端的数量,是否比您预期的要多?您可以使用netstat -anpotgrep 检查您服务器的进程ID。尤其是处于等待状态的打开套接字连接,例如 CLOSE_WAIT。
  • edit 向我们展示将数据从您的nodejs 程序“推送”到您的MySQL 服务器的代码。在我看来,您的 nodejs 程序建立了与 MySQL 的大量连接(几乎每小时 11,000 个)。这是很多连接流失。尝试使用connection pooling..
  • @O.Jones 我已经更新了我的问题
  • @rveerd 是的,看起来确实如此。
  • @mynameisbutt 这些连接是到数据库、到客户端还是到其他东西?什么是状态,是否有计时器?您能否编辑您的问题并添加 netstat 命令的相关输出。

标签: mysql node.js sockets nodes


【解决方案1】:

我会尝试回答,或者至少做一些观察。

让您的 nodejs 应用程序在没有集群的情况下稳健运行可能是值得的。保持简单有助于故障排除。固定后,您可以添加集群。

您的服务器过度配置。您的工作负载是每分钟 1,800 个连接(每 6 秒来自 300 个设备)或 30 个/秒。如果您的程序编写得很好,那么具有 4-8GiB RAM 的 2 核服务器应该可以很好地处理这种工作负载。 1 核服务器可能就足够了。而且,一个预计会持续运行数千小时的程序必须写得好。 (如果您的数据库服务器与您的 nodejs 应用程序在同一虚拟机上运行,​​请忽略此建议。)

您没有描述如何从 nodejs 代码连接到数据库服务器。您应该使用connection pooling 来消除关闭和重新打开连接的需要。

在你的初始化代码中做这样的事情:

const mysql = require('mysql')
const mySqlPool = mysql.createPool({
  connectionLimit    : 8,         /* make this as small as possible */
  waitForConnections : true,
  queueLimit         : 300,       /* enough for 6sec worth of workload */

  host            : 'dbhost',
  user            : 'bob',
  password        : 'secret',
  database        : 'my_db',
})
/* make the pool available to other code. 
 * There may be a better way to do this. */
global.mySqlPool = mySqlPool

/* make a little monitor function to 
 * let you know of database problems.
 * this pings the database every 10sec */
let monitorTimeout = setTimeout ( function() {
    global.mySqlPool.ping ( function (err) {
        if (err) {
            clearTimeout(monitorTimeout)
            console.error('database connection error', err)
            throw err
        }
    })
}, 10000)

然后,当您需要访问数据库时,请在您拥有的代码中使用 global.mySqlPool. 代替 db.。每个查询都将使用来自池的连接。如果所有连接都在使用中,则查询将在队列中等待,直到有连接空闲。

这种池化/排队策略给您的 nodejs 程序和 MySQL 数据库带来了更可预测的负载。

对于 firebase 来说,同样的事情应该是可能的。

【讨论】:

  • 而且,我当然希望您没有通过不安全的原始 TCP/IP 套接字从 我的 车收集遥测数据!因为网络蠕变。
  • 非常感谢。我将使用您的代码,顺便说一句,我还在我的问题中添加了数据库代码
  • 我已经按照您的建议进行了更改。但这没有任何区别。我附上了新的 phpmyadmin 服务器状态截图并更新了代码
【解决方案2】:

一旦您使数据库连接池正常工作并保持池的大小较小,下一步要提高性能就有点困难了。但这将产生巨大的影响。

您要做的是:将您的 UPDATE 操作放入应用程序内的一个简单队列中。也就是说,将你的updateWhatever( car_id, whatever )函数修改为push()它们的SQL语句和参数放到一个共享数组中。

然后,编写一个函数,使用shift() 从数组中获取这些项目并在 dbms 上一个接一个地运行它们。当数组中有多个项目时,将这些项目包装在一个 database transaction 中。

这对性能有很大帮助:MySQL 服务器处理 INSERT 和 UPDATE 操作的大部分工作发生在这些操作被提交时。 (如果您没有显式启动事务,MySQL 会为每个操作使用自动提交。)因此,如果您将它们捆绑在包含几十个操作的事务包中,则可以减少 MySQL 的负载。

它还减少了您的应用程序在不同连接上同时处理许多 UPDATE 操作的潜在需求。这反过来又减少了对表访问权的争用。

(确实,如果数组很大,对数组的shift() 操作会影响内存中的性能。如果这是一个问题,您可以替换易于编程的数组/push() /shift() 带有专用队列包的队列规则。)

【讨论】:

  • Helo Jones,谢谢您的建议。您是否可以共享一些代码作为示例。例如,如果可能的话,您可以在我的代码中修改如何完成。我在 MySQL 中不是那么好。谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-13
  • 1970-01-01
相关资源
最近更新 更多