【问题标题】:Node.js redis subscription memory leakageNode.js redis 订阅内存泄漏
【发布时间】:2014-01-20 19:45:53
【问题描述】:

我是“服务器端 JS”的新手,找不到使用 node.js 和 Redis-Sub 进行长轮询的示例。

以下代码运行良好,但今天我注意到 RAM 使用量为 650MB,而代码仅使用了 6 天。

var http     = require('http'),
    redis    = require('redis'),
    client   = redis.createClient();

client.subscribe("example");

http.createServer(function (req, res) {

    res.setHeader('Access-Control-Allow-Origin', 'https://mywebsite.com');
    res.setHeader('Access-Control-Allow-Methods', 'GET');
    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
    res.setHeader('Access-Control-Allow-Credentials', true);

    res.writeHead(200, {'Content-Type': 'application/json'});

    client.on("message", function (channel, message) {

        res.end( JSON.stringify( message ) );

    });

}).listen(8080);

有人可以指出内存泄漏并解释一下吗?

我的 node.js 版本是:v0.10.21

【问题讨论】:

  • 函数function(req, res)每次服务器收到请求时运行。每次运行时,都会由client.on("message", ...) 设置一个处理程序。这些处理程序不会被删除;因此,每次收到请求时,内存使用量都会增加。不仅如此,每次redis客户端收到消息,都会运行function(channel, message)这个函数。如果res 已经被发送,大概它不会做任何事情,但如果有很多处理程序,这可能会产生很大的开销。 (在这种情况下,您会看到每个请求的处理都有延迟。)
  • 你想做什么?我大致知道我认为它会做什么,而且我看不出有任何合乎逻辑的理由这样做。客户端请求和redis数据有什么联系?

标签: node.js redis


【解决方案1】:

“内存泄漏”来自代码client.on。您在请求/响应函数中调用它。 client.on 是一个发射器(参见 redis 源代码,index.js:111(截至今天的 npm 安装)),它使用

定义函数 on

在指定事件的侦听器数组末尾添加一个侦听器。 (Nodejs-Docs:1

所以你不断向客户端添加"message"-functions。将client.on 移到此请求/响应-“循环”之外,它应该停止“泄漏”。

【讨论】:

    【解决方案2】:

    从逻辑上讲,您正在寻找类似以下的内容,所以从这里开始:

    var http     = require('http'),
        redis    = require('redis'),
        client   = redis.createClient();
    
    client.subscribe("example");
    
    var responses = [];
    
    client.on('message', function(channel, message) {
        var res;
        while (responses.length) {
            res = responses.pop();
            res.end(JSON.stringify(message));
        }
    });
    
    http.createServer(function (req, res) {
    
        res.setHeader('Access-Control-Allow-Origin', 'https://mywebsite.com');
        res.setHeader('Access-Control-Allow-Methods', 'GET');
        res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
        res.setHeader('Access-Control-Allow-Credentials', true);
    
        res.writeHead(200, {'Content-Type': 'application/json'});
    
        responses.push(res);
    
    }).listen(8080);
    

    随着您继续收到尚未对消息和end()ed 满意的请求,您的内存需求将会增加,但除此之外,这里并没有很多事情发生。任何真正的内存泄漏都必须从组件模块中引入。

    【讨论】:

      【解决方案3】:

      没错。我想我现在明白了,但是如果我错了,请纠正我:redis 客户端会定期发送更新,这些更新会准确说明其中包含哪些数据。 (因此,如果它永远不会改变,它将永远定期发送相同的消息。)使用当前代码,客户端可以向您的服务器发送请求,并接收 redis 服务器内容的更新。但是客户端必须等到下一次你的服务器从 redis 获取更新。

      客户必须等待的事实感觉不对。我认为它应该立即发送从 redis 收到的最新更新的响应。这应该可以通过一些修改来实现:

      var http     = require('http'),
          redis    = require('redis'),
          client   = redis.createClient();
      
      var lastMessage = null;
      
      client.subscribe("example");
      
      client.on("message", function (channel, message) {
      
          lastMessage = message
      
      });
      
      http.createServer(function (req, res) {
      
          res.setHeader('Access-Control-Allow-Origin', 'https://mywebsite.com');
          res.setHeader('Access-Control-Allow-Methods', 'GET');
          res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
          res.setHeader('Access-Control-Allow-Credentials', true);
      
          res.writeHead(200, {'Content-Type': 'application/json'});
      
          res.end( JSON.stringify( lastMessage ) );
      
      }).listen(8080);
      

      而且上面的代码也没有重复设置redis handler,所以应该不会泄露内存。

      我可以看到的一个缺点是,如果客户端在节点收到 redis 的消息之前请求更新,它将收到null。在这种(可能是不寻常的)情况下,在回复之前等待 redis 可能会更好。

      如果这让您感到困扰,您可以将res.end( JSON.stringify( lastMessage ) ); 行替换为“如果您已经收到redis 的消息,那么res.end( JSON.stringify( lastMessage ) );;否则,设置一个处理程序在redis 发送消息时执行此操作”。处理程序应该设置为只运行一次,然后被删除;您可以使用.once 而不是.on 来做到这一点。

      【讨论】:

      • Redis subscribe 的工作方式类似于,您正在订阅频道并等待新消息。我的代码正在等待带有.on("message, 函数的新消息。当有新消息到来时,它会推送并关闭连接。
      • 我是这么认为的。但我的观点是:使用您的代码版本,向您的服务器发送请求的客户端将不得不等到您的服务器下一次从 redis 收到消息。我认为这不是你想要的,所以我重写了它,以便服务器立即回复从 redis 收到的 previous 消息。但是,如果我错了,而您确实想让客户等待,那么请忽略我的回答并尝试将 client.on("message", ... ) 更改为 client.once("message", ... )。或者,如果您想要其他内容,请详细说明。
      • 我用client.once对其进行了负载测试,它仍然线性增加(从45MB开始,达到80-85MB,GC下降到50MB,然后达到90-95MB,GC下降到60MB ...)。
      • 您的负载测试是否包含来自 redis 的消息?如果不是,那么我预计会发生内存泄漏,因为处理程序仍未被删除。如果即使使用 redis 消息和.once 它也会泄漏内存,那么我没有任何解释。但是,当我说客户端在等待服务器获取 redis 消息时遇到延迟时,我说对了吗?您是否尝试过按照我在回答中所说的方式进行操作?发生了什么?
      猜你喜欢
      • 1970-01-01
      • 2012-03-11
      • 2016-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-24
      相关资源
      最近更新 更多