面试知识点梳理及相关面试题(十)-- rabbitmq
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
1. 什么是消息队列
队列中存放的内容是 message 是一种跨进程的通信机制用于上下游传递消息。
在互联网架构中MQ 是一种非常常见的上下游 “逻辑解耦 + 物理解耦” 的消息通信服务。
2. 为什么使用消息队列
- 在分布式系统下具备异步,削峰,负载均衡等一系列高级功能
- 拥有持久化的机制进程消息队列中的信息也可以保存下来。
- 实现消费者和生产者之间的解耦。
- 对于高并发场景下利用消息队列可以使得同步访问变为串行访问达到一定量的限流利于数据库的操作。
- 可以使用消息队列达到异步下单的效果排队中后台进行逻辑下单。
2.1 流量削峰
比如某一时刻开放购买的场景消费者不能一下子消费那么多订单可以让订单在队列中排队过几秒钟再处理总比不能处理的体验要好。
2.2 应用解耦
可以实现消费者和生产者之间的解耦。比如说下单的动作需要多个服务配合如果一个服务出现了问题下单都无法完成但是如果是基于消息队列前一个服务可以将请求发送到队列继续去做自己的事情。
2.3 异步处理
有些服务间调用是异步的例如 A 调用 BB 需要花费很长时间执行但是 A 需要知道 B 什么时候可以执行完。
这时候只需要在A调用完BA去做自己的事情B处理完成后发送一条消息到MQMQ再将次消息转发给A。
3. Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点
4. 你们公司用的什么消息队列为什么选择rabbitmq作为你们的消息队列
4.1 Kafka
Kafka 主要特点是基于 Pull 的模式来处理消息消费追求高吞吐量一开始的目的就是用于日志收集和传输适合产生大量数据的互联网服务的数据收集业务。大型公司建议可以选用如果有日志采集功能肯定是首选 kafka 了。
4.2 RocketMQ
天生为金融互联网领域而生对于可靠性要求很高的场景尤其是电商里面的订单扣款以及业务削峰在大量交易涌入时后端可能无法及时处理的情况。RoketMQ 在稳定性上可能更值得信赖这些业务场景在阿里双 11 已经经历了多次考验如果你的业务有上述并发场景建议可以选择 RocketMQ。
4.3 RabbitMQ
结合 erlang 语言本身的并发优势性能好时效性微秒级社区活跃度也比较高管理界面用起来十分方便如果你的数据量没有那么大中小型公司优先选择功能比较完备的 RabbitMQ
对于我的业务来说就是
- 数据量没有那么大并且使用场景比较简单
- 同时rabbitmq本身可以支撑高并发、高吞吐、性能很高
- 同时有非常完善便捷的后台管理界面可以使用
- 还可以支持集群
- 社区活跃有问题能够及时得到解决
所以rabbitMQ就足够了。
5. 使用rabbitmq的场景。
- 服务间异步通信
- 顺序消费
- 定时任务
- 请求削峰
- 消息推送
6. rabbitmq四大核心概念
- 生产者产生数据发送消息的程序。
- 交换机是 RabbitMQ 非常重要的一个部件一方面它接收来自生产者的消息另一方面它将消息推送到队列中。交换机必须确切知道如何处理它接收到的消息是将这些消息推送到特定队列还是推送到多个队列亦或者是把消息丢弃这个得有交换机类型决定。
- 队列队列是 RabbitMQ 内部使用的一种数据结构尽管消息流经 RabbitMQ 和应用程序但它们只能存储在队列中。队列仅受主机的内存和磁盘限制的约束本质上是一个大的消息缓冲区。许多生产者可以将消息发送到一个队列许多消费者可以尝试从一个队列接收数据。
- 消费者大多时候是一个等待接收消息的程序。请注意生产者消费者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又是可以是消费者。
7. rabbitmq相关名词介绍
-
Producer 消息生产者就是投递消息的程序
-
Connectionproducer/consumer 和 broker 之间的 TCP 连接。
-
Channel信道如果每一次访问 RabbitMQ 都建立一个 Connection在消息量大的时候建立 TCP Connection 的开销将是巨大的效率也较低。Channel 是在 connection 内部建立的逻辑连接如果应用程序支持多线程通常每个线程创建单独的 channel 进行通讯AMQP method 包含了 channel id 帮助客户端和 message broker 识别 channel所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销。每个channel代表一个会话任务由Exchange、Queue、RoutingKey三个才能决定一个从Exchange到Queue的唯一的线路。
-
Broker接收和分发消息的应用RabbitMQ Server 就是 Message Broker。简单来说就是消息队列服务器实体
-
Virtual host出于多用户和安全因素设计的把 AMQP 的基本组件划分到一个虚拟的分组中类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时可以划分出多个 vhost每个用户在自己的 vhost 创建 exchange/queue 等。可以理解为虚拟broker 即mini-RabbitMQ server。其内部均含有独立的queue、exchange和binding等但最最重要的是其拥有独立的权限系统可以做到vhost范围的用户控制。当然从RabbitMQ的全局角度vhost可以作为不同权限隔离的手段一个典型的例子就是不同的应用可以跑在不同的 vhost 中。
-
Exchange消息交换机。message 到达 broker 的第一站根据分发规则匹配查询表中的 routing key分发消息到 queue 中去。常用的类型有direct (point-to-point), topic (publish-subscribe) and fanout (multicast)。
-
Queue消息队列载体每个消息都会被投入到一个或多个队列。消息最终被送到这里等待 consumer 取走。
-
Binding绑定exchange 和 queue 之间的虚拟连接即将exchange和queue按照路由规则绑定起来binding 中可以包含 routing keyBinding 信息被保存到 exchange 中的查询表中用于 message 的分发依据
-
Routing Key 路由关键字exchange根据这个关键字进行消息投递
8. 消息的分发方式有哪些
- 轮询同一个队列的多个消费者按顺序消费队列中的消息
- 不公平分发通过设置
channel.basicQos(1);
来设置不公平分发0是公平分发只要有消费者空闲并且队列中有消息就接收队列中的消息并执行。 - 预取值分发队列中有7个消息A消费者设置了预取值为2B为5则会以轮询的方式直到A的信道上有2条消息B有5条接下来再有第8条第9条就以不公平方式分发。通过
channel.basicQos(prefetchCount);
设置
9. rabbitmq如何保证消息不丢失
消息不可靠的情况可能是消息丢失劫持等原因
丢失又分为
- 生产者丢失消息通过发布确认解决
- 消息列表丢失消息消息持久化解决
- 消费者丢失消息消息应答机制解决
9.1 生产者丢失消息即blackholed问题
生产者向 exchange 投递了 message 而由于各种原因导致该message 丢失但发送者却不知道。
可导致 blackholed 的情况
- 向未绑定 queue 的exchange 发送 message
- exchange 以 binding_key key_A 绑定了 queue queue_A但向该 exchange 发送 message 使用的 routing_key 却是 key_B
从生产者弄丢数据这个角度来看RabbitMQ提供transaction和confirm模式来确保生产者不丢消息
9.1.1 transaction模式几乎不用
发送消息前开启事务channel.txSelect(),然后发送消息如果发送过程中出现什么异常事务就会回滚channel.txRollback(),如果发送成功则提交事务channel.txCommit()。然而这种方式有个缺点吞吐量下降
9.1.2 confirm模式发布确认主要用
一旦channel进入confirm模式所有在该信道上发布的消息都将会被指派一个唯一的ID从1开始一旦消息被投递到所有匹配的队列之后
- rabbitMQ就会发送一个ACK给生产者包含消息的唯一ID这就使得生产者知道消息已经正确到达目的队列了
- 如果rabbitMQ没能处理该消息则会发送一个Nack消息给你你可以进行重试操作。
channel.queueDeclare(MqConnectUtil.QUEUE_NAME, true, false, false, null);
// 开启发布确认
channel.confirmSelect();
// 开始时间
long begin = System.currentTimeMillis();
// 准备消息的监听器监听哪些消息成功了哪些消息失败了
// 参数一确认成功的回调函数
// 参数二确认失败的回调函数
// deliveryTag消息的标记
// multiple: 是否为批量确认
channel.addConfirmListener((deliveryTag, multiple) -> {},
(deliveryTag, multiple) -> {log.debug("未确认的消息{}", deliveryTag);});
// 批量发消息
for (int i = 0; i < MESSAGE_COUNT; i++) {
channel.basicPublish("", UUID.randomUUID().toString(), null, String.valueOf(i).getBytes());
}
9.1.3 如何处理confirm模式中为处理未确认的消息
最好的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列 比如说用 ConcurrentSkipListMap在 confirm callbacks 与发布线程之间进行消息的传递。
9.2 消息队列丢数据消息持久化。
处理消息队列丢数据的情况一般是开启持久化磁盘的配置。
这个持久化配置可以和confirm机制配合使用你可以在消息持久化磁盘后再给生产者发送一个Ack信号。
这样如果消息持久化磁盘之前rabbitMQ阵亡了那么生产者收不到Ack信号生产者会自动重发。
9.2.1 关于消息队列持久化步骤
- 持久化队列
之前创建的队列都是非持久化的rabbitmq 如果重启的话该队列就会被删除掉如果要队列实现持久化需要在声明队列的时候把 durable 参数设置为持久化。将queueDeclare的第二个参数设为truechannel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
- 持久化消息
虽然持久化了队列如果消息不进行持久化消息还是会丢失所以我们同样需要持久化消息
9.3 消费者丢失消息消息应答机制
9.3.1 消息应答有两种方式
选用规则是高吞吐量和数据传输安全性方面做权衡
- 自动应答
- 手动应答
消费者丢数据一般是因为采用了自动确认消息模式消费者在收到消息之后处理消息之前会自动回复RabbitMQ已收到消息如果这时处理消息失败就会丢失该消息
9.3.2 解决方案
改为手动确认消息即可处理消息成功后手动回复确认消息。
9.3.3 手动应答的三个方法
- Channel.basicAck (用于肯定确认)RabbitMQ 已知道该消息成功被处理可以将其丢弃了。
- Channel.basicNack (用于否定确认)
- Channel.basicReject (用于否定确认)与 Channel.basicNack 相比少一个参数不处理该消息了直接拒绝可以将其丢弃了。
9.3.4 批量应答
手动应答可以通过设置批量应答来减少网络拥堵。
通过设置
- true 代表批量应答 channel 上未应答的消息比如说队列往信道中放了4个消息 5、6、7、8 当前消息是 8 那么此时 5-8 的这些还未应答的消息都会被确认收到消息应答。
- false 同上面相比只会应答8的消息 5、6、7 这三个消息依然不会被确认收到消息应答。
10. 什么是交换机有哪些类型
生产者生产的消息从不会直接发送到队列 生产者只能将消息发送到交换机 (exchange)然后交换机将消息推入到设置的队列中。
如果我们没有指定交换机用的空字符串的话表示是默认或无名称交换机。消息从路由发送到队列是由routingKey(bindingKey)绑定key指定的。
交换机的类型包括
- 直接 (direct)
- 主题 (topic)
- 标题 (headers)
- 扇出 (fanout)
- 延迟交换机需要安装插件不是默认拥有
可以通过如下代码创建exchange并设置名称和类型
/**
* 声明一个 exchange
* 1.exchange 的名称
* 2.exchange 的类型
*/
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
10.1 扇出交换机fanout
Fanout 这种类型非常简单。正如从名称中猜到的那样它是将接收到的所有消息广播到它知道的所有队列中。哪怕交换机和队列关联的key是不同的也不影响
10.2 直接交换机direct
消息只去到它绑定的 routingKey 队列中去
如上图的绑定关系下生产者发布消息到 exchange 上绑定键为 orange 的消息会被发布到队列 Q1。绑定键为 black或green的消息会被发布到队列 Q2其他消息类型的消息将被丢弃
10.2.1 多重绑定
如果 exchange 的绑定类型是 direct但是它绑定的多个队列的 key 如果都相同在这种情况下虽然绑定类型是 direct 但是它表现的就和 fanout 有点类似了就跟广播差不多
10.3 主题交换机Topic
简单说就是routingkey模糊匹配的交换机
当我们的队列想要接收某个类型的消息的时候比如现在的消息有桔子苹果汽水、桔子橙子汽水、西瓜苹果汽水我的某个队列只想要有桔子的汽水这时候使用direct类型的交换机就比较难做到了这时候我们可以使用主题交换机。
发送到类型是 topic 交换机的消息的 routing_key 不能随意写必须满足一定的要求它必须是一个单词列表以点号分隔开。这些单词可以是任意单词。比如说”stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”. 这种类型的。当然这个单词列表最多不能超过 255 个字节。
在这个规则列表中其中有两个替换符是大家需要注意的
- *(星号) 可以代替一个单词
- #(井号) 可以替代零个或多个单词
例
- Q1–> 绑定的是
- 中间带 orange 带 3 个单词的字符串 (.orange.)
- Q2–> 绑定的是
- 最后一个单词是 rabbit 的 3 个单词 (..rabbit)
- 第一个单词是 lazy 的多个单词 (lazy.#)
10.4 延迟交换机
需要通过安装延迟队列插件来实现延迟交换机默认是不自带的。
功能生产者将消息直接分给延迟交换机延迟交换机在经过延迟时间后将消息分发。
11. 交换机和队列是如何关联的bindings是什么
交换机是如何跟队列进行关联的呢就是通过bingdings我们可以在bindings中设置routingkey并关联一个队列将交换机和队列进行绑定。
- 一个交换机可以和多个队列绑定
- 一个交换机可和一个队列建立多个绑定关系routingKey不同
11.1 如何进行绑定
- 可以在rabbitmq的操作界面对交换机进行bindings设置设置routingkey从而绑定队列。
- 也可以在代码中设置‘01’是routingkey
channel.queueBind(queueName, EXCHANGE_NAME, "01")
12. 什么是临时队列
没有持久化的队列就是临时队列。每当我们连接到mq时我们都需要一个全新的空队列为此我们可以创建一个具有随机名称的队列一旦我们断开了消费者的连接临时队列将被自动删除。
看Features是否有D的标识如果没有就说明是临时队列。
13. 什么是死信队列
producer 将消息投递到 broker 或者直接到 queue 里了consumer 从 queue 取出消息进行消费但某些时候由于特定的原因导致 queue 中的某些消息无法被消费这样的消息如果没有后续的处理就变成了死信有死信自然就有了死信队列。
死信进入到死信队列后可以被专门用于处理死信队列消息的消费者消费。
13.1 死信产生的原因
- 消息TTL过期
- 队列达到最大长度队列满了无法再添加数据到mq中
- 消息被拒绝basci.reject或者basic.nack并且requeue = false
13.2 死信队列也满了怎么办
如果出现死信队列和普通队列都满的情况此时考虑消费者消费能力不足可以对消费者开多线程进行处理。
13.3 死信的应用
应用场景
- 为了保证订单业务的消息数据不丢失需要使用到 RabbitMQ 的死信队列机制当消息发生异常时将消息投入死信队列中。
- 后续等到环境好了之后再消费死信队列中的消息
- 用户在商城下单成功并点击去支付后在指定时间未支付时自动失效
- 当做延迟队列来处理死信可以在指定时间内被消费者消费
14. 延迟队列
概念延时队列就是用来存放需要在指定时间被处理的元素的队列
14.1 应用场景
- 订单在十分钟之内未支付则自动取消
- 新创建的店铺如果在十天内都没有上传过商品则自动发送消息提醒
- 用户注册成功后如果三天内没有登陆则进行短信提醒
- 用户发起退款如果三天内没有得到处理则通知相关运营人员
- 预定会议后需要在预定的时间点前十分钟通知各个与会人员参加会议
这些场景都有一个特点需要在某个事件发生之后或者之前的指定时间点完成某一项任务。
14.2 实现方式延迟交换机
需要通过安装延迟队列插件来实现延迟交换机默认是不自带的。
功能生产者将消息直接分给延迟交换机延迟交换机在经过延迟时间后将消息分发。
15. 如何避免消息重复投递或重复消费
首先谈谈什么是幂等性用户对于同一操作发起的一次请求或者多次请求的结果是一致的不会因为多次点击而产生了副作用。
15.1 什么是重复消费
消费者在消费 MQ 中的消息时MQ 已把消息发送给消费者消费者在给 MQ 返回 ack 时网络中断 故 MQ 未收到确认信息该条消息会重新发给其他的消费者或者在网络重连后再次发送给该消费者但实际上该消费者已成功消费了该条消息造成消费者消费了重复的消息。
15.2 什么是消息重复投递
在海量订单生成的业务高峰期生产端有可能就会重复发生了消息这时候消费端就要实现幂等性 这就意味着我们的消息永远不会被消费多次即使我们收到了一样的消息。
15.3 如何解决
MQ 消费者的幂等性的解决一般使用全局 ID 或者写个唯一标识比如时间戳或者 UUID 订单消费者消费 MQ 中的消息也可利用 MQ 的该 id 来判断或者可按自己的规则生成一个全局唯一 id每次消费消息时用该 id 先判断该消息是否已消费过。
业界主流的幂等性有两种操作:
- 唯一 ID + 指纹码机制用数据库主键去重
指纹码我们的一些规则或者时间戳加别的服务给到的唯一信息码它并不一定是我们系统生成的基本都是由我们的业务规则拼接而来但是一定要保证唯一性然后就利用查询语句进行判断这个 id 是否存在数据库中优势就是实现简单就一个拼接然后查询判断是否重复劣势就是在高并发时如果是单个数据库就会有写入性能瓶颈当然也可以采用分库分表提升性能但也不是我们最推荐的方式 - 利用 redis 的原子性去实现。
利用 redis 执行 setnx 命令天然具有幂等性从而实现不重复消费。
当然也可以根据业务不同去进行操作
- 比如你拿到这个消息做数据库的insert操作。那就容易了给这个消息做一个唯一主键那么就算出现重复消费的情况就会导致主键冲突避免数据库出现脏数据。
- 再比如你拿到这个消息做redis的set的操作那就容易了不用解决因为你无论set几次结果都是一样的set操作本来就算幂等操作。
16. 什么是优先级队列
简单说就是根据队列权重优先消费权重高的消息
16.1 使用场景
在我们系统中有一个订单催付的场景我们的客户在天猫下的订单淘宝会及时将订单推送给我们如果在用户设定的时间内未付款那么就会给用户推送一条短信提醒很简单的一个功能对吧。
但是天猫商家对我们来说肯定是要分大客户和小客户的对吧比如像苹果、小米这样大商家一年起码能给我们创造很大的利润所以理应当然他们的订单必须得到优先处理。
所以苹果小米等大客户的催付订单我们可以放到优先级高的队列中其他的催付订单可以放到优先级较低的队列中。
17. 什么是惰性队列你怎么理解
惰性队列会尽可能的将消息存入磁盘中而在消费者消费到相应的消息时才会被加载到内存中它的一个重要的设计目标是能够支持更长的队列即支持更多的消息存储。当消费者由于各种各样的原因 (比如消费者下线、宕机亦或者是由于维护而关闭等) 而致使长时间内不能消费消息造成堆积时惰性队列就很有必要了。
18. 消息是基于什么传输的
由于TCP连接的创建和销毁开销较大且并发数受系统资源限制会造成性能瓶颈。RabbitMQ使用信道的方式来传输数据。信道是建立在真实的TCP连接内的虚拟连接且每条TCP连接上的信道数量没有限制
19. 消息队列有什么缺点
19.1 系统可用性降低
系统引入的外部依赖越多越容易挂掉本来你就是A系统调用BCD三个系统的接口就好了人 ABCD四个系统好好的没啥问题你偏加个MQ进来万一MQ挂了咋整MQ挂了整套系统崩溃了你不就完了么。
系统复杂性提高硬生生加个MQ进来你怎么保证消息没有重复消费怎么处理消息丢失的情况怎么保证消息传递的顺序性头大头大问题一大堆痛苦不已。
19.2 一致性问题
A系统处理完了直接返回成功了人都以为你这个请求就成功了但是问题是要是BCD三个系统那里BD两个系统写库成功了结果C系统写库失败了咋整你这数据就不一致了。
所以消息队列实际是一种非常复杂的架构你引入它有很多好处但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉最好之后你会发现妈呀系统复杂度提升了一个数量级也许是复杂了10倍。但是关键时刻用还是得用的
19.3 系统复杂度提高
加入了消息队列要多考虑很多方面的问题比如一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。因此需要考虑的东西更多复杂性增大
20. rabbitmq的工作模式有哪些
20.1 简单模式无需交换机
- 消息产生消息将消息放入队列
- 消息的消费者(consumer) 监听 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患 消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失这里可以设置成手动的ack,但如果设置成手动ack处理完后要及时发送ack消息给队列否则会造成内存溢出)。
20.2 工作模式竞争模式无需交换机
- 消息产生者将消息放入队列消费者可以有多个消费者1和消费者2同时监听同一个队列,消息被消费。
- C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize) 保证一条消息只能被一个消费者使用)。
20.3 发布订阅模式三种通过交换机实现
- 1个生产者多个消费者
- 每一个消费者都有自己的一个队列
- 生产者没有将消息直接发送到队列而是发送到了交换机
- 每个队列都要绑定到交换机
- 生产者发送的消息经过交换机到达队列实现一个消息被多个消费者获取的目的
- 交换机一方面接收生产者发送的消息。另一方面知道如何处理消息例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作取决于Exchange的类型。
- Fanout广播将消息交给所有绑定到交换机的队列
- Direct定向把消息交给符合指定routing key 的队列
- Topic通配符把消息交给符合routing pattern路由模式 的队列
- Exchange交换机只负责转发消息不具备存储消息的能力因此如果没有任何队列与Exchange绑定或者没有符合路由规则的队列那么消息会丢失
20.3.1 广播模型fanout交换机
- 多个消费者每个消费者绑定自己的队列
- 每个队列都需要绑定交换机
- 生产者生产消息交给交换机生产者不能决定消息被交换机分发给哪个队列
- 交换机把消息发送给绑定过的所有队列
- 订阅该队列的消费者都能消费消息实现一个消息被多个消费者消费
20.3.2 direct模型direct交换机
在Direct模型下队列与交换机的绑定不能是任意绑定了而是要指定一个RoutingKey路由key消息的发送方在向Exchange发送消息时也必须指定消息的routing key。
- P生产者向Exchange发送消息发送消息时会指定一个routing key。
- XExchange交换机接收生产者的消息然后把消息递交给 与routing key完全匹配的队列
- C1消费者其所在队列指定了需要routing key 为 error 的消息
- C2消费者其所在队列指定了需要routing key 为 info、error、warning 的消息
20.3.3 主题模型topic交换机
Topic 类型的 Exchange 与 Direct 相比都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型 Exchange 可以让队列在绑定 Routing key 的时候使用通配符
- 星号井号代表通配符
- 星号代表1个单词,井号代表1到多个单词
21. 如何保证rabbitmq的消息的顺序性
拆分多个queue(消息队列)每个queue(消息队列) 一个consumer(消费者)就是多一些queue(消息队列)而已确实是麻烦点
或者就一个queue (消息队列)但是对应一个consumer(消费者)然后这个consumer(消费者)内部用内存队列做排队然后分发给底层不同的worker来处理
22. 为什么不对所有的消息进行持久化
- 必然导致性能的下降因为写磁盘比写RAM慢的多message的吞吐量可能有10倍的差距
- message的持久化机制用在RabbitMQ的内置cluster方案时会出现“坑爹”问题。矛盾点在于若message设置了persistent属性但queue未设置durable属性那么当该queue的owner node出现异常后在未重建该queue前发往该queue 的message将被 blackholed若 message 设置了 persistent属性同时queue也设置了durable属性那么当queue的owner node异常且无法重启的情况下则该queue无法在其他node上重建只能等待其owner node重启后才能恢复该 queue的使用而在这段时间内发送给该queue的message将被 blackholed 。
所以是否要对message进行持久化需要综合考虑性能需要以及可能遇到的问题。若想达到100,000 条/秒以上的消息吞吐量单RabbitMQ服务器则要么使用其他的方式来确保message的可靠delivery 要么使用非常快速的存储系统以支持全持久化例如使用SSD。
另外一种处理原则是仅对关键消息作持久化处理根据业务重要程度且应该保证关键消息的量不会导致性能瓶颈
23. rabbitmq是如何保证高可用的集群如何搭建
RabbitMQ是比较有代表性的因为是基于主从非分布式做高可用性的我们就以RabbitMQ为例子讲解第一种MQ的高可用性怎么实现。RabbitMQ有三种模式单机模式、普通集群模式、镜像集群模式。
23.1 单机模式
就是Demo级别的一般就是你本地启动了玩玩儿的?没人生产用单机模式
23.2 普通集群模式
意思就是在多台机器上启动多个RabbitMQ实例每个机器启动一个。
你创建的queue只会放在一个RabbitMQ实例上但是每个实例都同步queue的元数据元数据可以认为是queue的一些配置信息通过元数据可以找到queue所在实例。
你消费的时候实际上如果连接到了另外一个实例那么那个实例会从queue所在实例上拉取数据过来。这方案主要是提高吞吐量的就是说让集群中多个节点来服务某个queue的读写操作。
23.3 镜像集群模式
这种模式才是所谓的RabbitMQ的高可用模式。跟普通集群模式不一样的是在镜像集群模式下你创建的queue无论元数据还是 queue 里的消息都会存在于多个实例上就是说每个RabbitMQ节点都有这个queue的一个完整镜像包含queue的全部数据的意思。然后每次你写消息到queue的时候都会自动把消息同步到多个实例的queue上。
RabbitMQ有很好的管理控制台就是在后台新增一个策略这个策略是镜像集群模式的策略指定的时候是可以要求数据同步到所有节点的也可以要求同步到指定数量的节点再次创建queue的时候应用这个策略就会自动将数据同步到其他的节点上去了。
这样的好处在于你任何一个机器宕机了没事儿其它机器节点还包含了这个queue的完整数据别的consumer都可以到其它节点上去消费数据。坏处在于第一这个性能开销也太大了吧消息需要同步到所有机器上导致网络带宽压力和消耗很重RabbitMQ一个queue的数据都是放在一个节点里的镜像集群下也是每个节点都放这个queue的完整数据。
24. 有几百万消息持续积压几小时怎么办
临时紧急扩容
- 先修复consumer的问题确保其恢复消费速度然后将现有consumer都停掉。
- 新建一个topicpartition是原来的10倍临时建立好原先10倍的queue数量。
- 然后写一个临时的分发数据的consumer程序这个程序部署上去消费积压的数据消费之后不做耗时的处理直接均匀轮询写入临时建立好的10倍数量的queue。
- 接着临时征用10倍的机器来部署consumer每一批consumer消费一个临时queue的数据。这种做法相当于是临时将 queue 资源和consumer资源扩大10倍以正常的10倍速度来消费数据。
- 等快速消费完积压数据之后得恢复原先部署的架构重新用原先的consumer机器来消费消息。
25. 如何解决消息队列的延时以及过期失效问题
假设你用的是RabbitMQRabbtiMQ是可以设置过期时间的也就是 TTL。如果消息在queue中积压超过一定的时间就会被RabbitMQ给清理掉这个数据就没了。那这就是第二个坑了。
这就不是说数据会大量积压在mq里而是大量的数据会直接搞丢。我们可以采取一个方案就是批量重导这个我们之前线上也有类似的场景干过。就是大量积压的时候我们当时就直接丢弃数据了然后等过了高峰期以后比如大家一起喝咖啡熬夜到晚上12点以后用户都睡觉了。这个时候我们就开始写程序将丢失的那批数据写个临时程序一点一点的查出来
然后重新灌入mq里面去把白天丢的数据给他补回来。也只能是这样了。假设1万个订单积压在mq里面没有处理其中 1000个订单都丢了你只能手动写程序把那1000个订单给查出来手动发到mq里去再补一次。
26. 消息队列满了以后该怎么处理
27. 可以在地理上分开的不同数据中心使用 RabbitMQ cluster 么
不能。第一你无法控制所创建的 queue 实际分布在 cluster 里的哪个 node 上一般使用 HAProxy + cluster 模型时都是这样这可能会导致各种跨地域访问时的常见问题第二Erlang 的 OTP 通信框架对延迟的容忍度有限这可能会触发各种超时导致业务疲于处理第三在广域网上的连接失效问题将导致经典的“脑裂”问题而RabbitMQ 目前无法处理该问题主要是说 Mnesia
28. 设计MQ思路。
比如说这个消息队列系统我们从以下几个角度来考虑一下
首先这个mq得支持可伸缩性吧就是需要的时候快速扩容就可以增加吞吐量和容量那怎么搞设计个分布式的系统呗参照一下kafka的设计理念broker->topic->partition每个partition放一个机器就存一部分数据。如果现在资源不够了简单啊给topic增加partition然后做数据迁移增加机器不就可以存放更多数据提供更高的吞吐量了
其次你得考虑一下这个mq的数据要不要落地磁盘吧那肯定要了落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊顺序写这样就没有磁盘随机读写的寻址开销磁盘顺序读写的性能是很高的这就是 kafka 的思路。
其次你考虑一下你的mq的可用性啊这个事儿具体参考之前可用性那个环节讲解的kafka的高可用保障机制。多副本 -> leader&follower->broker挂了重新选举 leader 即可对外服务。能不能支持数据0丢失啊可以呀有点复杂的。