如何在 Redis 中实现队列和栈数据结构?

Sherwin.Wei Lv7

如何在 Redis 中实现队列和栈数据结构?

回答重点

可以通过 List 类型 来实现 队列

实现队列(FIFO)

  • 队列是一种 先进先出(FIFO) 的数据结构。在 Redis 中,可以使用 LPUSHRPOP 命令组合来实现队列。
  • LPUSH 向列表的左侧推入元素,而 RPOP 从列表的右侧弹出元素,这样可以保证最先进入的元素最先被弹出。

实现栈(LIFO)

  • 栈是一种 后进先出(LIFO) 的数据结构。在 Redis 中,可以使用 LPUSHLPOP 命令组合来实现栈。
  • LPUSH 向列表的左侧推入元素,而 LPOP 从列表的左侧弹出元素,这样可以保证最后进入的元素最先被弹出。

扩展知识

队列的实现与应用场景

FIFO 队列的实现通过 LPUSHRPOP,或者 RPUSHLPOP,确保数据按插入顺序出队。
例如:

1
2
3
LPUSH myqueue "task1"
LPUSH myqueue "task2"
RPOP myqueue # 弹出 "task1"

应用场景

  • 可以用来实现简单的任务队列,例如,将任务按顺序插入到队列中,由多个消费者按照插入顺序依次处理任务。
  • 在消息队列中,可以通过阻塞操作 BLPOPBRPOP 来实现消费等待,使得当队列为空时消费者会阻塞直到新任务加入。

栈的实现与应用场景

LIFO 栈的实现通过 LPUSHLPOP,这样确保最后加入的元素最先被弹出。
例如:

1
2
3
LPUSH mystack "item1"
LPUSH mystack "item2"
LPOP mystack # 弹出 "item2"

应用场景

  • 栈的操作可以用于实现回溯功能,例如浏览器中的页面后退功能,每次访问新页面时将页面压入栈,后退时从栈顶弹出最后访问的页面。

队列使用注意点

这里需要注意,如果 redis list 内没数据,则执行 rpop/lpop 操作的时候,会返回 null 值。

所以业务代码消费逻辑只能使用死循环来消费队列,伪代码如下:

1
2
3
4
5
6
7
while(true) {
msg = redis.rpop("queue");
if (msg == null) {
continue;
}
process(msg);
}

如果队列里长时间没有消息,这样会导致应用 cpu 空转,疯狂消耗 cpu 资源,还会频繁请求 redis,给缓存上上压力

所以需要改造下,比如加个睡眠时间:

1
2
3
4
5
6
7
8
while(true) {
msg = redis.rpop("queue");
if (msg == null) {
sleep(1s);
continue;
}
process(msg);
}

但是这样消息又会有延迟了,上面就会导致消息延迟 1s,如果缩短睡眠的时间,可能又会导致空转太频繁了。

所以 redis 提供了阻塞式消费 list 的接口,即 brpop/blpop,这里的 b 指的就是 block,并且还支持超时时间,即等待一段时间还未接收到消息,先返回 null,给执行线程处理别的操作的机会。

1
2
3
4
5
6
7
8
while(true) {
msg = redis.brpop("queue", 5);
if (msg == null) {
sout("写一些日志,或者其他什么动作")
continue;
}
process(msg);
}

优先级队列的实现

Redis 的 List 类型适合用于基本的队列和栈,但如果需要实现优先级队列,List 类型并不是最优的选择。

可以考虑使用 Sorted Set 来实现优先级队列,通过分数来表示优先级,然后使用 ZRANGEZPOPMIN 命令获取优先级最高的元素。

例如,使用 ZADD 为任务设置优先级:

1
2
3
ZADD priority_queue 1 "low_priority_task"
ZADD priority_queue 0 "high_priority_task"
ZPOPMIN priority_queue # 弹出 "high_priority_task"

List 实现队列和栈的优势与劣势

优势

  • Redis 的 List 提供了高效的插入和删除操作(O(1) 时间复杂度),适用于对队列和栈的快速操作需求。
  • 支持阻塞操作,可以实现生产者-消费者模式,适合任务调度和消息传递。

劣势

  • Redis List 并不适合过大的数据量,尤其是在高并发场景中,List 的长度过大会影响操作效率。
  • 对于需要在队列中查找、删除特定元素的场景,List 的效率不高,因为 Redis List 的随机访问和删除操作时间复杂度为 O(N)。
Comments