有关于消息的内容可以查看上一篇文章

消息队列(Message Queue)**

队列:一种FIFO(先进先出)的数据结构

消息队列:是一种进程间通信或同一进程的不同线程间的通信方式。实际上,消息队列常常保存在链表结构中。拥有权限的进程可以向消息队列中写入或读取消息。我们可以认为,消息队列实际上就是消息的容器,存放在容器内的消息,可以被应用程序通过一定的规则选择性消费

消息队列作为高并发系统的核心组件之一,能够帮助业务系统解构提升开发效率和系统稳定性。主要具有以下优势:

  • 削峰填谷(主要解决瞬时写压力大于应用服务能力导致消息丢失、系统奔溃等问题)
  • 系统解耦(解决不同重要程度、不同能力级别系统之间依赖导致一死全死)
  • 提升性能(当存在一对多调用时,可以发一条消息给消息系统,让消息系统通知相关系统)
  • 蓄流压测(线上有些链路不好压测,可以通过堆积一定量消息再放开来压测)

消息中间件(Message-Oriented Middleware

消息中间件就是常说的消息队列,指的是**消息队列的实现,如Apache ActiveMQ、Apache RocketMQ、RebbitMQ、Kafka等,我们可以认为,消息中间件就是消息队列的容器**。

在java的概念中,消息中间件**指提供了对JMS协议的第三方组件**

为什么使用消息中间件

这个问题其实归根结底问的为什么使用消息队列

我们先来看一个传统应用场景

(1)小卖报员需要每天将报纸卖给村子里的每家每户并收款,刚开始仅需要在一个村子里卖报纸就行,小卖报员用小本本记下来每一家人需要什么样的报纸,然后下次采购回来,还算得心应手

(2)后来隔壁村子的住户也要跟他买报纸,小卖报员就加了个本子记录每个需要报纸的住户,每家每户的派送,小卖报员有点累了,虽然等的久点,但是各家各户还是可以接受的

(3)这时候又有一个村子要跟小卖报员合作,小卖报员顶不住了,但是钱还是个好东西啊。所以他又想了个法子,在各村各户的活动中心放了个报亭,每次只往报亭放报纸,村民有需要的给了钱就能从里面拿报纸,同时能在保亭留言需要哪种报纸,这样小卖报员甚至都不需要知道谁需要报纸,也不需要单独去收钱,只需要管理保亭就行了,业务蒸蒸日上,效率杠杠的

在这个场景里面,报纸其实就是消息,而小卖报员相当于生产消息的人,村民则是消费消息的人。小卖报员要送的报纸一多,实际上就要有次序的安排送报业务,于是形成了消息队列。当消息一多,小卖报员肯定也顶不住啊,所以就改变了方式,放了个报亭,报亭相当于一个消息中间件,小卖报员往消息中间件放了很多消息,村民则按需消费,双方都很舒服,一个不用等,一个不用急。(事实上,村民的需求也是一种消息,所以也可以放进消息中间件等待小卖报员消费)

上述场景中,小卖报员发布消息到报亭,然后村民按需获取,假设一份报纸可以被重复消费,那这种就是典型的发布-订阅者模型
而村民给小卖报员的留言,小卖报员读取了就没有了,这是典型的点对点模型

我们再来看看最常见的实际应用场景
(1)电商交易平台的下单支付流程,正常下单是酱紫的:
image.png
(2)这时候领导说要加个积分系统,订单支付了要加积分哦:
image.png
(3)领导又说要加个优惠券系统,订单支付之后可能要扣优惠券的喔:
image.png
(4)领导说还想加个短信通知,巴拉巴拉巴拉:
image.png
完犊子了呀,100ms加到300多ms,顶不住了啊

(5)领导说不怕,顶多加个多线程,只要支付成功,积分和优惠券,短信啥的可以同步进行:
image.png
哔哩啪啦打代码,加了多线程果然快很多喔。

现在只是加了三个子系统,如果后面领导又加了几个子系统,我不是又要去改这一块代码?这么复杂的逻辑我可不想动。而且,如果搞了个活动,一下子很多人支付,那这个架构可能就炸了哇。

(6)那就这样吧,彻底做到异步,我加个消息中间件,把支付成功这个消息放里面,别的子系统谁爱用谁用,我照常做我的事
image.png
只要支付成功,我就发布消息给消息中间件,接着直接返回成功,用户等待的时间可能就是100ms左右。放在消息中心的消息等待被其他子系统消费去处理【如加积分,扣优惠券,发短信之类的】即可,这样就真正实现了异步

而且这样做的好处就是可以解耦,当后续基于这个架构再加一个子系统,根本不需要去改原有代码,只需要这个子系统去消费这个消息即可。

上述场景就是典型的发布-订阅者模型。

综上所述,使用消息中间件的原因就是为了提高系统响应速度,负载均衡,同时解耦合,降低修改代码的成本。

消息中间件的优点

1、异步

将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度

2、解耦

将消息写入消息队列,需要消息的其他系统自己从消息队列中订阅,从而生产消息的系统不需要做任何修改

3、削峰

需要消费消息的系统按照数据库能处理的并发量,从消息队列中慢慢拉取消息,从而把高峰期访问请求延缓。在生产中,这个短暂的高峰期积压是允许的。

消息中间件的缺点

1、系统可用性降低

多个系统之间业务往来可能依赖于消息中间件,如果消息中间件突然宕机了,可能系统都得瘫痪

2、系统复杂性增加

一个是维护问题,另一个是引入消息中间件需要考虑一致性和重复消费等问题。

消息中间件的角色

Broker

消息服务器,作为server提供消息核心服务【也就是MQ】

Producer

消息生产者,业务的发起方,负责生产消息传输给broker

Consumer

消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理

常见消息中间件通信协议

底层协议(例如 TCP)是设计如何将一个消息从一个发送者(sender)传递给一个接收者(receiver),每个协议都有自己的角色定义

AMQPMQTTSTOMP是三种最常见、最流行的基于TCP/IP的消息传递协议

AMQP(Advanced Message Queuing Protocol)


描述
高级消息队列协议,**一种二进制协议,基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制

角色

  • Message(消息):消息服务器处理消息的原子单元,包括一个内容头,一组属性和一个内容体。
    __使用AMQP协议,消息服务器不能修改内容体和内容头,但可以在内容头上添加额外信息。

_

  • PubLisher(消息生产者):发送消息
  • Consumer(消息消费者):消费消息
  • Broker(消息代理):消息队列服务器,负责接收客户端连接,路由消息。
  • Queue(消息队列):Broker中的一个角色,一个Broker中可以有多个Queue,负责保存消息直到发送给不同的消费者。算是消息的容器一个消息可以被投入一个或多个队列中,每个队列的消息都会等待消费者连接到这个队列并被取走。
  • Exchange(交换路由):Broker中的一个角色,负责接收生产者发送的消息,并路由给服务器中的队列。可以被理解成一个规则表,指明消息该被投到哪个队列中
  • Channel(信道):信道是一条独立的双向数据流通道。为了解决操作系统无法承受每秒建立特别多的TCP连接问题

特性

  • 生产者发送消息时,必须指定消息要被路由到哪个消息队列中
  • 当消息到消息队列中,消息队列会尝试将消息传给消费者,如果失败,消息队列会存储消息并等待消费者
  • 如果没有消费者,消息队列将选择性的将消息返回给生产者
  • 如果消息被消费掉,消息队列会删除消息,删除的过程或者是及时的,或者是等到消费者消费结果后才删除的。

典型实现RabbitMQ

MQTT(Message Queuing Telemetry Transport)

描述
消息队列遥测传输,是一种**基于发布/订阅范式**的“轻量级”消息协议,由 IBM 发布。

角色

  • Publisher(发布者):消息发布客户端
  • Subscriber(订阅者):消息订阅客户端
  • Broker(消息代理):消息服务器端
  • Application Message(应用消息):指通过网络传输的应用数据,一般包括主题和负载。
  • Topic(主题):一般消息发布者会确定消息的主题,订阅者选择不同的主题进行消息订阅消费。
  • Payload(负载):消息订阅者具体接收的内容。

特性

  • 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。
  • 对负载内容屏蔽的消息传输。
  • 使用TCP/IP提供网络连接。
  • 小型传输,开销很小(固定长度的头部是2字节),协议交换最小化,以降低网络流量
  • 有三种消息发布服务质量:

Qos = 0; “至多一次”,消息发布完全依赖底层TCP/IP网络。会发生消息丢失或重复
Qos = 1; “至少一次”,确保消息到达,但消息重复可能会发生。
Qos = 0; “只有一次”,确保消息到达一次。在一些要求比较严格的计费系统中,可以使用此级别

典型实现:物联网(IoT)场景中更适合【消息体量更小】,支持几乎所有语言进行开发,并且浏览器也可通过 WebSocket 来发送和接收 MQTT 消息。
**

STOMP(Streaming Text Orientated Messaging Protocal)

描述
流文本定向消息协议,是一个相对简单的文本消息传输协议。它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。

与AMQP一样,STOMP提供带有属性的消息(或帧)标头或帧体。这里的设计原则是创建一些简单且可广泛互操作的东西。例如,可以使用想telnet客户端这样简单的东西连接到STOMP代理。

但是,STOMP不处理队列和主题。它使用带有“目标”字符串的SEND语义(命令)。

角色

  • 客户端:既可以是生产者,也可以是消费者
  • 服务端:消息中心

特性

  • STOMP的客户端和服务器之间的通信是通过“帧”(Frame)实现的,每个帧由多“行”(Line)组成。
  • 第一行包含了命令,然后紧跟键值对形式的Header内容。
  • 第二行必须是空行。
  • 第三行开始就是Body内容,末尾都以空字符结尾。
  • STOMP的客户端和服务器之间的通信是通过MESSAGE帧、RECEIPT帧或ERROR帧实现的,它们的格式相似。

典型实现ActiveMQ以及它的下一代实现Apache Apollo。

JMS是规范,是对AMQP,MQTT,STOMP,XMPP等协议更高一层的抽象。

消息中间件面临的问题

不管是哪种消息中间件,都要解决以下几个问题:

1、消息丢失问题(少消费了)

消息丢失可能发生在生产者,MQ以及消费者,因为网络或者其他原因

2、消息重复消费问题(多消费了)


发生在消费者**

正常情况下,消费者在消费消息时候,消费完毕后,会发送一个确认信息给broker,broker就知道该消息被消费了,就会将该消息从消息队列中删除
因为网络传输等等故障,确认信息没有传送到broker,导致消息队列没有删除消息,再次将该消息分发给其他的消费者。

3、消息顺序问题

即如何保证在MQ中的消息是按照生产者生产消息的顺序去保存的,还有就是如何保证消费者按正确顺序去消费消息

4、如何实现高可用

消息中间件的引入降低了系统的可用性,如何保障消息中间件的高可用,避免故障是避不开的问题。

几种消息中间件对比

网络上见的比较多次的图片:

MOM-消息中间件 - 图7

总结应用场景:

(1) 中小型软件公司,建议选RabbitMQ.
一方面,erlang语言天生具备高并发的特性,而且他的管理界面用起来十分方便。

RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug,这点对于中小型公司来说十分重要。

小公司数据量小,而Rocket和Kafka则更加适用于数据量大的场景

(2) 大型软件公司,根据具体使用在RocketMq和Kafka之间二选一

一方面,大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量。

针对RocketMQ,大型软件公司也可以抽出人手对RocketMQ进行定制化开发,毕竟国内有能力改JAVA源码的人,还是相当多的。

至于kafka,根据业务场景选择,如果有日志采集功能,肯定是首选kafka了。具体该选哪个,看使用场景。