Redis 消息队列的三种方案选型

Redis 消息队列的三种方案选型

消息队列(Message Queue,简称 MQ)

消息队列(Message Queue,简称 MQ)。简单理解,生产者先将消息投递一个叫做【队列】的容器中,然后再从这个容器中取出消息,最后再转发给消费者。

在这里插入图片描述

消息队列使用场景

系统解耦
在分布式环境下,系统间的相互依赖,最终会会导致整个依赖关系混乱,特别在微服务环境下,会出现相互依赖,甚至是循环依赖的情况,对后期系统的拆分和优化都带来极大负担。那么我们就可以用MQ来进行处理。上游系统将数据投递到MQ,下游系统取MQ的数据进行消费,投递和消费可以用同步的方式处理,不会影响上游系统的性能。

异步处理
如果采用同步的方式,系统的性能(并发量,吞吐量,响应时间)会有瓶颈。如何解决这个问题呢?引入消息队列,将不必要的业务逻辑异步处理。

流量削峰
消息中间件的存储能力能够有效的帮助消费者进行缓冲。试想下,正常流量下消费者能够愉快的进行消费,瞬时高峰流量来的时候,消费者消费能力跟不上,刚好阻塞在消息中间件,等峰值过后,消费者又能很快的将阻塞的消息进行消费。流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛!

数据分发
大部分开源的MQ中间件基本都支持一对多或者广播的模式,而且都可以根据规则选择分发的对象。这样上游的一份数据,众多下游系统中,可以根据规则选择是否接收这些数据,这样扩展性就很强了。

Redis 消息队列应用背景,选型思考

当前使用较多的 消息队列 有 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMQ 等。

各种开源的 MQ 已经足够使用了,为什么需要用 Redis 实现 MQ 呢?

Kafka、RabbitMQ 是非常专业的消息中间件,但它们的部署和运维,相比于 Redis 来说,也会更复杂一些。
有些简单的业务场景,可能不需要重量级的 MQ 组件(相比 Redis 来说,Kafka 和 RabbitMQ 都算是重量级的消息队列)。

如果你在一个大公司,公司本身就有优秀的运维团队,那么使用这些中间件肯定没问题,因为有足够优秀的人能 hold 住这些中间件,公司也会投入人力和时间在这个方向上。

但如果你是在一个初创公司,业务正处在快速发展期,暂时没有能 hold 住这些中间件的团队和人,如果贸然使用这些组件,当发生故障时,排查问题也会变得很困难,甚至会阻碍业务的发展。

而这种情形下,如果公司的技术人员对于 Redis 都很熟,综合评估来看,Redis 也基本可以满足业务 90% 的需求,那当下选择 Redis 未必不是一个好的决策。

Redis消息队列发展历程

Redis消息队列发展历程
参考URL: https://baijiahao.baidu.com/s?id=1730236985407277099

Redis是目前最受欢迎的kv类数据库,当然它的功能越来越多,早已不限定在kv场景,消息队列就是Redis中一个重要的功能。

Redis从2010年发布1.0版本就具备一个消息队列的雏形,随着10多年的迭代,其消息队列的功能也越来越完善,作为一个全内存的消息队列,适合应用与要求高吞吐、低延时的场景。

在这里插入图片描述

在Redis中提供了三种消息队列对比

redis 实现消息队列的4种方案
参考URL: https://www.lanmper.cn/vue/t9597

  • List结构:基于List结构来模拟消息队列

List实现消息队列的缺点:
- 无法避免消息丢失:例如消费者拿到消息还没有消费就宕机了
- 只能支持单个消费(它没有广播功能,一个消息只能被消费一次。而在大型系统中,通常一个消息会被下游多个应用同时订阅和消费)

基于list的特点,可以推导出在两种常见场景下的实现方案:

针对生产者都采用lpush的方式推送数据,

针对消费者:
对实时性和可靠性要求高的情况,消费者使用brpop阻塞读取和消费数据
对实时性和可靠性要求不高的情况,但是推送数据量大,要求处理性能高,消费者使用定时任务+lrange+ltrim方式读取和消费数据

  • PubSub:基本的点对点消息模型
    Pub/Sub 在实现时非常简单,它没有基于任何数据类型,也没有做任何的数据存储,它只是单纯地为生产者、消费者建立「数据转发通道」,把符合规则的数据,从一端转发到另一端。

基于PubSub的消息队列的缺点:
- 消息一旦发布,不能接收。换句话就是发布时若客户端不在线,则消息丢失,不能寻回。
- 不能保证每个消费者接收的时间是一致的。
- 若消费者客户端出现消息积压,到一定程度,会被强制断开,导致消息意外丢失。通常发生在消息的生产远大于消费速度时。

也正是以上原因,Pub/Sub 在实际的应用场景中用得并不多。
Pub/Sub 模式不适合做消息存储,消息积压类的业务,而是擅长处理广播,即时通讯,即时反馈的业务。

当生产、消费速度不匹配时,就会导致缓冲区的内存开始膨胀,Redis 为了控制缓冲区的上限,所以就有了上面讲到的,强制把消费者踢下线的机制。

现在我们总结一下 Pub/Sub 的优缺点:

  1. 支持发布 / 订阅,支持多组生产者、消费者处理消息
  2. 消费者下线,数据会丢失
  3. 不支持数据持久化,Redis 宕机,数据也会丢失
  4. 消息堆积,缓冲区溢出,消费者会被强制踢下线,数据也会丢失

有没有发现,除了第一个是优点之外,剩下的都是缺点。

首先,pubsub的消息数据是瞬时的,它在Redis服务端不做保存,publish发送到Redis的消息会立刻推送到所有当时subscribe连接的客户端,如果当时客户端因为网络问题断连,那么就会错过这条消息,当客户端重连后,它没法重新获取之前那条消息,甚至无法判断是否有消息丢失。

其次,pubsub中消费者获取消息是一个推送模型,这意味着Redis会按消息生产的速度给所有的消费者推送消息,不管消费者处理能力如何,如果消费者应用处理能力不足,消息就会在Redis的client buf中堆积,当堆积数据超过一个阈值后会断开这条连接,这意味着这些消息全部丢失了,在也找不回来了。如果同时有多个消费者的client buf堆积数据但又还没达到断开连接的阈值,那么Redis服务端的内存会膨胀,进程可能因为oom而被杀掉,这导致了整个服务中断。

消息缺乏堆积能力,不能削峰填谷。推送的方式缺乏背压机制,没有考虑消费者处理能力,推送的消息超过消费者处理能力后可能导致消息丢失或服务异常。

  • Stream:较完善的消息队列模型
    Stream是Redis5.0引入的新的数据类型,可以实现一个功能较为完善的消息队列
    Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。

大部分使用消息队列的场景都可以使用stream替代。基于redis的高性能和使用内存的机制使得其的性能优于大部分消息队列。在小规模场景会有更出色的表现。但是针对大流量的场景不推荐使用stream,毕竟内存的大小是有限的,这也是所有redis实现的消息队列的局限之处。

List 其实是属于「拉」模型,而 Pub/Sub 其实属于「推」模型。List 中的数据可以一直积压在内存中,消费者什么时候来「拉」都可以。

List 实现消息队列思路

因为 Redis 单线程的特点,所以在消费数据时,同一个消息会不会同时被多个 consumer 消费掉,但是需要我们考虑消费不成功的情况。

不过为了保险,我这块实现在消费者进程这块使用了redis的分布式锁,保证多个消费者消费的list数据不会重复。
在这里插入图片描述

  1. Redis rpush 命令用于将一个或多个值插入到列表的尾部(最右边)。如果列表不存在,一个空列表会被创建并执行 RPUSH 操作。 当列表存在但不是列表类型时,返回一个错误。
  2. 消费者利用Redis lrange 一次取完list内容去消费。消费者多个实例的话,可以搭配同时使用redis分布式锁。
    Redis lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

猜你喜欢

转载自blog.csdn.net/inthat/article/details/127145108