Redis 列表(List)是一种简单的字符串列表,它的底层实现是一个双向链表。
在生产环境中,许多公司都将 Redis 列表应用于轻量级消息队列。在本文中,我们将探讨如何使用 List 命令实现消息队列的功能,并深入分析消费者线程模型。
1 核心流程
生产者使用
LPUSH key element[ element...]
将消息插入到队列的头部,如果 key 不存在则会创建一个空的队列再插入消息。
如下,生产者向队列 queue 先后插入了 「Java」、「勇哥」、「Go」,返回值表示消息插入队列后的个数。
消费者使用
RPOP key
依次读取队列的消息,先进先出,所以 「Java」会先读取消费:
接下来,我们可以通过 spring-data-redis API 演示生产消费流程:
- 生产者
- 消费者
我们启动一个独立的线程从队列中读取消息(RPOP 命令),读取成功之后,消费消息,若没有消息,则休眠一会,下一次循环再继续。
上图的伪代码中,
while(true)
循环内不停地调用
RPOP
指令,当有消息时,可以及时处理,但假如没有读取到消息,则需要休眠一会。
这里要加休眠,主要是为了减少空读的频率,避免 CPU 无意义的消耗。
有什么更优化的方式吗? 有,那就是使用 Redis 阻塞读取 List 的命令。
Redis 提供了
BLPOP、BRPOP
阻塞读取的命令,消费者在在读取队列没有数据的时自动阻塞,直到有新的消息写入队列,才会继续读取新消息执行业务逻辑。
参数 0 表示阻塞等待时间无限制。
如图,我们启动一个消费线程永动机,消费线程拉取消息后,执行消费逻辑。
这种消费者线程模型非常容易理解,同时也非常适合顺序消费的模式。同时,假如我们在消费消息时,服务器宕机或者断电,可能丢失一条消息。
接下来,我们想一想,有没有消费速度更高的消费模型吗? 笔者根据过往的经历,列举三种模式:
...当我们分析消费者线程模型时,无论我们使用哪种方式,假如服务器突然宕机、或者物理机断电,则会丢失消息。
笔者推荐两种方式:
1、平滑停服
平滑停服是指在停止应用程序时,尽量避免中断正在进行的请求或任务,尽量让正在进行的任务处理完成,并且不再接收新的任务,等所有任务执行完成后关闭应用。
在 Unix/Linux 系统中,可以使用
kill
命令发送信号给运行中的进程。
常见的信号有:
-
SIGTERM
(15):请求进程终止,可以被捕捉和处理,用于优雅地停止进程。 -
SIGKILL
(9):强制终止进程,不能被捕捉或忽略。 -
SIGQUIT
(3):进程退出并生成核心转储(core dump)。
为了实现平滑停服,可以使用 Java 的
Runtime.getRuntime().addShutdownHook
方法注册一个关闭钩子(shutdown hook)。当 JVM 接收到
SIGTERM
信号时,关闭钩子会被执行,从而可以在应用程序停止前执行一些清理工作。
我们可以在钩子里,关闭拉取线程池,优雅关闭消费线程池等,这样可以尽量避免丢失消息。
2、定时任务补偿
使用 List 做消息队列,不可避免的会有消息丢失,所以我们需要用定时任务做补偿,每隔一段时间去业务表里查询业务状态机,若状态机不符合条件,则触发补偿策略。
标签:游戏攻略