Redis在美团面试中的多重应用:不止是缓存,能否作为消息队列的深入分析

在面试中,Redis的应用场景是一个常见的基础题目,主要考察应聘者对Redis的多种用途的了解。

即使您不打算面试,这些知识在实际开发中也是非常有价值的。

内容来概览:

图片

Redis的多种应用场景

  • 分布式锁:利用Redis实现分布式锁是一种广泛采用的方案。通常,我们会基于Redisson来实现这一功能。
  • 限流:通常通过结合Redis和Lua脚本实现限流。
  • 消息队列:Redis提供的List数据结构可以简单地作为一个队列使用。在Redis 5.0版本中引入的Stream类型更加适合用作消息队列,类似于Kafka,具有主题和消费组的概念,支持消息持久化和确认机制。
  • 延时队列:Redisson提供内置的延时队列,基于Sorted Set实现。
  • 分布式会话:可以利用String或Hash数据类型来保存会话数据,所有服务器均可访问。
  • 复杂业务场景:通过Redis及其扩展(如Redisson)提供的数据结构,我们可以轻松完成许多复杂的业务逻辑,比如使用Bitmap统计活跃用户,以及通过Sorted Set维护排行榜。
  • ……

Redis如何实现分布式锁?

Redis是否可以作为消息队列?

在实际项目中,Redis作为消息队列的使用并不普遍,因此了解这一知识点即可。

结论是:可以使用Redis作为消息队列,但并不推荐。与专用的消息队列相比,Redis存在许多不足之处。

在Redis 2.0之前,若希望使用Redis作为消息队列,仅能通过List实现。

使用 RPUSH/LPOPLPUSH/RPOP 可以实现简单的消息队列:

# 生产者生产消息  
> RPUSH myList msg1 msg2  
(integer) 2  
> RPUSH myList msg3  
(integer) 3  
# 消费者消费消息  
> LPOP myList  
"msg1"  

然而,使用 RPUSH/LPOPLPUSH/RPOP 的方式存在性能问题,我们需要频繁进行轮询来调用 RPOPLPOP 来消费消息。当List为空时,大多数轮询请求都是无效的,从而浪费系统资源。

因此,Redis提供了BLPOPBRPOP等阻塞式读取命令(以B-Blocking开头的命令均为阻塞式),并支持超时参数。如果List为空,Redis服务端不会立即返回结果,而是等待List中有新数据后再返回,或者在最多一个超时时间后返回。如果将超时时间设置为0,则会无限期等待,直到弹出消息。

# 超时时间为 10秒  
# 如果有数据立即返回,否则最多等待10秒  
> BRPOP myList 10  
null  

List实现的消息队列功能过于简单,像消息确认机制等功能需要我们自己实现,而最致命的是缺乏广播机制,消息只能被消费一次。

Redis 2.0引入了发布/订阅(pub/sub)功能,以解决List实现消息队列时没有广播机制的问题。

图片

Redis的发布/订阅(pub/sub)功能

在pub/sub中引入了一个概念,称为频道(channel),其实现便是基于该频道。

pub/sub涉及发布者(Publisher)和订阅者(Subscriber,也称消费者)两个角色:

  • 发布者通过PUBLISH将消息发送到指定频道。
  • 订阅者通过SUBSCRIBE订阅其关注的频道,订阅者可以订阅一个或多个频道。

我们可以启动三个Redis客户端进行简单演示:

图片

发布/订阅实现消息队列的演示

pub/sub既支持单播也支持广播,同时还支持频道的简单正则匹配。但消息丢失(如客户端断开连接或Redis宕机都会导致消息丢失)、消息堆积(发布者发布消息时不考虑消费者的处理能力)等问题依然没有得到良好的解决。

为此,Redis 5.0引入了一个新的数据结构Stream,以更好地支持消息队列功能。Stream支持:

  • 发布/订阅模式
  • 按消费者组进行消费
  • 消息持久化(RDB和AOF)

虽然Stream在使用上稍显复杂,这里不做演示。而且在实际使用中,Stream依然存在一些小问题,比如在Redis故障恢复后,不能确保消息至少被消费一次。

综上所述,与专业的消息队列相比,使用Redis来实现消息队列依然存在许多不足之处,如消息丢失和堆积问题难以解决。因此,我们通常建议避免使用Redis作为消息队列,而是选择市场上成熟的消息队列产品,如RocketMQ或Kafka。