redis应用 2:延时队列
发布网友
发布时间:2024-09-30 12:05
我来回答
共1个回答
热心网友
时间:2024-09-30 16:19
我们通常倾向于采用 Rabbitmq 和 Kafka 作为中间件,以实现应用程序间的异步消息传递。然而,这两个中间件功能丰富,可能超出了许多人的理解。
熟悉 Rabbitmq 的朋友都知道,它操作复杂。发消息前需要创建 Exchange 和 Queue,并通过规则将它们绑定起来。同时,还需指定 routing-key 和控制头部信息。消费者在消费消息前,也需要进行类似的繁琐过程。尽管大多数情况下,我们的消息队列只有一组消费者,但仍然需要经历这些步骤。
Redis 的出现,为解决这一问题提供了便利。对于只有一组消费者的消息队列,使用 Redis 可以轻松实现。Redis 的消息队列并非专业的消息队列,缺乏高级特性,无法保证消息的可靠性,因此不适用于对消息可靠性要求极高的场景。
Redis 的 list(列表)数据结构常用于异步消息队列。使用 rpush/lpush 操作入队列,使用 lpop 和 rpop 来出队列。
客户端通过 pop 操作获取消息进行处理。处理完毕后,再次获取消息进行处理,如此循环。这就是作为队列消费者的客户端的生命周期。
然而,如果队列空了,客户端将陷入 pop 的死循环,不停地尝试获取消息。这不仅会浪费资源,还会导致客户端的 CPU 使用率和 Redis 的 QPS 增高。
通常,我们可以通过 sleep 来解决这个问题。让线程暂停 1 秒,以降低 CPU 使用率和 Redis 的 QPS。
然而,这种方法会导致消息延迟增大。如果有多个消费者,这个延迟会降低。但更好的解决方案是使用 blpop/brpop。
这两个指令的前缀字符 b 代表 blocking(阻塞读)。在队列没有数据时,它们会立即进入休眠状态。一旦数据到来,则立刻醒来。使用 blpop/brpop 替代 lpop/rpop,可以完美解决这个问题。
然而,这并非完美方案。如果线程一直阻塞,Redis 的客户端连接将成为闲置连接,服务器可能会主动断开连接,以减少闲置资源占用。此时,blpop/brpop 会抛出异常。
因此,在编写客户端消费者时,需要小心捕获异常,并进行重试。
上节课我们介绍了分布式锁的问题,但没有提到客户端在处理请求时加锁失败的情况。通常有三种策略来处理加锁失败:直接抛出异常、sleep 和延时队列。
延时队列可以通过 Redis 的 zset(有序列表)实现。我们将消息序列化成一个字符串作为 zset 的 value,消息的到期处理时间作为 score,然后使用多个线程轮询 zset,获取到期的任务进行处理。
Redis 的 zrem 方法是多线程多进程争抢任务的关键。通过 zrem 来决定唯一的属主。同时,要确保 handle_msg 的异常捕获,避免因个别任务处理问题导致循环异常退出。
为了优化这个逻辑,可以考虑使用 lua scripting 将 zrangebyscore 和 zrem 一起进行原子化操作,避免多个进程之间争抢任务时的浪费。