一、消息队列概述
(1)为什么要使用消息队列
- 使用消息队列就是因为六个字,解耦、异步、削峰
- 下面使用传统模式和消息队列的模式来进行对比,来显示消息队列的作用
-解耦
-传统模式
- 缺点:
1.系统之间的耦合性太强,也就是依赖性太大,系统A想要去调用新的代码就需要修改之前的代码,过于麻烦 2.系统A不管是去调用B、C、D系统的接口还是去调用新增加的E系统的接口,A系统都需要做出改变,这样使A系统和其他系统的耦合性比较高
-消息队列(中间件)
- 优点
1.显而易见的,将A系统的数据放入MQ中间件,然后其他系统主动去MQ拿,这种模式使各个系统之间的
-异步
- 场景说明:将用户输入的注册信息写入数据库后,系统需要给用户发送注册邮件和注册短信
-传统模式
串行: 将注册信息写入数据库后,先发送注册邮件,再发送注册短信
并行: 将注册信息写入数据库后,在发送注册邮件的同时,发送注册短信
- 传统模式都需要在发送注册邮件和短信上进行响应,虽然并行的速度比串行快,但是并行的速度还是较慢
-消息队列
- 可以明显看出,消息队列比传统模式来说,响应时间更快,并且消息队列在发送注册邮件和短信上是不需要进行响应的,所以大大缩短了响应时间
-削峰
- 场景说明:商品秒杀业务,一般会因为瞬间的访问量过大,从而导致流量暴增,应用程序可能会因此挂掉
-传统模式
**缺点:**在并发量大的时候,所有的数据会直接写入数据库,可能会造成数据库连接异常等情况
-消息队列(中间件)
优点: 使用消息队列的优点就是可以使A系统按照数据库的并发量或者指定的规则去处理请求,从消息队列慢慢去拉取消息,例如:数据库最大并发是500,那么A系统可以每次只拉500的请求去处理,虽然这样降低了处理请求的效率,但是在短暂的高峰期积压是允许的
(2)使用消息队列的缺点
- 系统可用性降低
本来其他系统只要运行的好好的,那自己系统就是正常的,但是如果非要加一个消息队列,那消息队列挂了,整个系统也会挂,这样会使系统可用性降低,得不尝失
- 系统复杂性增加
系统复杂性增加是因为在加入消息队列后,会出现很多方面的问题,比如一致性问题、消息的重复消费
(3)选择消息队列
- 常用的消息队列中间件有:ActiveMQ、RabbitMQ、RocketMQ、Kafka
- 从官网的更新频率来看,ActiveMQ的更新间隔是较长的,其他的更新频率还是比较频繁地
#选择消息队列中间件: 1.中小型软件公司建议使用RabbitMQ,RabbitMQ使用erlang语言开发,erlang语言天生具备高并发的特性,而且RabbitMQ的管理界面用起来非常方便,但是国内的erlang程序员又有多少呢,所幸,RabbitMQ的社区十分活跃,完全可以解决生产环境中遇到的bug,这点对于中小型软件公司十分重要。不考虑RocketMQ和Kafka的原因是,中小型软件公司不如互联网公司,数据量没那么大,选择消息队列中间件应该首选功能比较完备的,所以Kafka显然不适合,而不考虑RocketMQ的原因就是因为它是由阿里出品的,如果阿里放弃维护,那么中小型软件公司一般是抽不出人来进行RocketMQ的定制化开发的,所以不推荐 2.大型软件公司,根据具体使用在rocketMq和kafka之间二选一。一方面,大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量。 针对rocketMQ,大型软件公司也可以抽出人手对rocketMQ进行定制化开发,毕竟国内有能力改JAVA源码的人,还是相当多的。至于kafka,根据业务场景选择,如果有日志采集功能,肯定是首选kafka了。具体该选哪个,看使用场景。
(4)保证消息队列的高可用
-RocketMQ
RocketMQ的集群就有多master 模式、多master多slave异步复制模式、多 master多slave同步双写模式。
多master多slave模式部署架构图:
Producer 与 NameServer集群中的其中一个节点(随机选择)建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 建立长连接,且定时向 Broker 发送心跳。Producer 只能将消息发送到 Broker master,但是 Consumer 则不一样,它同时和提供 Topic 服务的 Master 和 Slave建立长连接,既可以从 Broker Master 订阅消息,也可以从 Broker Slave 订阅消息
-Kafka
如上图所示,一个典型的Kafka集群中包含若干Producer(可以是web前端产生的Page View,或者是服务器日志,系统CPU、Memory等),若干broker(Kafka支持水平扩展,一般broker数量越多,集群吞吐率越高),若干Consumer Group,以及一个Zookeeper集群。Kafka通过Zookeeper管理集群配置,选举leader,以及在Consumer Group发生变化时进行rebalance。Producer使用push模式将消息发布到broker,Consumer使用pull模式从broker订阅并消费消息。
(5)保证消息队列不被重复消费
其实无论是那种消息队列,造成重复消费原因其实都是类似的:
正常情况下,消费者在消费消息时候,消费完毕后,会发送一个确认信息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的消息队列发送的确认信息形式不同,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,kafka实际上有个offset的概念,简单说一下,就是每一个消息都有一个offset,kafka消费过消息后,需要提交offset,让消息队列知道自己已经消费过了。那造成重复消费的原因?,就是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。
解决方法:
1. 比如,你拿到这个消息做数据库的insert操作。那就容易了,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。 2.准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。
(6)保证消费的可靠性传输
每种MQ都要从三个角度来分析:
- 生产者弄丢数据
- 消息队列弄丢数据
- 消费者弄丢数据