消息中间件的使用场景,流行的消息中间件有哪些?

应用场景

1.业务场景

对于一个电商APP而言,每卖掉了一个商品,就要扣减掉商品的库存,而且一旦用户成功支付了,还需要将订单的状态更新成待发货。

消息中间件的使用场景,流行的消息中间件有哪些?
电商系统

在完成这些最核心的功能后,其实是有很多事情要做的,如果这些动作都以同步方式来完成,根据线上系统的一般统计,多个子步骤全部执行完毕,加起来大概需要1秒~2秒的时间。

有时候在高峰期并发量特别大,服务器的磁盘、IO、CPU的负载会很高,执行SQL语句的性能也会有所下降。因此有的时候甚至需要几秒钟的时间完成上述几个步骤。

想象一下,如果你是一个用户,在支付完一个订单之后,界面上会有一个圈圈不停的旋转,让你等待好几秒之后才能提示支付成功。对用户来说几秒钟的时间,会让人非常不耐烦的!

2.解决问题

所以首先针对子步骤过多、速度过慢、让用户支付之后等待时间过长的问题,就是订单系统急需解决的问题!

而解决这个问题的一大利器就是消息中间件,英文全称“Message Queue”,简称MQ。

消息中间件的使用场景,流行的消息中间件有哪些?
架构

在引入消息中间件以后,系统A和系统B之间就由同步变为异步通信,而完成这样的一个核心概念就是“消息”。

系统A发送消息给MQ后,就认为已经完成了自己的任务;然后系统B根据自己的情况,可能会在系统A投递消息到MQ之后的1秒内,也可能是1分钟之后,也可能是1小时之后,多长时间都有可能。

反正不管是多长时间后,系统B会根据自己的节奏从MQ里获取到一条属于自己的消息,再根据消息的指示完成自己的工作。

在“异步调用”的整个过程中,系统A仅仅是发个消息到MQ,至于系统B什么时候获取消息,有没有获取消息,系统A是不管的。

消息队列

当我们在使用一个消息队列时,希望它的功能如下:

  • 支持阻塞等待拉取消息
  • 支持发布 / 订阅模式
  • 消费失败,可重新消费,消息不丢失
  • 实例宕机,消息不丢失,数据可持久化
  • 消息可堆积

顺着这个思路,再重新思考一下订单系统。由于支付订单流程中,过多的子步骤如红包发放、短信push通知、积分等等导致性能很差。

在引入MQ后,可以让订单系统仅仅完成最核心的功能,然后发送消息到MQ。比如需要进行减库存,就发送一个消息到库存消息队列中,然后库存系统从这个MQ里获取消息再进行处理就可以,把这些很耗时的步骤慢慢执行,从而也实现了系统之间的解耦。

在大促活动的时候,同样可以让瞬间涌入的大量下单请求到MQ里去排队,然后让订单系统在后台慢慢的获取订单,以数据库可以接受的速率完成操作,避免瞬间请求量过大击垮数据库。

这也是MQ的另一重要功能:削峰填谷。

所谓消息中间件,就是一种系统,它自己本身也是独立部署的,通过消息的收发,是多个系统之间不局限于同步调用,通过异步调用更好地实现解耦。

目前业界使用最广泛的是Kafka、RabbitMQ以及RocketMQ这三种消息中间件。

Redis消息队列

Redis 很轻量,自带好几种可以用来实现消息队列的功能,用来作队列也很方便。

  • list链表
  • 订阅/发布
  • Redis Stream

1.pub/sub优缺点

Pub/Sub 的优缺点:

  • 支持发布 / 订阅,支持多组生产者、消费者处理消息
  • 消费者下线,数据会丢失
  • 不支持数据持久化,Redis 宕机,数据也会丢失
  • 消息堆积,缓冲区溢出,消费者会被强制踢下线,数据也会丢失
消息中间件的使用场景,流行的消息中间件有哪些?
Pub/Sub

Pub/Sub缓冲区是有「上限」的(可配置),如果消费者拉取消息很慢,就会造成生产者发布到缓冲区的消息开始积压,缓冲区内存持续增长。

如果超过了缓冲区配置的上限,此时,Redis 就会「强制」把这个消费者踢下线。

这时消费者就会消费失败,也会丢失数据。

如果你有看过 Redis 的配置文件,可以看到这个缓冲区的默认配置:client-output-buffer-limit pubsub 32mb 8mb 60。

  • 32mb:缓冲区一旦超过 32MB,Redis 直接强制把消费者踢下线
  • 8mb + 60:缓冲区超过 8MB,并且持续 60 秒,Redis 也会把消费者踢下线

2.stream

Stream 通过 XADD 和 XREAD 完成最简单的生产、消费模型:

  • XADD:发布消息
  • XREAD:读取消息

Stream 通过以下命令完成发布订阅:

  • XGROUP:创建消费者组
  • XREADGROUP:在指定消费组下,开启消费者拉取消息

Stream 保证消息不丢失,重新消费的方式:

当一组消费者处理完消息后,需要执行 XACK 命令告知 Redis,这时 Redis 就会把这条消息标记为「处理完成」。

// group1下的 1618472043089-0 消息已处理完成
127.0.0.1:6379> XACK queue group1 1618472043089-0

如果消费者异常宕机,肯定不会发送 XACK,那么 Redis 就会依旧保留这条消息。

待这组消费者重新上线后,Redis 就会把之前没有处理成功的数据,重新发给这个消费者。这样一来,即使消费者异常,也不会丢失数据了。

Stream处理消息堆积:

在发布消息时,可以指定队列的最大长度,防止队列积压导致内存爆炸。

// 队列长度最大10000
127.0.0.1:6379> XADD queue MAXLEN 10000 * name zhangsan
"1618473015018-0"
当队列长度超过上限后,旧消息会被删除,只保留固定长度的新消息。

Kafka

1.优势

  • Kafka的吞吐量几乎是行业里最优秀的,在常规的机器配置下,一台机器可以达到每秒十几万的QPS,可谓是消息中间件中的顶尖水平。
  • 性能高,发送消息给Kafka可以控制在毫秒级。
  • 可用性高,Kafka能够支持集群部署,如果部分服务器发生了宕机,集群其余部分可以保证任务的继续运行。

2.劣势

  • Kafka的存储策略是在消息收到之后,将其写入磁盘缓冲区内,并不会直接存储到物理硬盘上。这就导致了假如机器本身发生故障,磁盘缓冲区里的数据非常有可能丢失。而消息作为最重要的资源, kafka的这一特点有可能造成严重后果。
  • Kafka另外一个比较大的缺点是功能比较单一。它使用了典型的推拉架构设计,生产者将发送消息给它,然后消费者在从它拉取消息进行消费。除此之外,其他就没有什么额外的高级功能,所以基于Kafka有限的功能,一些比较复杂的商业场景并不是很适合。

RabbitMQ

再说RabbitMQ,在RocketMQ出现之前,国内大部分公司,包括很多一线互联网大厂都在使用RabbitMQ,而且直到目前,还有很多中小型公司在使用RabbitMQ。

1.优势

  • 可以有保证数据不丢失的机制。
  • 保证高可用性,集群部署的时候部分,即使部分服务器宕机可以继续执行任务。
  • 实现了部分高级功能,比如死信队列,消息重试。

2.劣势

  • RabbitMQ的吞吐量是比较低,只有每秒几万的级别,遇到像双十一这样的高并发场景,很容易到达性能的瓶颈。
  • 维护比较复杂,在集群部署时,如果需要线性扩展,比较麻烦。
  • 它的开发语言是erlang,国内目前大大小小公司的技术骨干大部分都是BAT背景,精通erlang语言的还是少数。阅读源代码非常困难,也就更加无从谈起根据个性化要求修改源代码了

RocketMQ

RocketMQ是阿里开源的消息中间件,经过实战的检验,比较靠谱。后发优势使它在最初设计的时候,就为了去解决Kafka和RabbitMQ所存在的缺陷。

1.优势

  • 性能强大,吞吐量高,能够达到10万QPS以上的级别
  • 可以保证高可用性,能够大规模集群化部署
  • 支持通过配置保证数据绝对不丢失
  • 满足多种需求,比如说延迟消息、事务消息、消息回溯、死信队列、消息积压等等。
  • RocketMQ是基于Java开发的,符合国内大多数公司的技术栈,很容易就可以阅读他的源码,甚至是修改他的源码。

2.劣势

  • 当然RocketMQ也有美中不足的地方,RocketMQ的官方文档相对简单,而这也是国内开源软件和社区的一个通病,可能国内996的工作强度导致大家没有更多的精力维护。