在node.js应用程序中,我正在使用由redis支持的kue队列库。作业完成后,我将其从队列中删除。在一夜之间运行大约70,000个工作后,redis内存使用量大约为30MB。数据库中仍有18个失败的作业,队列长度目前为零 - 作业的处理速度比排队的速度快。 Redis没有以任何其他方式使用。
即使我正在删除已完成的作业,为什么redis内存使用量不断增加? Coffeescript代码:
gaemodel.update = (params) ->
job = jobs.create "gaemodel-update", params
job.attempts 2
job.save()
job.on "complete", ->
job.remove (err) ->
throw err if err
console.log 'completed job #%d', job.id
答案 0 :(得分:19)
如果排队系统出现内存消耗问题,并且100%肯定所有排队的项目都已从商店中删除而且没有进入异常/错误队列,那么最可能的原因是事实上,排队率远远高于出列率。
Redis使用通用内存分配器(jemalloc,ptmalloc,tcmalloc等等)。这些分配器不一定将内存返回给系统。当释放一些内存时,分配器倾向于保留它(为将来的分配重用它)。当随机分配许多小对象时尤其如此,这通常是Redis的情况。
结果是在给定时间点内存消耗的峰值将导致Redis累积内存并保留它。该内存不会丢失,如果发生另一个内存消耗峰值,它将被重用。但从系统的角度来看,内存仍然分配给Redis。对于排队系统,如果您将项目排队的速度超过了它们的排队速度,那么内存消耗就会达到峰值。
我的建议是检测应用程序以定期时间间隔获取和记录队列长度,以检查队列中项目数量的变化(并确定峰值)。
更新:
我用kue测试了一些东西,以了解它在Redis中存储的内容。实际上,数据结构非常复杂(字符串,集合,zset和散列的混合)。如果您查看Redis,您会发现以下内容:
q:job:nnn (hash, job definition and properties)
q:search:object:nnn (set, metaphone tokens associated to job nnn)
q:search:word:XXXXX (set, reverse index to support job full-text indexing)
q:jobs:inactive (zset, all the unprocessed jobs)
q:jobs:X:inactive (zset, all the unprocessed jobs of job type X)
q:jobs:active (zset, all the on-going jobs)
q:jobs:X:active (zset, all the on-going jobs of job type X)
q:jobs:complete (zset, all the completed jobs)
q:jobs:X:complete (zset, all the completed jobs of job type X)
q:jobs:failed (zset, all the failed jobs)
q:jobs:X:failed (zset, all the failed jobs of job type X)
q:jobs:delayed (zset, all the delayed jobs)
q:jobs:X:delayed (zset, all the delayed jobs of job type X)
q:job:types (set, all the job types)
q:jobs (zset, all the jobs)
q:stats:work-time (string, work time statistic)
q:ids (string, job id sequence)
我根本不知道Coffeescript,所以我尝试使用普通的旧Javascript重现问题:
var kue = require('kue'),
jobs = kue.createQueue();
jobs.process( 'email', function(job,done) {
console.log('Processing email '+JSON.stringify(job) )
done();
});
function create_email(i) {
var j = jobs.create('email', {
title: 'This is email '+i
, to: 'didier'
, template: 'Bla bla bla'
});
j.on('complete', function() {
console.log('complete email job #%d', j.id);
j.remove(function(err){
if (err) throw err;
console.log('removed completed job #%d', j.id);
});
});
j.save();
}
for ( i=0; i<5; ++i )
{
create_email(i);
}
kue.app.listen(8080);
我运行此代码,检查处理后Redis中剩余的内容:
redis 127.0.0.1:6379> keys *
1) "q:ids"
2) "q:jobs:complete"
3) "q:jobs:email:complete"
4) "q:stats:work-time"
5) "q:job:types"
redis 127.0.0.1:6379> zrange q:jobs:complete 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
所以似乎完成的工作保存在q:jobs:complete和q:jobs:X:尽管已删除作业,但仍然完成。我建议你在自己的Redis实例中检查这些zsets的基数。
我的解释是在发出'completed'事件之后发生这些zset的管理。因此,正确删除了作业,但是它们的ID在之后的那些zset中插入。
解决方法是避免依赖每个作业事件,而是使用每个队列事件来删除作业。例如,可以进行以下修改:
// added this
jobs.on('job complete', function(id) {
console.log('Job complete '+id )
kue.Job.get(id, function(err, job) {
if (err) return;
job.remove(function(err){
if (err) throw err;
console.log('removed completed job #%d', job.id);
});
});
});
// updated that
function create_email(i) {
var j = jobs.create('email', {
title: 'This is email '+i
, to: 'didier'
, template: 'Bla bla bla'
});
j.save();
}
修复程序后,Redis中的内容要好得多:
redis 127.0.0.1:6379> keys *
1) "q:stats:work-time"
2) "q:ids"
3) "q:job:types"
您可以使用Coffescript中的类似策略。
答案 1 :(得分:2)
很高兴看到你修复了你的问题。在任何情况下,下次Redis出现内存问题时,您的第一个调用端口应该是“INFO”redis命令。此命令将告诉您有价值的信息,例如
used_memory:3223928 used_memory_human:3.07M used_memory_rss:1916928 used_memory_peak:3512536 used_memory_peak_human:3.35M used_memory_lua:37888 mem_fragmentation_ratio:0.59
或者
DB0:键= 282,期满= 27,avg_ttl = 11335089640
在任何特定时刻,了解内存和键空间的状态非常方便。
答案 2 :(得分:0)
实际上问题出在旧版本的节点上。升级到0.6.x链解决了内存消耗问题。