主要围绕如下几点进行阐述:
为什么使用消息队列?
使用消息队列有什么缺点?
消息队列如何选型?
如何保证消息队列是高可用的?
如何保证消息不被重复消费?
如何保证消费的可靠性传输?
如何保证消息的顺序性?
1.为什么要使用消息队列?
解耦、异步、削峰
2.使用了消息队列会有什么缺点?
我们引入一个技术,要对这个技术的弊端有充分的认识,才能做好预防。
- 系统可用性降低:如果消息中间件挂了,系统也就挂了。因此,系统可用性会降低。
- 系统复杂性增加:加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。因此,需要考虑的东西更多,复杂性增大。
3.消息队列如何选型?
特性 | ActiveMQ | RabbitMQ | RocketMQ | kafka |
---|---|---|---|---|
开发语言 | java | erlang | java | scala |
单机吞吐量 | 万级 | 万级 | 10万级 | 10万级 |
时效性 | 毫秒级 | 微秒级 | 毫秒级 | 毫秒级以内 |
可用性 | 高(主从架构) | 高(主从架构) | 非常高(分布式架构) | 非常高(分布式架构) |
功能特性 | 成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低;管理界面较丰富 | MQ功能比较完备,扩展性佳 | 只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。 |
还需考虑社区活跃程度,使用场景等。
4.如何保证消息队列是高可用的?
4.1 RocketMQ
架构部署方式:
(1)多master模式;
(2)多master多slave异步复制模式;
(3)多master多slave同步双写模式。
4.1.1 多master多slave模式部署架构图
4.1.2 通信过程如下
(1)生产者:
Producer 与 NameServer集群中的其中一个节点(随机选择)建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 建立长连接,且定时向 Broker 发送心跳。Producer 只能将消息发送到 Broker master。
(2)消费者:
它同时和提供 Topic 服务的 Master 和 Slave建立长连接,既可以从 Broker Master 订阅消息,也可以从 Broker Slave 订阅消息。
4.2 kafka
kafka的拓扑架构图
如上图所示,一个典型的Kafka集群中包含以下四大要素
(1)Producer(可以是web前端产生的Page View,或者是服务器日志,系统CPU、Memory等);
(2)若干broker(Kafka支持水平扩展,一般broker数量越多,集群吞吐率越高);
(3)若干Consumer Group;
(4)一个Zookeeper集群。
Kafka通过Zookeeper管理集群配置,选举leader,以及在Consumer Group发生变化时进行rebalance。Producer使用push模式将消息发布到broker,Consumer使用pull模式从broker订阅并消费消息。
5.如何保证消息不被重复消费?
5.1 消息中间件如何知道消息被正确消费?
正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。
只是不同的消息队列发出的确认消息形式不同。
(1)例如RabbitMQ是发送一个ACK确认消息;
(2)RocketMQ是返回一个CONSUME_SUCCESS成功标志;
(3)kafka实际上有个offet的概念,简单说一下,就是每一个消息都有一个offset,kafka消费过消息后,需要提交offset,让消息队列知道自己已经消费过了。
5.2 那造成重复消费的原因?
就是因为网络传输故障或业务系统故障等等,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将消息分发给其他的消费者。
5.3 如何解决?
这个问题针对业务场景来答,分以下三种情况:
(1)通过数据库设置主键或唯一索引,如果出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
(2)如果拿到消息后仅做redis的set的操作,并且数据多次set并不会对数据产生影响,那此时就不需要解决了。
(3)使用第三方介质来存储消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将id写入redis,那消费者开始消费前,先去redis中查询有没有该记录即可。
6.如何保证消费的可靠性传输?
有三个角度可能会产生丢数据
- 生产者弄丢数据
- 消息队列弄丢数据
- 消费者弄丢数据
6.1 生产者丢数据
从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。
(1)transaction机制就是说,发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。
这种方式有个缺点:吞吐量下降。
(2)confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了。如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。
6.2 消息队列丢数据
一般是开启持久化磁盘的配置。
6.3 消费者丢数据
消费者丢数据一般是因为采用了自动确认消息模式。
解决方案:采用手动确认消息即可。
7.如何保证消息的顺序性
7.1 相对顺序
将需要保持先后顺序的消息hash到同一个消息队列中(RocketMQ中是queue,kafka中就是partition,rabbitMq中就是queue)。在消费端选择一个或者多个消费者。
7.2 绝对顺序
单个producer,单个queue,单个consumer.