如何使用Redis实现一个任务队列,并防止同类型任务并发执行

场景介绍

今天的业务中遇到了这样一个场景:

  1. 某些类型的任务由专门的服务负责执行,且执行时间相对较长,因此需要对这些任务进行排队逐一处理。
  2. 此外,由于同种类型的两个任务之间可能需要按照先后顺序执行,因此还需要防止在集群环境下同一类型的多个任务出现并发执行的情况。

下面介绍我通过Redis解决上述两个问题的思路。

使用Redis实现任务队列

假设服务A为任务执行的调度方,服务B为任务的执行方,由服务A指定服务B需要执行哪些任务;当服务B集群中的机器全部都在执行任务时,后续来的任务需要在队列中按先后顺序进行排队,如图所示。如何使用Redis实现一个任务队列,并防止同类型任务并发执行

下面介绍Redis的两个命令:

  • LPush :Redis LPush 命令将一个或多个值插入到列表头部。如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。 当 key 存在但不是列表类型时,返回一个错误。
  • BRPop :Redis BRPop 命令移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

借助 Redis 的 List ,我们可以实现一个任务队列,由服务A执行入队操作,服务B执行出队操作。服务A使用 LPush 将所需执行的任务从队列左侧不断入队,服务B使用 BRPop 从队列中不断取出并执行相应任务,若某一时刻服务B全部都在执行任务中,则后续新增的任务将会在队列中排队。

使用Redis防止同类型任务并发执行

除了任务排队,业务上还有另一个要求:由于某种原因,同类型的任务不能并行运行。因此当服务B集群中1号机器(服务B-1)正在执行类型1的2号任务(类型1任务2)时,即使类型1服务1到达队列最右侧,且服务B-2处于空闲状态,也不能执行类型1任务1。

如何满足这一需求呢?可以借助Redis通过一个简单的锁实现。如何使用Redis实现一个任务队列,并防止同类型任务并发执行

 

如图所示,当服务B-1开始执行类型1任务2时,服务B-1会将“类型1”作为 Key ,通过 incr 命令在Redis中创建一个键值对,键为“类型1”,值为1,可以将这个键值对看作一把锁,因此此时服务B-1持有了“类型1”的锁。

随后,类型1任务1到达队尾,且此时服务B-2处于闲置状态,因此服务B-2将类型1任务1取出。服务B-2获取到类型1任务1后,通过 incr 命令在Redis中创建或修改一个键值对,此命令执行完成后服务B-2会得到该键值对的最新值。此时,若值为1,则证明目前没有服务持有“类型1”的锁,则服务B-2可以持有“类型1”的锁,并执行类型1任务1;若值不为1,则代表此时有其他服务正在执行类型1的其他任务,因此服务B-2不可执行类型1任务1。这时,类型1任务1尚未被执行,因此需要使用 LPush 命令将其重新入队列并等待被执行。

至此,我们便实现了需求中所要求的任务需要排队执行,且相同类型的任务不可并行执行。