1. 简述什么是消息队列 ?
消息(Message)是指在应用间传送的数据。消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。
消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,由消息系统来确保消息的可靠传递。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。这样发布者和使用者都不用知道对方的存在。
消息队列是一种应用间的异步协作机制,同时消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。
当前业界比较流行的开源消息中间件包括:RabbitMQ、RocketMQ、Kafka。
2. 简述消息队列有哪些使用场景 ?
1 应用解耦
场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口
2 流量削峰流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛!
应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。
可以控制活动的人数,可以缓解短时间内高流量压垮应用
3 异步处理
场景说明:用户注册后,需要发注册邮件和注册短信。传统的做法有两种:串行的方式和并行方式。
串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户。
并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
4 消息通讯
消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等
5 日志处理
日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题
3. 简述什么是消息持久化?
持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。
4. 消息持久化有哪些缺点?如何缓解?
消息持久化的缺点是很消耗性能,因为要写入硬盘要比写入内存性能较低很多,从而降低了服务器的吞吐量。可使用固态硬盘来提高读写速度,以达到缓解消息持久化的缺点
5. 消息队列由哪些角色组成?
生产者:负责生产消息
消费者:负责消费消息
消息代理:负责存储和转发消息。转发消息分为推模式和拉模式。
6. 消息队列架构过程中的难点?
消息堆积:由于生产者和消费者的速率不一致,消息处理中心可能会导致堆积消息。消息队列要能感知处理中心的堆积情况,避免资源被耗尽。
7. 简述消息队列的消息协议 ?
AMQP、MQTT和STOMP是三种最常见、最流行的基于TCP/IP的消息传递协议
8. 简述什么是AMQP协议 ?
AMQP,即高级消息队列协议(Advanced Message Queuing Protocol),一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。
核心角色如下:
Message(消息):消息服务器处理消息的原子单元,包括一个内容头,一组属性和一个内容体。消息有优先级,高优先级的消息在等待同一消息队列时会比低优先级的消息先发送,而且当消息必须被丢弃时,低优先级的消息优先被丢弃。使用AMQP协议,消息服务器不能修改内容体和内容头,但可以在内容头上添加额外信息。
PubLisher(消息生产者):发送消息
Consumer(消息消费者):消费消息
Broker(消息代理):消息队列服务器,负责接收客户端连接,路由消息。
Queue(消息队列):Broker中的一个角色,一个Broker中可以有多个Queue,负责保存消息直到发送给不同的消费者。算是消息的容器。一个消息可以被投入一个或多个队列中,每个队列的消息都会等待消费者连接到这个队列并被取走。
Exchange(交换路由):Broker中的一个角色,负责接收生产者发送的消息,并路由给服务器中的队列。可以被理解成一个规则表,指明消息该被投到哪个队列中。
Channel(信道):信道是一条独立的双向数据流通道。为了解决操作系统无法承受每秒建立特别多的TCP连接。
生产者发送消息时,必须指定消息要被路由到哪些个消息队列中。当消息到消息队列中,消息队列会尝试将消息传给消费者,如果失败,消息队列会存储消息并等待消费者。如果没有消费者,消息队列将选择性的将消息返回给生产者。如果消息别消费掉,消息队列会删除消息,删除的过程或者是及时的,或者是等到消费者消费结果后才删除的。
RabbitMQ是AMQP消息队列最有名的开源实现,当然RabbitMQ同时还可以通过插件支持STOMP、MQTT等协议接入。Kafka、RocketMQ均使用自定义的协议
9. 简述什么是MQTT协议?
MQTT,即消息队列遥测传输(Message Queuing Telemetry Transport)。由IBM开发,现在被广泛用于物联网公司。因为他的特点就是轻量,简单,开放和易于实现。所以它常用于很多计算能力有限、带宽低、网络不可靠的远程通信应用场景。
核心角色如下:
Publisher(发布者):消息发布客户端
Subscriber(订阅者):消息订阅客户端
Broker(消息代理):消息服务器端
Application Message(应用消息):指通过网络传输的应用数据,一般包括主题和负载。
Topic(主题):应用消息的类型,一般消息发布者会确定消息的主题,订阅者根据自己实际情况选择不同的主题进行消息订阅消费。
Payload(负载):消息订阅者具体接收的内容。
MQTT协议是通过交换预定义的MQTT控制报文来通信的,控制报文内容由三部分组成:固定报头,可变报头和消息体。固定报头通过标识不同位的值来确定报文类型,包括发布订阅的一些完成状态等;可变报头的内容根据控制报文类型不同而不同,常作为包的标识符;消息体也是根据不同的消息类型有着不同的内容。
MQTT协议中,客户端和服务端是通过请求-应答模式通信的。客户端发送一条控制报文数据给服务器,服务器再发送一条控制报文数据给客户端。
MQTT在发布消息时,有三种Qos等级:
至多一次(0级)
至少一次(1级)
只有一次(2级)
至多一次等级最低,客户端只需要将消息发出去即可,这种等级很低,用于消息不重要但特别多,为了减轻通信压力,就不顾质量,只看数量了。
至少一次等级中等,客户端要保证发出去的消息至少一次被服务端接收到,所以要收到服务端的回应,否则一直发,这种等级一般用于服务端有幂等处理,所以不怕重复消费,还要保证消息不会丢失。
只有一次等级最高,客户端先发消息过去,然后本地记录一个我已发送,但不确定你是否收到的状态,然后服务端接收到消息后,回给客户端一个我已接收的报文,同时服务端记录一个我不确定你知不知道我已接收的状态,然后客户端收到这个已接收的消息后,就确定服务端收到这个消息了,于是把自己本地记录的已发送未确定的状态删除,同时再给客户端发送一个我已经知道你收到的报文,服务端收到这个报文,也会把自己之前记录的状态删掉,整个一条报文只有一次的通信才算完成,这种等级就比较严格了,但质量上去了,相对低等级的,数量就会相对小些,但可靠就是王道,不多不少才是最好的。
只有一次的发送和确定,其实思想和三次握手差不多,都是两端互相确认的过程,所以会一来一回的。如果传输过程中出现丢包,都会由发送者重发上一条消息。
10. 简述什么是STOMP协议?
STOMP,即流文本定向消息协议(Streaming Text Orientated Messaging Protocal),是一个相对简单的文本消息传输协议,主要特点就是简单易懂,没有特别多的套路。
核心角色如下:
客户端:既可以是生产者,也可以是消费者
服务端:消息中心
ActiveMQ以及它的下一代实现Apache Apollo,是STOMP协议的典型实现
11. 简述什么是JMS ?
MS(Java MessageService)实际上是一套JAVA API接口,是由Sun公司早期提出的消息标准,旨在为java应用提供统一的消息操作,包括create、send、receive等。
严格来讲,JMS并不属于消息协议,而是一种规范,是对AMQP,MQTT,STOMP等协议更高一层的抽象。从使用角度看,JMS和JDBC担任差不多的角色,用户都是根据相应的接口可以和实现了JMS的服务进行通信,进行相关的操作。
JMS通常包含如下一些角色:
JMS provider:实现了JMS接口的消息中间件,如ActiveMQ
JMS client:生产或者消费消息的应用
JMS producer/publisher:JMS消息生产者
JMS consumer/subscriber:JMS消息消费者
JMS message:消息,在各个JMS client传输的对象
JMS queue:Provider存放等待被消费的消息的地方
JMS topic:一种提供多个订阅者消费消息的一种机制;在MQ中常常被提到,topic模式
消息如何从producer端达到consumer端由message-routing来决定。在JMS中,消息路由非常简单,由producer和consumer链接到同一个queue(p2p)或者topic(pub/sub)来实现消息的路由。JMSconsumer同时支持message selector(消息选择器),通过消息选择器,consumer可以只消费那些通过了selector筛选的消息。
12. 解释JMS的通信模式 ?
Point-to-Point Messaging Domain (点对点)
Publish/Subscribe Messaging Domain (发布/订阅模式)
在JMS API出现之前,大部分产品使用“点对点”和“发布/订阅”中的任一方式来进行消息通讯。JMS定义了这两种消息发送模型的规范,它们相互独立。任何JMS的提供者可以实现其中的一种或两种模型,这是它们自己的选择。JMS规范提供了通用接口保证我们基于JMS API编写的程序适用于任何一种模型。
13. 简述消息队列P2P模型 ?
P2P模式包含三个角色:消息队列(Queue),发送者(Sender),接收者(Receiver)。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。
P2P的特点:
每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中)
发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列
接收者在成功接收消息之后需向队列应答成功
14. 简述消息队列Pub/Sub模式模型 ?
包含三个角色主题(Topic),发布者(Publisher),订阅者(Subscriber) 。多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。
Pub/Sub的特点:
每个消息可以有多个消费者
发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息。
为了消费消息,订阅者必须保持运行的状态。
为了缓和这样严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息
15. 简述P2P模式和发布订阅模式的区别 ?
1、点对点模式
生产者发送一条消息到queue,一个queue可以有很多消费者,但是一个消息只能被一个消费者接受,当没有消费者可用时,这个消息会被保存直到有 一个可用的消费者,所以Queue实现了一个可靠的负载均衡。
2、发布订阅模式
发布者发送到topic的消息,只有订阅了topic的订阅者才会收到消息。topic实现了发布和订阅,当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到这个消息的拷贝。
16. 如何保证消息队列-消息不丢失?
丢数据一般分为两种,一种是mq把消息丢了,一种就是消费时将消息丢了。下面从rabbitmq和kafka分别说一下,丢失数据的场景。
RabbitMQ:
RabbitMQ丢失消息分为如下几种情况:
生产者丢消息:
生产者将数据发送到RabbitMQ的时候,可能在传输过程中因为网络等问题而将数据弄丢了。
RabbitMQ自己丢消息:
如果没有开启RabbitMQ的持久化,那么RabbitMQ一旦重启数据就丢了。所以必须开启持久化将消息持久化到磁盘,这样就算RabbitMQ挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢失。除非极其罕见的情况,RabbitMQ还没来得及持久化自己就挂了,这样可能导致一部分数据丢失。
消费端丢消息:
主要是因为消费者消费时,刚消费到还没有处理,结果消费者就挂了,这样你重启之后,RabbitMQ就认为你已经消费过了,然后就丢了数据。
针对上述三种情况,RabbitMQ可以采用如下方式避免消息丢失:
生产者丢消息:
可以选择使用RabbitMQ提供是事务功能,就是生产者在发送数据之前开启事务,然后发送消息,如果消息没有成功被RabbitMQ接收到,那么生产者会受到异常报错,这时就可以回滚事务,然后尝试重新发送。如果收到了消息,那么就可以提交事务。这种方式有明显的缺点,即RabbitMQ事务开启后,就会变为同步阻塞操作,生产者会阻塞等待是否发送成功,太耗性能会造成吞吐量的下降。
可以开启confirm模式。在生产者那里设置开启了confirm模式之后,每次写的消息都会分配一个唯一的id,然后如何写入了RabbitMQ之中,RabbitMQ会给你回传一个ack消息,告诉你这个消息发送OK了。如果RabbitMQ没能处理这个消息,会回调你一个nack接口,告诉你这个消息失败了,你可以进行重试。而且你可以结合这个机制知道自己在内存里维护每个消息的id,如果超过一定时间还没接收到这个消息的回调,那么你可以进行重发。
事务机制是同步的,你提交了一个事物之后会阻塞住,但是confirm机制是异步的,发送消息之后可以接着发送下一个消息,然后RabbitMQ会回调告知成功与否。 一般在生产者这块避免丢失,都是用confirm机制。
RabbitMQ自己丢消息:
设置消息持久化到磁盘,设置持久化有两个步骤:
创建queue的时候将其设置为持久化的,这样就可以保证RabbitMQ持久化queue的元数据,但是不会持久化queue里面的数据。
发送消息的时候讲消息的deliveryMode设置为2,这样消息就会被设为持久化方式,此时RabbitMQ就会将消息持久化到磁盘上。 必须要同时开启这两个才可以。
而且持久化可以跟生产的confirm机制配合起来,只有消息持久化到了磁盘之后,才会通知生产者ack,这样就算是在持久化之前RabbitMQ挂了,数据丢了,生产者收不到ack回调也会进行消息重发。
消费端丢消息:
使用RabbitMQ提供的ack机制,首先关闭RabbitMQ的自动ack,然后每次在确保处理完这个消息之后,在代码里手动调用ack。这样就可以避免消息还没有处理完就ack。
Kafka:
Kafka丢失消息分为如下几种情况:
生产者丢消息:
生产者没有设置相应的策略,发送过程中丢失数据。
Kafka自己丢消息:
比较常见的一个场景,就是Kafka的某个broker宕机了,然后重新选举partition的leader时。如果此时follower还没来得及同步数据,leader就挂了,然后某个follower成为了leader,它就少了一部分数据。
消费端丢消息:
消费者消费到了这个数据,然后消费之自动提交了offset,让Kafka知道你已经消费了这个消息,当你准备处理这个消息时,自己挂掉了,那么这条消息就丢了。
针对上述三种情况,Kafka可以采用如下方式避免消息丢失:
生产者丢消息:
关闭自动提交offset,在自己处理完毕之后手动提交offset,这样就不会丢失数据。
Kafka自己丢消息:
一般要求设置4个参数来保证消息不丢失:
给topic设置 replication.factor 参数,这个值必须大于1,表示要求每个partition必须至少有2个副本。
在kafka服务端设置 min.isync.replicas 参数,这个值必须大于1,表示 要求一个leader至少感知到有至少一个follower在跟自己保持联系正常同步数据,这样才能保证leader挂了之后还有一个follower。
在生产者端设置 acks=all ,表示 要求每条每条数据,必须是写入所有replica副本之后,才能认为是写入成功了。
在生产者端设置 retries=MAX (很大的一个值),表示这个是要求一旦写入事变,就无限重试。
消费端丢消息:
如果按照上面设置了ack=all,则一定不会丢失数据,要求是,你的leader接收到消息,所有的follower都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。
数据,大数据量的数据处理上。
架构模型方面
RabbitMQ:以broker为中心,有消息的确认机制。
Kafka:以consumer为中心,没有消息的确认机制。
吞吐量方面
RabbitMQ:支持消息的可靠的传递,支持事务,不支持批量操作,基于存储的可靠性的要求存储可以采用内存或硬盘,吞吐量小。
Kafka:内部采用消息的批量处理,数据的存储和获取是本地磁盘顺序批量操作,消息处理的效率高,吞吐量高。
集群负载均衡方面
RabbitMQ:本身不支持负载均衡,需要loadbalancer的支持。
Kafka:采用zookeeper对集群中的broker,consumer进行管理,可以注册topic到zookeeper上,通过zookeeper的协调机制,producer保存对应的topic的broker信息,可以随机或者轮询发送到broker上,producer可以基于语义指定分片,消息发送到broker的某个分片上
17. 消息队列如何保证不重复消费?
大概说一说可能会有哪些重复消费的问题。首先就是比如rabbitmq、rocketmq、kafka,都有可能会出现消费重复消费的问题,正常。因为这问题通常不是mq自己保证的,是给你保证的。然后我们挑一个kafka来举个例子,说说怎么重复消费吧。
kafka实际上有个offset的概念,就是每个消息写进去,都有一个offset,代表他的序号,然后consumer消费了数据之后,每隔一段时间,会把自己消费过的消息的offset提交一下,代表我已经消费过了,下次我要是重启啥的,你就让我继续从上次消费到的offset来继续消费吧。
但是凡事总有意外,比如我们之前生产经常遇到的,就是你有时候重启系统,看你怎么重启了,如果碰到点着急的,直接kill进程了,再重启。这会导致consumer有些消息处理了,但是没来得及提交offset,尴尬了。重启之后,少数消息会再次消费一次。
其实重复消费不可怕,可怕的是你没考虑到重复消费之后,怎么保证幂等性。举个例子,假设你有个系统,消费一条往数据库里插入一条,要是你一个消息重复两次,你不就插入了两条,这数据不就错了?但是你要是消费到第二次的时候,自己判断一下已经消费过了,直接扔了,不就保留了一条数据?
一条数据重复出现两次,数据库里就只有一条数据,这就保证了系统的幂等性幂等性。通俗点说,就一个数据,或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,不能出错。
想要保证不重复消费,其实还要结合业务来思考,这里给几个思路:
比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下。
比如你是写redis,那没问题了,反正每次都是set,天然幂等性。
比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去比如redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个id写redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
还有比如基于数据库的唯一键来保证重复数据不会重复插入多条,我们之前线上系统就有这个问题,就是拿到数据的时候,每次重启可能会有重复,因为kafka消费者还没来得及提交offset,重复数据拿到了以后我们插入的时候,因为有唯一键约束了,所以重复数据只会插入报错,不会导致数据库中出现脏数据。
18. 简述MQ处理消息失败了怎么办?
一般生产环境中,都会在使用MQ的时候设计两个队列:一个是核心业务队列,一个是死信队列。核心业务队列,就是比如专门用来让订单系统发送订单消息的,然后另外一个死信队列就是用来处理异常情况的。
比如说要是第三方物流系统故障了,此时无法请求,那么仓储系统每次消费到一条订单消息,尝试通知发货和配送,都会遇到对方的接口报错。此时仓储系统就可以把这条消息拒绝访问,或者标志位处理失败!注意,这个步骤很重要。
一旦标志这条消息处理失败了之后,MQ就会把这条消息转入提前设置好的一个死信队列中。然后你会看到的就是,在第三方物流系统故障期间,所有订单消息全部处理失败,全部会转入死信队列。然后你的仓储系统得专门有一个后台线程,监控第三方物流系统是否正常,能否请求的,不停的监视。一旦发现对方恢复正常,这个后台线程就从死信队列消费出来处理失败的订单,重新执行发货和配送的通知逻辑。死信队列的使用,其实就是MQ在生产实践中非常重要的一环,也就是架构设计必须要考虑的
19. 请简述消息队列推和拉的使用场景 ?
【推模式】
推模式是服务器端根据用户需要,由目的、按时将用户感兴趣的信息主动发送到用户的客户端。
优点:
对用户要求低,方便用户获取需要的信息;
及时性好,服务器端及时地向客户端推送更新动态信息,吞吐量大。
缺点:
不能确保发送成功,推模式采用广播方式,只有服务器端和客户端在同一个频道上,推模式才有效,用户才能接收到信息;
没有信息状态跟踪,推模式采用开环控制技术,一个信息推送后的状态,比如客户端是否接收等,无从得知;
针对性较差。推送的信息可能并不能满足客户端的个性化需求。
拉模式:
【拉模式是客户端主动从服务器端获取信息】
优点:
针对性强,能满足客户端的个性化需求;
信息传输量较小,网络中传输的只是客户端的请求和服务器端对该请求的响应;
服务器端的任务轻。服务器端只是被动接收查询,对客户端的查询请求做出响应。
缺点:
实时性较差,针对于服务器端实时更新的信息,客户端难以获取实时信息;
对于客户端用户的要求较高,需要对服务器端具有一定的了解
20. 解释消息队列的异步处理 ?
主要应用于短信通知、终端状态推送、App推送、用户注册等。
异步处理的优势:更快速返回结果;减少等待,实现并发处理,提升系统总体性能。
消息队列的模式可以有:
(1)1对1:读取之后立刻从队列中移除消息。
(2)1对多:这种模型是发布订阅模型,消息队列的元素可以被重复消费。至于何时删除消息,可以设置消息的存活周期;比如kafka可以设置24H后删除消息
21. 解释消息队列的流量控制(削峰)?
在秒杀场景下的下单状态,使用消息队列隔离网关和后端服务,以达到流量控制和保护后端服务的目的。
秒杀服务
设置消息队列的最大限制数量,在达到最大数量时网关不再生成消息到消息队列中。
可以这么想:有一个商品秒杀活动,商品数量为100,当消息队列的数量达到100时不再生产秒杀成功消息,直接返回秒杀失败给用户,只有1到100的用户秒杀成功 获得商品
22. 解释消息队列的服务解耦 ?
A系统负责数据分发,其他系统调用A系统提供的接口处理数据;当新增一个系统时,A系统需要改代码调用新的系统,并实现新的接口给新的系统去调用。这种方式是系统间高度耦合。
使用消息队列,A系统负责将数据分发到MQ,消费端根据需要从MQ获取消息即可,不需要就取消MQ的消费。
23. 简述什么是消息队列的高并发缓冲 ?
这个和消息队列的流量控制(削峰)有些类似。区别在于,这里没有大小限流,可能在某个时间点会出现超过后端处理能力的访问;比如后端处理能力是50000每秒,在某个时间点出现每秒80000的访问,这就可能造成击穿。
针对此情况,消息全部放入消息队列,消息队列提供可以把数据固化到磁盘的能力,降低高峰数据对后端的短暂冲击。
比如,后端处理能力50000,某个短暂时间点(比如一秒的时间)数据访问达到80000,消息队列将多的数据缓存到磁盘,后端仍然处理50000数据;冲击点退去后,访问数据降到了30000,那么消息队列将把缓存的数据放到后端处理。
比如kafka 日志服务、监控上报
24. 简述什么是消息的ACK确认机制 ?
即消息的Ackownledge确认机制,为了保证消息不丢失,消息队列提供了消息Acknowledge机制,即ACK机制,当Consumer确认消息已经被消费处理,发送一个ACK给消息队列,此时消息队列便可以删除这个消息了。
如果Consumer宕机/关闭,没有发送ACK,消息队列将认为这个消息没有被处理,会将这个消息重新发送给其他的Consumer重新消费处理
25. 简述什么是消息的同步和异步收发 ?
同步: 消息的收发支持同步收发的方式 一应一答。
同时还有另一种同步方式:同步收发场景下,消息生产者和消费者双向应答模式。
消息的接收如果以同步的方式(Pull)进行接收,如果队列中为空,此时接收将处于同步阻塞状态,会一直等待,直到消息的到达
异步: 消息的收发同样支持异步方式:异步发送消息,不需要等待消息队列的接收确认;异步接收消息,以Push的方式触发消息消费者接收消息
26. 简述什么是Kafka协议 ?
Kafka协议是基于TCP/IP的二进制协议。消息内部是通过长度来分割,由一些基本数据类型组成。
特点是:
1:结构简单
2:解析速度快
3:无事务支持
4:有持久化设计
27. 简述消息队列高可用和高可靠方案( 重要 ) ?
高可用机制
所谓高可用:是指产品在规定的条件和规定的时刻或时间内处于可执行规定功能状态的能力。
当业务量增加时,请求也过大,一台消息中间件服务器的会触及硬件(CPU,内存,磁盘)的极限,一台消息服务器你已经无法满足业务的需求,所以消息中间件必须支持集群部署。来达到高可用的目的。
集群模式1 - Master-slave主从共享数据的部署方式
解说:生产者讲消费发送到Master节点,所有的都连接这个消息队列共享这块数据区域,Master节点负责写入,一旦Master挂掉,slave节点继续服务。从而形成高可用
集群模式2 - Master- slave主从同步部署方式
解释:这种模式写入消息同样在Master主节点上,但是主节点会同步数据到slave节点形成副本,和zookeeper或者redis主从机制很类同。这样可以达到负载均衡的效果,如果消费者有多个这样就可以去不同的节点就行消费,以为消息的拷贝和同步会暂用很大的带宽和网络资源。在后续的rabbtmq中会有使用
集群模式3 - 多主集群同步部署模式
解释:和上面的区别不是特别的大,但是它的写入可以往任意节点去写
集群模式4 - 多主集群转发部署模式
解释:如果你插入的数据是broker-1中,元数据信息会存储数据的相关描述和记录存放的位置(队列)。
它会对描述信息也就是元数据信息就行同步,如果消费者在broker-2中进行消费,发现自己几点没有对应的消息,可以从对应的元数据信息中去查询,然后返回对应的消息信息,场景:比如买火车票或者黄牛买演唱会门票,比如第一个黄牛有顾客说要买的演唱会门票,但是没有但是他会去联系其他的黄牛询问,如果有就返回
集群模式5 Master-slave与Breoker-cluster组合的方案
解释:实现多主多从的热备机制来完成消息的高可用以及数据的热备机制,在生产规模达到一定的阶段的时候,这种使用的频率比较高。
这么集群模式,具体在后续的课程中会进行一个分析和讲解。他们的最终目的都是为保证:消息服务器不会挂掉,出现了故障依然可以抱着消息服务继续使用。
28. 简述消息队列和RPC对比 ?
一. 区别
1.消息队列能够积压消息,让消费者可以按照自己的节奏处理消息,但是RPC不能.
2.消息队列是一个异步的过程(生产者发送消息之后,不会等待消息的处理),RPC是一个同步的过程.
3.消息队列的生产者不能得知谁消费了消息,消费结果是否成功,而RPC的调用者明确知道被调用者是谁,处理结果也能获取到.
4.由于消息队列在生产者和消费者之间还有一个queue节点,系统性能除了受自身因素影响外还受queue节点影响,而RPC没有中间节点,系统性能只受自己的影响.
二. 适用场景
由异同大致就能理解出两者的适用场景是什么:
1.消息队列能够让服务器的负载不会过高,降低了并发度,所以效率受到了影响,又由于消息队列是一个异步的过程,且生产者不能得知消费者的信息,所以消息队列一般用于实时性要求不高的花费时间的操作.
2.RPC是一个同步的过程,可能会因为突然高的并发量导致系统出问题,但是RPC具有很高的实时性,所以他一般用户需要立即返回结果的操作.
29. 请列举消息队列“家族”有哪些成员 ?
ActiveMQ:ActiveMQ由Apache软件基金会基于Java语言开发的一个开源的消息代理。能够支持多个客户机或服务器。计算机集群等属性支持ActiveMQ来管理通信系统。
RabbitMQ:RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。RabbitMQ支持多种消息传递协议、传递确认等特性。
Kafka:Apache Kafka是由Apache软件基金会开发的一个开源消息系统项目,由Scala写成。Kafka最初是由LinkedIn开发,并于2011年初开源。2012年10月从Apache Incubator毕业。该项目的目标是为处理实时数据提供一个统一、高通量、低等待的平台。Kafka是一个分布式的、分区的、多复本的日志提交服务。它通过一种独一无二的设计提供了一个消息系统的功能。
RocketMQ:Apache RocketMQ是一个分布式消息和流媒体平台,具有低延迟、强一致、高性能和可靠性、万亿级容量和灵活的可扩展性。它有借鉴Kafka的设计思想,但不是kafka的拷贝。
Pulsar:Apache Pulsar是Apache软件基金会顶级项目,是下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体,采用计算与存储分离架构设计,支持多租户、持久化存储、多机房跨区域数据复制,具有强一致性、高吞吐、低延时及高可扩展性等流数据存储特性,被看作是云原生时代实时消息流传输、存储和计算最佳解决方案。
30. 简述目前消息队列主要的数据组织方式 ?
通常在大部分组件设计时,往往会选择一种主要介质来存储、另一种介质作为辅助使用。就拿redis来说,它主要采用内存存储数据,磁盘用来做辅助的持久化。拿RabbitMQ举例,它也是主要采用内存存储消息,但也支持将消息持久化到磁盘。而RocketMQ、Kafka、Pulsar这种,则是数据主要存储在磁盘,通过内存来助力提升系统的性能。关系型数据库例如mysql这种组件也是主要采用磁盘组织数据,合理利用内存提升性能。
针对采用内存存储数据的方案而言,难点一方面在于如何在不降低访问效率的情况下,充分利用有限的内存空间来存储尽可能多的数据,这个过程中少不了对数据结构的选型、优化;另一方面在于如何保证数据尽可能少的丢失,我们可以看到针对此问题的解决方案通常是快照+广泛意义的wal文件来解决。此类典型的代表就是redis啦
针对采用磁盘存储数据的方案而言,难点一方面在于如何根据系统所要解决的特点场景进行合理的对磁盘布局。读多写少情况下采用b+树方式存储数据;写多读少情况下采用lsm tree这类方案处理。另一方面在于如何尽可能减少对磁盘的频繁访问,一些做法是采用mmap进行内存映射,提升读性能;还有一些则是采用缓存机制缓存频繁访问的数据。还有一些则是采用巧妙的数据结构布局,充分利用磁盘预读特性保证系统性能。
总的来说,针对写磁盘的优化,要不采用顺序写提升性能、要不采用异步写磁盘提升性能(异步写磁盘时需要结合wal保证数据的持久化,事实上wal也主要采用顺序写的特性);针对读磁盘的优化,一方面是缓存、另一方面是mmap内存映射加速读。
上述这些存储方案上权衡的选择在Kafka、RocketMQ、Pulsar中都可以看到。其实抛开消息队列而言,这些存储方案的选择上无论是关系型数据库还是kv型组件都是通用的。
31. 列举架构中引入 MQ 后存在的问题 ?
- 系统可用性降低
MQ 可能挂掉,导致整个系统崩溃 - 系统复杂性变高
可能发重复消息,导致插入重复数据;消息丢了;消息顺序乱了;系统 B,C,D 挂了,导致 MQ 消息积累,磁盘满了; - 一致性问题
本来应该A,B,C,D 都执行成功了再返回,结果A,B,C 执行成功 D 失败
32. 简单列举MQ消息丢失的原因 ?
生产者:生产者推送消息到MQ中,但是网络出现了故障,比如网络超时,网络抖动,导致消息没有推送到MQ中,在网络中丢失了。又或者推送到MQ中了,但是这时候MQ内部出错导致消息丢失。
MQ:MQ自己内部发生了错误,导致消息丢失。
消费者:有时处理消息的消费者处理不当,还没等消息处理完,就给MQ发送确认信息,但是这时候消费者自身出问题,挂了,确认消息已经发送给MQ告诉MQ自己已经消费完了,导致消息丢失
33. 简述什么是消息队列MQ的延迟堆积 ?
如果每次pull时间间隔比较久,会增加消息延迟,消息到达消费者时间会加长。这样时间一长会导致MQ中消息的堆积,而消息长时间堆积就会导致一系列的问题:
1、如果积压了几个小时的数据,有几千万的数据量,消费端处理的压力会越来越大。
2、如果是带有过期时间的消息,可能这些消息已经到了过期时间,因为积压时间太长,但还没被消费端消费掉,消费端来不及消费。
3、如果持续的积压,达到了MQ能存储消息数量的上限,也就是说MQ满了,存不下了,会导致MQ丢掉数据,导致数据丢失。
想一下,上面的情形是不是跟TCP/IP协议的流量控制和拥塞控制遇到的一些问题很像,也有很多不同。
b、如果每次pull的时间间隔比较短,在一段时间内MQ中没有可消费的消息,会产生很多无效的pull请求,导致一定的网络开销。
所以解决问题的办法最主要就是优化消费端的消费性能。1.优化消费逻辑 2.水平扩容,增加消费端并发
34. 消息队列选型(Kafka、RocketMQ、RabbitMQ) ?
(1)Kafka
为大数据而生的消息中间件,靠着百万级TPS的吞吐量在数据采集、传输、存储的过程中发挥着举足轻重的作用。
优点
性能卓越,单机写入TPS约在百万条/秒,最大的优点,就是吞吐量高。
时效性ms级
可用性非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
消费者采用Pull方式获取消息,消息有序,通过控制能够保证所有消息被消费且仅被消费一次
有优秀的第三方Kafka Web管理界面Kafka-Manager
在日志领域比较成熟,被多家公司和多个开源项目使用
功能支持:功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用
缺点
Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长
使用短轮询方式,实时性取决于轮询间隔时间
消费失败不支持重试
支持消息顺序,但是一台代理宕机后,就会产生消息乱序
社区更新较慢
(2)RabbitMQ
RabbitMQ 2007年发布,是一个在AMQP(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。
优点
由于erlang语言的特性,mq 性能较好,高并发;
吞吐量到万级,MQ功能比较完备
健壮、稳定、易用、跨平台、支持多种语言、文档齐全;
开源提供的管理界面非常棒,用起来很好用
社区活跃度高
缺点
erlang开发,很难去看懂源码,基本职能依赖于开源社区的快速维护和修复bug,不利于做二次开发和维护。
RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。
需要学习比较复杂的接口和协议,学习和维护成本较高。
(3)RocketMQ
RocketMQ出自阿里公司的开源产品,用 Java 语言实现,在设计时参考了 Kafka,并做出了自己的一些改进。被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景。
优点
单机吞吐量:十万级
可用性:非常高,分布式架构
消息可靠性:经过参数优化配置,消息可以做到0丢失
功能支持:MQ功能较为完善,还是分布式的,扩展性好
支持10亿级别的消息堆积,不会因为堆积导致性能下降
源码是java,我们可以自己阅读源码,定制自己公司的MQ,可以掌控
缺点
支持的客户端语言不多,目前是java及c++,其中c++不成熟;
社区活跃度一般
没有在 mq 核心中去实现JMS等接口,有些系统要迁移需要修改大量代码
【选型建议】
Kafka
Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,适合产生大量数据的互联网服务的数据收集业务。
RocketMQ
可以用于对于可靠性要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况。
RoketMQ在稳定性上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验,如果你的业务有上述并发场景,建议可以选择RocketMQ。
RabbitMQ
结合erlang语言本身的并发优势,性能较好,社区活跃度也比较高,但是不利于做二次开发和维护。
RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug。如果你的数据量没有那么大,小公司优先选择功能比较完备的RabbitMQ。
35. 各类消息队列简述( 重要 )?
(1)ActiveMQ是Apache出品的、采用Java语言编写的完全基于JMS1.1规范的面向消息的中间件,为应用程序提供高效的、可扩展的、稳定的和安全的企业级消息通信。不过由于历史原因包袱太重,目前市场份额没有后面三种消息中间件多,其最新架构被命名为Apollo,号称下一代ActiveMQ,有兴趣的同学可行了解。
(2)RabbitMQ是采用Erlang语言实现的AMQP协议的消息中间件,最初起源于金融系统,用于在分布式系统中存储转发消息。RabbitMQ发展到今天,被越来越多的人认可,这和它在可靠性、可用性、扩展性、功能丰富等方面的卓越表现是分不开的。它非常重量级,更适合于企业级的开发。同时实现了Broker构架,这意味着消息在发送给客户端时先在中心队列排队。对路由,负载均衡或者数据持久化都有很好的支持。
(3)Kafka起初是由LinkedIn公司采用Scala语言开发的一个分布式、多分区、多副本且基于zookeeper协调的分布式消息系统,现已捐献给Apache基金会。它是一种高吞吐量的分布式发布订阅消息系统,以可水平扩展和高吞吐率而被广泛使用。
Kafka具有以下特性:快速持久化,可以在O(1)的系统开销下进行消息持久化;高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现负载均衡;支持Hadoop数据并行加载,对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka通过Hadoop的并行加载机制统一了在线和离线的消息处理。Apache Kafka相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。目前越来越多的开源分布式处理系统如Cloudera、Apache Storm、Spark、Flink等都支持与Kafka集成。
(4)RocketMQ是阿里开源的消息中间件,目前已经捐献给Apache基金会,它是由Java语言开发的,具备高吞吐量、高可用性、适合大规模分布式系统应用等特点。经历过双11的洗礼,实力不容小觑。
(5)ZeroMQ号称史上最快的消息队列,尤其针对大吞吐量的需求场景,基于C语言开发。ZeroMQ是一个消息处理队列库,可在多线程、多内核和主机之间弹性伸缩,虽然大多数时候我们习惯将其归入消息队列家族之中,但是其和前面的几款有着本质的区别,ZeroMQ本身就不是一个消息队列服务器,更像是一组底层网络通讯库,对原有的Socket API上加上一层封装而已。ZeroMQ能够实现RabbitMQ不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,技术上的复杂度是对这MQ能够应用成功的挑战。ZeroMQ具有一个独特的非中间件的模式,你不需要安装和运行一个消息服务器或中间件,因为你的应用程序将扮演这个服务器角色。你只需要简单的引用ZeroMQ程序库,可以使用NuGet安装,然后你就可以愉快的在应用程序之间发送消息了。但是ZeroMQ仅提供非持久性的队列,也就是说如果宕机,数据将会丢失。其中,Twitter的Storm 0.9.0以前的版本中默认使用ZeroMQ作为数据流的传输(Storm从0.9版本开始同时支持ZeroMQ和Netty作为传输模块)
(6)Redis
Redis是一个基于Key-Value对的NoSQL数据库,开发维护很活跃。虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用。对于RabbitMQ和Redis的入队和出队操作,各执行100万次,每10万次记录一次执行时间。测试数据分为128Bytes、512Bytes、1K和10K四个不同大小的数据。实验表明:入队时,当数据比较小时Redis的性能要高于RabbitMQ,而如果数据大小超过了10K,Redis则慢的无法忍受;出队时,无论数据大小,Redis都表现出非常好的性能,而RabbitMQ的出队性能则远低于Redis。
目前市面上的消息中间件还有很多,比如腾讯系的PhxQueue、CMQ、CKafka,又比如基于Go语言的NSQ,当然它们都很优秀,但是本文篇幅限制无法穷极所有,下面会针对性的挑选RabbitMQ和Kafka两款典型的消息中间件来做分析,力求站在一个公平公正的立场来阐述消息中间件选型中的各个要点。
36. 简述AMQP协议分为哪几层 ?
1、Module Layer: 协议最高层,主要定义了一些客户端调用的命令,客户端可以用这些命令实现自己的业务逻辑。
2、Session Layer: 中间层,主要负责客户端命令发送给服务器,再将服务端应答返回客户端,提供可靠性同步机制和错误处理。
3、TransportLayer: 最底层,主要传输二进制数据流,提供帧的处理、信道服用、错误检测和数据表示等。