Node.js 100%CPU - gettimeofday调用

时间:2014-03-12 06:24:17

标签: javascript linux node.js strace

我有一个长时间运行的node.js进程,有时会跳转到100%CPU并停止响应请求。最近一次这样做我将strace附加到流程中,这就是我所看到的:

Process 19523 attached - interrupt to quit
gettimeofday({1394558574, 234192}, NULL) = 0
gettimeofday({1394558574, 235381}, NULL) = 0
gettimeofday({1394558574, 306460}, NULL) = 0
mmap(0x3edab823a000, 2097152, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x3edab823a000
munmap(0x3edab823a000, 811008)          = 0
munmap(0x3edab8400000, 237568)          = 0
mmap(0x3edab8300000, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x3edab8300000
gettimeofday({1394558574, 316435}, NULL) = 0
gettimeofday({1394558574, 316576}, NULL) = 0
gettimeofday({1394558574, 316677}, NULL) = 0
gettimeofday({1394558574, 316775}, NULL) = 0
gettimeofday({1394558574, 316879}, NULL) = 0
gettimeofday({1394558574, 316978}, NULL) = 0
gettimeofday({1394558574, 317439}, NULL) = 0
gettimeofday({1394558574, 317537}, NULL) = 0
gettimeofday({1394558574, 318139}, NULL) = 0
gettimeofday({1394558574, 318234}, NULL) = 0
gettimeofday({1394558574, 318343}, NULL) = 0
gettimeofday({1394558574, 318437}, NULL) = 0
gettimeofday({1394558574, 318530}, NULL) = 0
gettimeofday({1394558574, 318624}, NULL) = 0
gettimeofday({1394558574, 319135}, NULL) = 0
gettimeofday({1394558574, 319648}, NULL) = 0
gettimeofday({1394558574, 319769}, NULL) = 0
gettimeofday({1394558574, 319975}, NULL) = 0
futex(0x7f5b380008c8, FUTEX_WAKE_PRIVATE, 1) = 1
gettimeofday({1394558574, 322266}, NULL) = 0
gettimeofday({1394558574, 322426}, NULL) = 0
gettimeofday({1394558574, 322520}, NULL) = 0
gettimeofday({1394558574, 322759}, NULL) = 0
gettimeofday({1394558574, 322853}, NULL) = 0
gettimeofday({1394558574, 322995}, NULL) = 0
futex(0x7f5b380008c8, FUTEX_WAKE_PRIVATE, 1) = 1
gettimeofday({1394558574, 417614}, NULL) = 0
gettimeofday({1394558575, 386566}, NULL) = 0
gettimeofday({1394558575, 387704}, NULL) = 0
gettimeofday({1394558575, 463410}, NULL) = 0
mmap(0x24cfd260f000, 2097152, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x24cfd260f000
munmap(0x24cfd260f000, 987136)          = 0
munmap(0x24cfd2800000, 61440)           = 0
mmap(0x24cfd2700000, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x24cfd2700000

这是很多gettimeofday次来电,而不是其他!什么可能导致node.js像这样被卡住?

更新:我从较旧版本的node.js升级到10.29(我认为),这就消失了。我刚刚升级到10.33,问题又回来了。这次我在调试方面取得了一些进展。第一次:

$ sudo strace -p 11812 -c
Process 11812 attached - interrupt to quit
Process 11812 detached
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.000192           0      2780           gettimeofday
  0.00    0.000000           0      1390           getrusage
  0.00    0.000000           0        31           futex
  0.00    0.000000           0      1390           clock_gettime
------ ----------- ----------- --------- --------- ----------------
100.00    0.000192                  5591           total

Node.js: How to attach to a running process and to debug the server with a console?我发现有关将节点调试器附加到正在运行的进程的问题,这是我得到的地方:

$ sudo kill -s SIGUSR1 11812
$ sudo node debug -p 11812
connecting... ok
break in timers.js:79
  77 }
  78
  79 function listOnTimeout() {
  80   var msecs = this.msecs;
  81   var list = this;
debug> bt
#0 timers.js:79:23

所以它看起来肯定与计时器有关,但我不知道如何进一步向上移动以找出我的代码中的问题被触发的位置。

6 个答案:

答案 0 :(得分:8)

我的猜测是有人手动实现阻止“setTimeout”。如果有人不想释放对主JS线程的控制以防止潜在的竞争条件,则可能发生这种情况。

由于显而易见的原因,这对于生产代码来说是一种不好的做法,但我在调试中偶尔使用它来强制异步进程按特定顺序执行。

你可以找到像这样愚蠢的东西:

var mockAsyncFunction = function (cb) {
    setTimeout(function () {
        cb(null, 'dummy_result')
    }, 5000);
};

var myResult = null;
mockAsyncFunction(function (err, result) {
    myResult = result;
});
var timeOut = 10000; // timeout in 10 sec.
var timeStart = new Date().getTime();


while (1) {
    if (new Date().getTime() - timeStart > 10000) {
        break;
    }
}
console.log('DONE');

或者更接近nextTick递归的东西,如:

var timeStart = new Date().getTime();

var recurseUntilDone = function () {
    if (new Date().getTime() - timeStart < 10000) {
        process.nextTick(recurseUntilDone);
    } else {
        console.log('Done recursing');
    }
};

recurseUntilDone();

答案 1 :(得分:4)

当CPU为100%时,使用node-inspector能够暂停你的代码 - 我的赌注也是在一段时间已经过去的某个时候执行了一些糟糕的循环检查状态,但是很难找到它们。 / p>

在启动节点时使用--debug附加调试器(即node index.js --debug)并在单独的窗口中运行node-inspector。使用Chrome连接到您的调试会话(url从node-inspector命令输出)并等到问题发生并暂停执行,您应该能够找到它。

答案 2 :(得分:2)

我们在生产和开发方面也看到了这一点。也在寻找答案。 我们开始调查节点运行时,但问题是如此罕见,以至于没有得到任何优先级。因为它完全是cpu绑定的,没有系统调用,所以它很难用strace进行捕获。

它不会成为定时循环检查状态,因为新的Date()。getTime()循环不会对gettimeofday进行任何调用(在节点v0.10.29中;它只在一个系列中进行了一系列纳米睡眠pid和futex只调用另一个。聪明,实际上)。 Date.now()也一样。

答案 3 :(得分:0)

不知道它是否相关,但这是破解节点进程的一种简单方法:当数组中包含数据时,将数组用作队列。处理速度非常慢,100%cpu使用。

//a = [];
a = new Array();

for (i=0; i<87370; i++) a.push({});

for (i=0; i<1000000; i++) {
    a.shift();
    a.push({});
    if (i%1000 === 0) {
    process.stdout.write(".");
    //global.gc();
    }
}

// node v0.10.29: 10k:  0.15 sec
//        80k:  0.17 sec
//        87369: 0.17 sec
//            87370: instant 41k, then .9k per second
//        87400: Array: instant 41k, then .9k per second
//        87400: []: instant 28k, then .9k per second
//        87400: Array, pushing 1 or 'x': .9k per second from the start
// node v0.11.13: 10k: 1.94 sec
//        16683: 3.87 sec
//        16884: uniform 237.16 sec

我跑perf record -F 997 -g node-v0.11.13 --perf-basic-prof script.js但几乎所有的时间都归因于Builtin_ArrayShift(5-6个不同的十六进制位置来自〜)

请注意,该行为有一个切换点,低于该切换点它快,高于它的速度。 0.10和0.11的行为是不同的; 0.11似乎有两个切换点(3个不同的点速度)。切换点似乎在两组运行之间保持不变。

无论是否调用global.gc()(使用--expose_gc开关)都无关紧要。如果推/移是可持续的或在上面的阻塞循环中无关紧要。

编辑:奇怪的是,这是数据和循环计数相关的。较短的循环运行得更快(即使它们被分成1k批次)。

此外,当存在这种情况时,移位/推送到其他(空)列表也会减慢到相同的.9k操作/秒(节点v0.10.29),但奇怪的是它们可以重叠:10个并发移位/追加循环将每个插入.9k项/秒,即总共9k /秒。 (所以有资源可以提高10倍的吞吐量 - 那么内部油门呢?)

编辑:不是内部油门;慢线程正在使用0.8 - 1.2 ms的cpu固态块,所有其他线程都被阻塞直到它完成。由于并发线程必须定期生成(我使用setImmediate),它们会在cpu hog后面被阻塞并相应地进展缓慢。

答案 4 :(得分:0)

来自较大的哈希或数组的显式delete,如清理内部数据容器,也可能导致这样的症状。固有的节点操作可能非常慢:

h = {}
for (i=0; i<200000; i++) h[i] = i;     // 25,000,000 / sec
for (i=0; i<1000; i++) delete h[i];    // 11,000 / sec
for (i=0; i<200000; i++) delete h[i];  // 7,700 / sec

a = new Array();
for (i=0; i<200000; i++) a[i] = i;     // 50,000,000 / sec
for (i=0; i<1000; i++) delete a[i];    // 10,000 / sec
for (i=0; i<200000; i++) delete a[i];  // 8,000 / sec
// and out of curiousity...
for (i=0; i<200000; i++) a[i];         // 250,000,000 / sec
for (i=1; i<200000; i++) a[i-1] = a[i]; // 180,000,000 / sec

从后到前删除速度提高了2倍,但仍然非常慢。 (注意:分配到一个新的数组是50米/秒,但进入[]只有25米/秒)

减速是O(n ^ 2):将数组大小加倍到400k运行时的四倍。每个删除操作都是散列/数组大小的O(n)。

400k run(100秒)的strace显示没有系统调用,并且与上面看到的gettimeofday()模式不匹配。

...
14:08:36.327886 munmap(0x44200000, 802816) = 0
14:08:36.328145 munmap(0xf7100000, 1183744) = 0
14:08:36.328483 clock_gettime(CLOCK_REALTIME, {1418584116, 328495018}) = 0
14:08:36.328540 clock_gettime(CLOCK_REALTIME, {1418584116, 328551680}) = 0
14:08:36.328581 clock_gettime(CLOCK_REALTIME, {1418584116, 328592628}) = 0
14:08:36.328633 futex(0x8e79a7c, FUTEX_WAKE_PRIVATE, 1) = 1
14:09:03.130336 clock_gettime(CLOCK_REALTIME, {1418584143, 130406483}) = 0
14:09:03.130479 clock_gettime(CLOCK_REALTIME, {1418584143, 130503357}) = 0
14:09:03.130560 futex(0x8e79a7c, FUTEX_WAKE_PRIVATE, 1) = 1
14:09:37.090127 clock_gettime(CLOCK_REALTIME, {1418584177, 90195108}) = 0
14:09:37.090271 clock_gettime(CLOCK_REALTIME, {1418584177, 90296760}) = 0
14:09:37.090355 futex(0x8e79a7c, FUTEX_WAKE_PRIVATE, 1) = 1
14:10:17.588552 mmap2(0x3f302000, 4231168, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x3f302000
14:10:17.588694 munmap(0x3f302000, 1040384) = 0
14:10:17.588768 munmap(0x3f709000, 8192) = 0
...

答案 5 :(得分:0)

如果仍然有人遇到此问题,我也会遇到此问题(状态如此之重,以至于我的“重复”节点每秒都会开始丢弃指令)。

在我的情况下,这是由“串行”节点引起的-您可以通过启动htop命令以监视CPU使用情况来检查它,然后在删除“串行输入”节点后重新部署所有节点。

CPU使用率应立即下降。