用Redis做简单的任务队列

队列本身其实是个有序的列表,而Redis是支持list的,我们可以查看Redis的官方文档 http://redis.io/commands#list,其中我们可以对这个队列的两端分别进行操作,所以其实Redis中的list即可以当做普通的先进先出的queue,也可以作为先进后出的stack。

如果当做队列来用,我们可以用LPUSH(头部插入)和RPOP(尾部弹出)或RPUSH(尾部插入)和LPOP(头部弹出),这两种方式都可以,只要是搭配使用即可。但我们平时一般搭配使用 LPUSH和RPOP。
一般开发的时候我们会分为生产者和消费者,生产者生产消息,消费者获取消息进行处理。

Producer:
redis->lpush(‘joblist’, ‘this is Job-1’);
redis->lpush(‘joblist’, ‘this is Job-2’);
….

 

Cosumer:
job = redis->rpop(‘joblist’);// return Job-1
done the job….
job = redis->rpop(‘joblist’);// return Job-2
done the job…

正常情况下上面这些都是没问题的,但是我们也经常遇到这种情况,当任务进行到一半(即任务没有完全执行完)的时候,突然服务器宕机或者网络中断,这时候任务其实是没有真正完成的,这时候出现队列中的任务“丢失”了,并不是说队列任务真的丢失了,而是我们把任务从队列拿出来之后并没有完成这个任务,这时候我们就需要考虑如何能在任务真正完成的时候才把任务从队列中删除。

幸运地是,redis其实给我们提供了这样的可能。继续翻看redis的官方文档,我们发现RPOPLPUSH,从字面含义来解释就是尾部弹出头部插入,它后面跟两个参数,一个是弹出的list,一个是要插入的list,而且文档上告诉我们它可以实现可靠的队列。我们可以把任务从队列中取出来放到另外一个执行中的队列,等到任务真正完成之后再从执行中的队列中删除。

那么上面的代码我们可以修改成如下,producer不变:

Cosumer:
job = redis->rpoplpush(‘joblist’, ‘job-doing’);// 把Job-1从joblist转移到job-doing
done the job….// 完成Job-1
redis->lrem(‘job-doing’, 1, job);// 等Job-1完成之后把它从job-doing队列中删除

这样如果Job失败,那Job的任务还存在在队列job-doing中,我们可以单独启一个进程来扫描job-doing列表,如果一个job长时间在队列中,则重新执行该Job或重新插入的job列表。

当然,这也不是完全100%靠谱的解决方案,因为如果存在很多相同的job,那我们从job-doing删除job的时候就无法确认是哪个job真正应该删除,但是像我们上面描述的,既然Job是相同的,那我们删除哪个也无所谓了,反正执行的结果都一样。上面的解决方案还有一个问题,我们必须要长时间地监控job-doing列表,这需要额外的资源,还有一种方式是job-doing和job用同一个列表,即:

Cosumer:
job = redis->rpoplpush(‘joblist’, ‘jobllist’); 把Job-1从joblist尾部转移到joblist头部
done the job….// 完成Job-1
redis->lrem(‘job-doing’, 1, job);// 等Job-1完成之后把它从joblist头部删除

其实,文章写到这里,大家应该明白了,这样一来,我们利用循环队列,即可实现可靠的队列。当然,相比较世面上比较成熟的队列,例如RabbitMQ、Beanstalkd、IronMQ等还是有很多的不足,但是如果是简单地项目或者项目本身就已经在使用redis又不想添加新的组件,试试redis list也不错。

打赏