MQ
一、消息中间件简介
Message-oriented middleware (MOM) is software or hardware infrastructure supporting sending and receiving messages between distributed systems. 维基百科给出的消息中间件的定义是支持在分布式系统中发送和接受消息的硬件或软件基础设施。消息中间件就是用来解决分布式系统之间消息传递的问题。
二、消息中间件的典型使用场景
1、系统解耦
首先假设有一个核心系统A,其能产生核心数据供下游服务(系统B和系统C)使用。此时最易想到的办法就是A直接把数据发送给B和C,流程如下。
图1
那么问题来了,此时假设又有D、E、F、G等多个系统也需要使用核心数据,此时流程图如下。
图2
我们可以想象一下,假设有上百个系统都需要系统A的核心数据,此时负责系统A的工程师将是崩溃的,一旦有系统加入,A系统就需要修改代码,将数据发送到新加入的系统。反之,如果有系统不再需要A发送数据,那么A系统又得修改代码不再向其发送数据。这样的架构设计耦合度太高了,我们就可以引入消息中间件来实现系统之间的解耦。即核心系统A生产核心数据,然后将核心数据发送到消息中间件,下游消费系统根据自身需求从中间件里获取消息进行消费,当不再需要数据时就不取消息进即可,这样系统之间耦合度就大大降低了。具体流程图如下。
图3
2、异步调用
假设有一个系统调用链路为A调用B耗时20ms,B调用C耗时20ms,而C调用D需要2s,这样下来整个调用需要耗时2040ms。但实际上A调用B,B调用C只需要40ms,而D系统的引入直接导致系统性能下降约50倍。此时我们应该考虑将D系统的调用抽离出来,做一个异步调用。
图4
生活中有一个很形象的例子。我们点一杯奶茶,下单、付款、通知商家制作都很快,然而到匹配外卖小哥配送这个过程很慢。作为用户来说,匹配外卖小哥这个过程延迟一些时间是可以接受的,只要我能快速下单成功,并且在一定时间范围内安排快递小哥送货即可。
按照上面的思路,系统A到系统B再到系统C就直接结束了,然后系统C再将消息发送到消息中间件中,系统D从消息中间件里取消息进行消费,这样子我们系统的性能就提高了接近50倍。过程如下图所示。
图5
3、削峰填谷
假设有一个系统,正常时间也就每秒几百个请求,部署在一个8核16G的机器上,运行起来轻松加愉快。然而突然由于搞一个活动,高峰期请求数达到了几千,出现了瞬时流量高峰,此时最易想到的是加机器,部署个10台机器,也能扛住此时的高并发。
图6
那么问题来了,瞬时流量每天也就那么几十分钟,过后就是正常的每秒几百请求,我们如果部署10台机器,那么平均下来没台机器的请求数也就每秒几十次,这样是不是有点太浪费资源了呢?大部分时候,每秒几百请求,一台机器就能够扛住了,但是为了抗那每天瞬时的高峰,硬是部署了10台机器,每天就那几十分钟有用,别的时候都是浪费资源的。
图7
但是如果仅仅部署一台机器,瞬间高峰就会击垮系统,因为单台机器是不能扛住每秒几千次请求的。这时我们就可以考虑引入消息中间件,进行流量削峰。我们可以部署一层消息队列在机器前面,平时正常的每秒几百次请求,机器就正常的消费消息即可,一旦流量高峰到达时,大量消息会堆积在消息队列里面,机器只需要按照自己的最大负荷从消息队列里面消费,等流量高峰过了,慢慢地队列里面的消息也消费完毕了。此时达到了一个削峰填谷的作用。具体如图所示。
图8
-
为什么使用RabbitMQ
RocketMQ和Kafka的单机吞吐量每秒十万级,相比RabbitMQ和ActiveMQ的每秒万级吞吐量更高。
RocketMQ由阿里开发,客户端语言还不成熟,部分功能要收费。
Kafka吞吐量较高,适用于日志和大数据领域。但不能保证消息投递的可靠性。
ActiveMQ属于老牌产品,api比较完善,但对高并发处理的不好。
RabbitMQ性能虽比不过RocketMQ和Kafka,但是比ActiveMQ好很多,能够保证消息传递的可靠性,数据不会丢失。缺点就是RabbitMQ开发语言是Erlang,ActiveMQ、RocketMQ、Kafka是基于Java的,出现问题更易查看源码。
-
为什么要用MQ
主要从 解耦、异步、削峰 这三个作用考虑
-
MQ的缺点
- 系统的稳定性降低了,如果消息队列挂掉了,那么整套系统都会挂掉。
- 增加了系统的复杂性,需要考虑消息传递有没有重复、消息丢失怎么处理、怎么保证消息传递的顺序性等问题。(消息多了、少了、乱了)
- 一致性会出问题,消息队列异步处理后某个子系统写库失败,主系统仍会成功返回,就会有数据不一致的情况。
-
如何保证消息队列(RabbitMQ)的高可用
RabbitMQ采用基于主从模式的集群架构
RabbitMQ通过镜像集群模式实现高可用,在多台机器上启动多个RabbitMQ实例,创建的queue里的消息都会存在于多个实例上,每次写消息到queue的时候都会将消息同步到各个实例的queue中。
这样可以保证如果某个机器宕机,其他机器的消息队列仍可使用。
缺点是性能开销较大,消息同步到所有机器上,网络带宽压力大。
通过RabbitMQ管理控制台实现,在后台新增一个镜像集群模式的策略,同时指定数据同步到所有节点或指定数量的节点,创建queue的时候应用这个策略就可以实现镜像集群模式。
-
如何保证消息队列的幂等性?保证消息不被重复消费?
当系统成功发送消息到RabbitMQ消息队列的Broker(消息队列服务器),但由于系统性能、网络异常等原因导致没有得到响应,就会重新发送消息到Broker,此时Broker中就会有重复的消息。假如是两条insert语句,就会破坏幂等性,插入重复的数据。
解决的办法就是:
- 如果要写入到MySQL数据库,可以根据数据主键判断,数据存在就改为update而不是insert;
- 如果是写到redis,因为set天然幂等性;
- 后者让生产者每次发消息时都通过雪花算法生成全局唯一id,消费后的数据将全局id记录到redis中,每次处理数据都到redis里查一下是否消费过了。
-
如何保证消息队列的可靠性传输?如何处理消息丢失?
-
如果防止生产者发送消息的时候弄丢数据,可以开启confirm模式。之后生产者每次写的消息都会分配一个唯一id,如果成功写入了RabbitMQ,MQ会通知确认。如果RabbitMQ接收失败会告诉生产者重新发送。
或者开启RabbitMQ的事务功能,如果MQ没有接收到消息会返回给生产者异常信息,让生产者回滚事务,收到信息则提交事务。
confirm机制是异步的,发送一个消息就可以发送另一个消息。但事务是同步的,提交一个事务后会阻塞,消息队列的吞吐量就会降低。
-
防止RabbitMQ弄丢数据,可以开启MQ的持久化,消息写入后持久化到磁盘,系统挂了,恢复之后会自动读取之前存储的数据。也有可能还未持久化数据就挂掉,概率较低。
- 第一步创建queue的时候将其设置为持久化的,可以保证RabbitMQ持久化queue的元数据,但不能持久化queue里的数据;
- 第二部发送消息时将消息的deliveryMode设置为2,将消息设置为持久化的。两步缺一不可。
持久化可以和生产者的confirm模式配合起来,只有消息持久化到磁盘之后,才给生产者发送确认通知,这样即使是持久化之前mq挂了也可以让生产者重新发送消息。
-
消费端弄丢消息是因为刚消费到消息,还没处理,进程就挂了。此时重启,RabbitMQ会认为消费者已经消费,而消费者却没处理,导致消息丢失。
- 可以关闭RabbitMQ的自动ack确认,当确保数据处理完之后,在代码里手动调用ack。此时如果处理失败,MQ并不会收到ack通知,就会将消息分配给别的消费者处理,消息不会丢失。
-
-
如何保证消息的顺序性
当RabbitMQ是一个queue多个consumer的情况就会出现顺序错乱,各个消费者的执行速度不同,会改变原有的消息执行顺序。
- 拆分多个queue,每个queue一个consumer,就是多一些queue而已
- 或者一个queue就是对应一个consumer,然后这个consumer内部用内存队列来做排列,然后分发给底层不同的worker来处理。
-
如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
比如消费端消费后写入数据到MySQL,结果MySQL挂了,消费端宕住;或者其他原因导致消费端效费较慢,导致MQ消息积压。
-
紧急扩容
1)先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉
2)新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量
3)然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue
4)接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据
5)这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据
6)等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息
-
rabbitmq可以设置过期时间TTL,积压会导致消息丢失
过了高峰期之后,在晚上写个临时程序,将丢失的数据查出来重新加到mq里
-
-
如果让你写一个消息队列,该如何进行架构设计?
-
遗留问题
后端mq异步处理
mq怎么提高项目的并发怎么监听的
还有其他类型的方法解决这个问题吗