MQ总结篇

1. MQ概述

MQ全称Message Queue(消息对列),是在消息的传输中,保存消息的容器。多用于分布式系统中进行通信。

2. MQ优劣势

优势:

  • 应用解耦(提高了系统的容错性和维护性)
  • 异步提速(提升了用户的体验和系统吞吐量)
  • 削峰填谷(提高系统稳定性)

劣势:

  • 系统可用性降低
  • 系统复杂度提高
  • 一致性问题

小结
1.

应用解耦:
在这里插入图片描述
在这里插入图片描述
异步提速:
在这里插入图片描述
在这里插入图片描述
削峰填谷
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
劣势
在这里插入图片描述

3. MQ产品

3.1 基本介绍

在这里插入图片描述

3.2 特性说明

特性ActiveMQRabbitMQRocketMQkafka
单机吞吐量万级,吞吐量比RocketMQ和Kafka要低了一个数量级万级,吞吐量比RocketMQ和Kafka要低了一个数量级10万级,RocketMQ也是可以支撑高吞吐的一种MQ10万级别,这是kafka最大的优点,就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景
topic数量对吞吐量的影响topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topictopic从几十个到几百个的时候,吞吐量会大幅度下降所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源
时效性ms级微秒级,这是rabbitmq的一大特点,延迟是最低的ms级延迟在ms级以内
可用性高,基于主从架构实现高可用性高,基于主从架构实现高可用性非常高,分布式架构非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
消息可靠性有较低的概率丢失数据经过参数优化配置,可以做到0丢失经过参数优化配置,消息可以做到0丢失
功能支持MQ领域的功能极其完备基于erlang开发,所以并发能力很强,性能极其好,延时很低MQ功能较为完善,还是分布式的,扩展性好功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准
优劣势总结非常成熟,功能强大,在业内大量的公司以及项目中都有应用偶尔会有较低概率丢失消息而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少几个月才发布一个版本而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用erlang语言开发,性能极其好,延时很低;吞吐量到万级,MQ功能比较完备而且开源提供的管理界面非常棒,用起来很好用社区相对比较活跃,几乎每个月都发布几个版本分在国内一些互联网公司近几年用rabbitmq也比较多一些但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集

3.3 使用建议

一般的业务系统要引入MQ, 最早大家都用ActiveMQ, 但是现在确实大家用的不多了, 没经过大规模吞吐量场景的验证, 社区也不是很活跃, 所以大家还是算了吧,我个人不推荐用这个了。

后来大家开始用Rabbit MQ, 但是确实erlang语言阻止了大量的Java工程师去深入研究和掌控它, 对公司而言, 几乎处于不可控的状态, 但是 确实人家是开源的,比较稳定的支持,活跃度也高。

不过现在确实越来越多的公司会去用RocketMQ, 确实很不错, 毕竟是阿里出品, 但社区可能有突然黄掉的风险(目前RocketMQ已捐给 Apache, 但Git Hub上的活跃度其实不算高) 对自己公司技术实力有绝对自信的, 推荐用RocketMQ, 否则回去老老实实用Rabbit MQ吧, 人 家有活跃的开源社区,绝对不会黄。

所以中小型公司, 技术实力较为一般, 技术挑战不是特别高, 用RabbitMQ是不错的选择; 大型公司, 基础架构研发实力较强, 用RocketMQ 是很好的选择。

如果是大数据领域的实时计算、日志采集等场景, 用Kafka是业内标准的, 绝对没问题, 社区活跃度很高, 绝对不会黄, 何况几乎是全世界这 个领域的事实性规范。

4. MQ高可用

4.1 RabbitMQ的高可用性

RabbitMQ是比较有代表性的, 因为是基于主从(非分布式) 做高可用性的, 我们就以RabbitMQ为例子讲解第一种MQ的高可用性怎么实现。

Rabbit MQ有三种模式:单机模式、普通集群模式、镜像集群模式。

单机模式
单机模式, 就是Demo级别的, 一般就是你本地启动了玩玩儿的, 没人生产用单机模式。

普通集群模式(无高可用性)
普通集群模式, 意思就是在多台机器上启动多个RabbitMQ实例, 每台机器启动一个。你创建的queue,只会放在一个RabbitMQ实例上,但是每个实例都同步queue的元数据(元数据可以认为是queue的一些配置信息, 通过元数据, 可以找到queue所在实例) 。你消费的时候,实际上如果连接到了另外一个实例, 那么那个实例会从queue所在实例上拉取数据过来。
在这里插入图片描述
这种方式确实很麻烦,也不怎么好,没做到所谓的分布式,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据, 要么固定连接那个queue所在实例消费数据, 前者有数据拉取的开销, 后者导致单实例性能瓶颈。

而且如果那个放queue的实例宕机了, 会导致接下来其他实例就无法从那个实例拉取, 如果你开启了消息持久化, 让RabbitMQ落地存储消息 的话, 消息不一定会丢, 得等这个实例恢复了, 然后才可以继续从这个queue拉取数据。

所以这个事儿就比较尴尬了, 这就没有什么所谓的高可用性, 这方案主要是提高吞吐量的, 就是说让集群中多个节点来服务某个queue的读写 操作。

镜像集群模式(高可用性)
这种模式, 才是所谓的RabbitMQ的高可用模式。跟普通集群模式不一样的是, 在镜像集群模式下, 你创建的queue, 无论是元数据还是 queue里的消息都会存在于多个实例上, 就是说, 每个Rabbit MQ节点都有这个queue的一个完整镜像, 包含queue的全部数据的意思。然 后每次你写消息到queue的时候, 都会自动把消息同步到多个实例的queue上。
在这里插入图片描述
那么如何开启这个镜像集群模式呢?其实很简单, Rabbit MQ有很好的管理控制台, 就是在后台新增一个策略, 这个策略是镜像集群模式的策略, 指定的时候是可以要求数据同步到所有节点的, 也可以要求同步到指定数量的节点, 再次创建queue的时候, 应用这个策略, 就会自动将 数据同步到其他的节点上去了。

这样的话, 好处在于你任何一个机器宕机了, 没事儿, 其它机器(节点) 还包含了这个queue的完整数据, 别的consumer都可以到其它节 点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!第二,这么玩 儿, 不是分布式的, 就没有扩展性可言了, 如果某个queue负载很重, 你加机器, 新增的机器也包含了这个queue的所有数据, 并没有办法线 性扩展你的queue。你想, 如果这个queue的数据量很大, 大到这个机器上的容量无法容纳了, 此时该怎么办呢?

4.2 幂等性问题

其实还是得结合业务来思考,我这里给几个思路:

  • 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了, update一下好吧。
  • 比如你是写Redis, 那没问题了, 反正每次都是set, 天然幂等性。
  • 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西, 然后你这里消费到了之后, 先根据这个id去比如Redis里查一下, 之前消费过吗?如果没有消费过, 你就处理, 然后这个id写Redis。如果消费过了, 那你就别处理了, 保证别重复处理相同的消息即可。
  • 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。

4.3 数据丢失问题

在这里插入图片描述

1. 生产者弄丢了数据
生产者将数据发送到RabbitMQ的时候, 可能数据就在半路给搞丢了, 因为网络问题啥的, 都有可能。
(1) RabbitMq事务机制:
此时可以选择用RabbitMQ提供的事务功能, 就是生产者发送数据之前开启RabbitMQ事务channel.tx Select() , 然后发送消息, 如果消息没有成功被RabbitMQ接收到, 那么生产者会收到异常报错, 此时就可以回滚事务channel.tx Rollback() , 然后重试发送消息; 如果收到了消息, 那么可以提交事务channel.tx Commit()

try {
     // 通过工厂创建连接
     connection = factory.new Connection();
     // 获取通道
     channeL = connection.createChannel();
     // 开启事务
     channel.tx SeLect();
     // 这里发送消息
     channeL.basic Publish (exchange, routingKey, Message Properties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
     // 模拟出现异常
     int result = 1 / 0;
     // 提交事务
     channel.txCommit();
 } catch (IOException | Timeout Exception e){
     // 捕捉异常,回滚事务
     channeL.txRollback();
 }

但是问题是, RabbitMQ事务机制(同步) 一搞, 基本上吞吐量会下来, 因为太耗性能。

(2) confirm机制
所以一般来说, 如果你要确保说写RabbitMQ的消息别丢, 可以开启confirm模式, 在生产者那里设置开启confirm模式之后, 你每次写的消息都会分配一个唯一的id, 然后如果写入了RabbitMQ中, RabbitMQ会给你回传一个ack消息, 告诉你说这个消息ok了。如果 RabbitMQ没能处理这个消息, 会回调你的一个nack接口, 告诉你这个消息接收失败, 你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。

事务机制和confirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿, 但是confirm机制是异步的, 你发送 个消息之后就可以发送下一个消息,然后那个消息RabbitMQ接收了之后会异步回调你的一个接口通知你这个消息接收到了。

所以一般在生产者这块避免数据丢失, 都是用confirm机制的。

2. RabbitMQ弄丢了数据
就是RabbitMQ自己弄丢了数据, 这个你必须开启RabbitMQ的持久化, 就是消息写入之后会持久化到磁盘, 哪怕是RabbitMQ自己挂了, 恢复之后会自动读取之前存储的数据, 一般数据不会丢。除非极其罕见的是, RabbitMQ还没持久化, 自己就挂了,可能导致少量数据丢失,但是这个概率较小。

设置持久化有两个步骤: ·

  • 创建queue的时候将其设置为持久化。这样就可以保证RabbitMQ持久化queue的元数据, 但是它是不会持久化queue里的数据的。
  • ·第二个是发送消息的时候将消息的delivery Mode设置为2。就是将消息设置为持久化的, 此时RabbitMQ就会将消息持久化到磁盘上去。

必须要同时设置这两个持久化才行, RabbitMQ哪怕是挂了, 再次重启, 也会从磁盘上重启恢复queue, 恢复这个queue里的数据。

注意, 哪怕是你给RabbitMQ开启了持久化机制,也有一种可能,就是这个消息写到了RabbitMQ中, 但是还没来得及持久化到磁盘上, 结果不巧,此时RabbitMQ挂了,就会导致内存里的一点点数据丢失。

所以, 持久化可以跟生产者那边的confirm机制配合起来, 只有消息被持久化到磁盘之后,才会通知生产者ack了,所以哪怕是在持久化到磁盘之前, RabbitMQ挂了,数据丢了,生产者收不到ack, 你也是可以自己重发的。

(3) 消费端弄丢了数据 RabbitMQ
消费端弄丢了数据 RabbitMQ如果丢失了数据,主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,RabbitMQ认为你都消费了,这数据就丢了。

这个时候得用RabbitMQ提供的ack机制, 简单来说, 就是你必须关闭RabbitMQ的自动ack,可以通过一个api来调用就行,然后每次你自己代码里确保处理完的时候, 再在程序里ack一把。这样的话, 如果你还没处理完,不就没有ack了?那RabbitMQ就认为你还没处理完,这个时候RabbitMQ会把这个消费分配给别的consumer去处理,消息是不会丢的。

为了保证消息从队列中可靠地到达消费者,RabbitMQ提供了消息确认机制。消费者在声明队列时, 可以指定noAck参数, 当 noAck=false, RabbitMQ会等待消费者显式发回ack信号后,才从内存(和磁盘, 如果是持久化消息) 中移去消息。否则,一旦消息被消费者消费,RabbitMQ会在队列中立即删除它。
在这里插入图片描述

4.4 大量数据堆积问题

几千万条数据在MQ里积压了七八个小时,从下午4点多,积压到了晚上11点多。这个是我们真实遇到过的一个场景,确实是线上故障了, 这个时候要不然就是修复consumer的问题, 让它恢复消费速度, 然后傻傻的等待几个小时消费完毕。这个肯定不能在面试的时候说吧。

一个消费者一秒是1000条,一秒3个消费者是3000条,一分钟就是18万条。所以如果你积压了几百万到上千万的数据,即使消费者恢复 了,也需要大概1小时的时间才能恢复过来。

一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下:

  • 先修复consumer的问题, 确保其恢复消费速度,然后将现有consumer都停掉。
  • 新建一个topic,partition是原来的10倍,临时建立好原先10倍的queue数量。
  • 然后写一个临时的分发数据的consumer程序, 这个程序部署上去消费积压的数据, 消费之后不做耗时的处理, 直接均匀轮询写入临时建 立好的10倍数量的queue。
  • ·接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据。这种做法相当于是临时将queue资源和 consumer资源扩大10倍,以正常的10倍速度来消费数据。
  • 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的consumer机器来消费消息。

4.5 MQ消息过期失效

假设你用的是RabbitMQ, RabbtiMQ是可以设置过期时间的, 也就是TTL。如果消息在queue中积压超过一定的时间就会被RabbitMQ给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在mq里,而是大量的数据会直接搞丢。

这个情况下, 就不是说要增加consumer消费积压的消息, 因为实际上没啥积压, 而是丢了大量的消息。我们可以采取一个方案, 就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖 啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入mq里面去,把白天丢的数据给他补回来。也只能是这样了。

假设1万个订单积压在mq里面,没有处理,其中1000个订单都丢了,你只能手动写程序把那1000个订单给查出来,手动发到mq里去再 补一次。

4.6 如何设计MQ架构

  • 首先这个mq得支持可伸缩性,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka的设计理念, broker->topic->partition, 每个partition放一个机器, 就存一部分数据。如果现在资源不够了, 简单啊, 给topic增加partition, 然后做数据迁移, 增加机器, 不就可以存放更多数据, 提供更高的吞吐量了?
  • 其次你得考虑一下这个mq的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊? 顺序写, 这样就没有磁盘随机读写的寻址开销, 磁盘顺序读写的性能是很高的, 这就是kafka的思路。
  • 其次你考虑一下你的mq的可用性啊?这个事儿, 具体参考之前可用性那个环节讲解的kafka的高可用保障机制。多副本->leader& follower->broker挂了重新选举leader即可对外服务。
  • ·能不能支持数据0丢失啊?可以的, 参考我们之前说的那个kafka数据零丢失方案。

5. RabbitMQ

安装

6. RocketMQ

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值