当集群退出并重新启动时,使用集群和 mariaSQL 100% 的 CPU 使用率

Posted

技术标签:

【中文标题】当集群退出并重新启动时,使用集群和 mariaSQL 100% 的 CPU 使用率【英文标题】:100% CPU usage using clusters and mariaSQL, when cluster is exit and restarted 【发布时间】:2016-10-15 07:03:48 【问题描述】:

在我的 Node.JS 应用程序中,我使用集群来利用我的多核 CPU。我正在使用节点的 mariasql 库与我的数据库进行通信。由于node-mariasql 库不支持池化,我使用第三方-generic-pool 来维护连接池。

我注意到,每当主线程中的连接在未捕获的异常导致其中一个子集群重新启动后关闭时,我们的 CPU 使用率就会上升到 100%。

每当子集群重新启动时,我都会破坏所有 mysql 连接。

节点版本 - v4.2.2 MariaDB 版本 - v10.0.15 node-mariasql 版本 - v0.2.5

可重现的代码 - https://github.com/bsurendrakumar/node-simplex/

代码 sn-ps

创建连接池 ...

var pool = poolModule.Pool(
  name: 'mariadb',
  create: function(callback) 
    var client = new mSQLClient();
    client.connect(dbConfig);
    client.on('error', function(err) 
      callback(err, null);
    );
    client.on('ready', function() 
      callback(null, client);
    );
  ,
  destroy: function(client) 
    if(cluster.isMaster) 
      console.log('Destroying / ending master thread ID -', client.threadId);
    
    if(isDraining) 
      client.destroy();
     else 
      client.end();
    
  ,
  max: dbConfig.maxConn,
  min: dbConfig.minConn,
  idleTimeoutMillis: dbConfig.idleTimeout
);

在主线程上...

console.log('------------------------------------');
console.log('Master Process ID:', process.pid);
console.log('------------------------------------\n\n');

console.log('Creating an extra DB connection on the master thread.\n\n');
getCountries();

// Create a worker for each CPU
for (var i = 0; i < cpuCount; i += 1) 
  cluster.fork();


// Restarting the thread if something exits...
cluster.on('exit', function () 
  cluster.fork();
);

一旦发生未捕获的异常...

// Handle uncaught exceptions...
process.on('uncaughtException', function (err) 
  try 
    console.log('\n--------------');
    console.log(err);
    // Stop the HTTP Server
    console.log('\n--------------');
    console.log('Encountered uncaught exception!');
    console.log('Stopping HTTP server ...');
    if(httpServer) 
      httpServer.close();
    
    console.log('Stopped HTTP server, performing cleanup ...');
    // Call the cleanup function
    cleanUp(function() 
      // Exit!!
      console.log('Cleanup done!');
      restartProcess();
    );
   catch (e) 
    console.log(e);
    restartProcess();
  

  function restartProcess() 
    console.log('Restarting process ...');
    process.exit(1);
  
);

清理功能...

function cleanUp(cbMain) 
  isDraining = true;
  if(pool.hasOwnProperty('_inUseObjects')
    && Array.isArray(pool._inUseObjects)
    && pool._inUseObjects.length > 0) 
      let inUseObjs = pool._inUseObjects;
      let inUseObjsLen = pool._inUseObjects.length;
    for(let i = 0; i !== inUseObjsLen; ++i) 
      inUseObjs[0].destroy();
      pool.release(inUseObjs[0]);
    
  
  pool.drain(function() 
    pool.destroyAllNow(function() 
      return cbMain();
    );
  );


连接池中的最小连接数设置为5。它的所有配置都可以在here下找到。因此,当服务器启动时,通用池将启动 5 个与 MySQL 的连接并将它们保留在其池中。

池中对象的idleTimeout 已设置为 120 秒。这意味着如果池中的对象超过 5 个(因为最少 5 个),并且其中一个在过去 120 秒内没有被使用,它将被销毁。

在服务器启动时,我正在对我们的国家模型进行简单调用以获取国家列表。此代码是here。这将建立与数据库的新连接,因此现在池中将有 6 个 SQL 连接,其中一个将在 120 秒后被清除。

以下是我认为问题在于我们对 mariasql 库的使用的分步过程 -

当服务器启动时,我将进程 ID 记录到控制台。获取主进程 ID,例如 - 20584

通过使用 -ls -l /proc/20584/fd 查看进程正在使用的文件描述符。记下套接字连接。这个的输出看起来像这样 - lrwx------ 1 abijeet abijeet 64 Jun 9 19:24 12 -> socket:[2469914] lrwx------ 1 abijeet abijeet 64 Jun 9 19:24 13 -> socket:[2469917] lrwx------ 1 abijeet abijeet 64 Jun 9 19:24 14 -> socket:[2468106] lrwx------ 1 abijeet abijeet 64 Jun 9 19:24 15 -> socket:[2468109] lrwx------ 1 abijeet abijeet 64 Jun 9 19:24 17 -> socket:[2467206] lrwx------ 1 abijeet abijeet 64 Jun 9 19:24 18 -> socket:[2467208] lrwx------ 1 abijeet abijeet 64 Jun 9 19:24 19 -> socket:[2467210] lrwx------ 1 abijeet abijeet 64 Jun 9 19:24 2 -> /dev/tty lrwx------ 1 abijeet abijeet 64 Jun 9 19:24 20 -> socket:[2467212] lrwx------ 1 abijeet abijeet 64 Jun 9 19:24 21 -> socket:[2467214] lrwx------ 1 abijeet abijeet 64 Jun 9 19:24 22 -> socket:[2467306]

复制几个套接字编号,例如 2467212,然后运行 ​​lsof | grep 2467212。您会注意到这些是与 MySQL 服务器的连接。的输出应该是这样的 - node 20584 abijeet 20u IPv4 2467212 0t0 TCP localhost:57092->localhost:mysql (ESTABLISHED) V8 20584 20585 abijeet 20u IPv4 2467212 0t0 TCP localhost:57092->localhost:mysql (ESTABLISHED) V8 20584 20586 abijeet 20u IPv4 2467212 0t0 TCP localhost:57092->localhost:mysql (ESTABLISHED) V8 20584 20587 abijeet 20u IPv4 2467212 0t0 TCP localhost:57092->localhost:mysql (ESTABLISHED) V8 20584 20588 abijeet 20u IPv4 2467212 0t0 TCP localhost:57092->localhost:mysql (ESTABLISHED)

转到http://127.0.0.1:3000/api/v1/country/list 使服务器崩溃。这将使其中一个子进程崩溃。每当发生未捕获的异常时,我都会进行一些清理并退出。然后我分叉另一个进程来代替刚刚被杀死的进程。清理包括 -

关闭http服务器 关闭通用池中的 MySQL 连接 关闭 winston 记录器流。 等待主线程中的 MySQL 连接关闭。发生这种情况时,我正在向控制台写入日志 - Destroying / ending master thread ID - 4984 检查您的 CPU 使用率,您会发现其中一个 CPU 已达到 100%。 下一次运行,strace -o log.txt -eepoll_ctl,epoll_wait -p 20584。请注意,您可能需要安装 strace。此命令记录 Node.JS 进程进行的所有 epoll_ctl, epoll_wait 系统调用,并将其放入当前工作目录名为 log.txt 的文件中。

打开 log.txt 文件,您会注意到与这些类似的日志 - epoll_wait(5, EPOLLIN|EPOLLHUP, u32=16, u64=16, 1024, 847) = 1 epoll_ctl(5, EPOLL_CTL_DEL, 16, 7ffe441aa850) = -1 EBADF (Bad file descriptor) epoll_wait(5, EPOLLIN|EPOLLHUP, u32=16, u64=16, 1024, 845) = 1 epoll_ctl(5, EPOLL_CTL_DEL, 16, 7ffe441aa850) = -1 EBADF (Bad file descriptor) epoll_wait(5, EPOLLIN|EPOLLHUP, u32=16, u64=16, 1024, 843) = 1 epoll_ctl(5, EPOLL_CTL_DEL, 16, 7ffe441aa850) = -1 EBADF (Bad file descriptor)

这里的文件描述符是16,如果你把它和你之前的ls -l /proc/20584/fdlsof | grep 2467212关联起来,你会发现这属于MySQL连接刚刚关闭。

这让我相信在某个地方,即使与 MySQL 的连接被释放,也有一个文件描述符挂在那里,它仍在使用。我在论坛中发现了各种类似问题的主题 -

https://github.com/joyent/libuv/issues/1099 https://github.com/nodejs/node-v0.x-archive/issues/6271 https://github.com/joyent/libuv/issues/826

【问题讨论】:

【参考方案1】:

您好,这已经发生在我身上……在我的情况下,我的服务器内存不足……而且我的数据库中充满了帖子。那就是……他有很多关于两种解码的信息。 (MyISAM 和 Innodb)在同一家银行...我找到的解决方案是导出此数据库...这是备份...通过仅决定单一编码(Innodb)从头开始创建数据库。并一点一点地检查所有表和可能的错误。

我做的另一个选择是删除旧的并将其放入另一个数据库中。成为另一个网站的一部分。 数据集成将通过两个站点之间的链接...

发送消息! 我希望它对你有帮助! 强烈的拥抱。祝你好运!

【讨论】:

以上是关于当集群退出并重新启动时,使用集群和 mariaSQL 100% 的 CPU 使用率的主要内容,如果未能解决你的问题,请参考以下文章

mysql ndb集群数据节点未同步且对同一查询的不同响应

当我们重新启动 Azure Databricks 群集时,内部会发生啥?

ETCD:运行时重新配置

如何让 Zeppelin 在 EMR 集群上干净地重新启动?

将 master 节点服务器从 k8s 集群中移除并重新加入

Node JS - 记录工作集群最后一个异常