消息队列:一种“先进先出”的数据结构。
常见应用场景
解耦
系统耦合性越高,容错性越低;任何一个子系统出问题都会导致大量系统不能用。
使用消息队列解耦合。用户提交的请求都放在消息队列中。假设其中某个用户请求的系统发生了故障,需要几分钟才能修复;再这段修复的时间内,消息被缓存在消息队列中。当故障的系统恢复后,再去消息队列去取出用户的请求进行处理即可。且用户感受不到故障的存在,或许只是觉得网卡了一会。
异步
A系统接收到一个请求,需要写入自己的库,同时还要再B、C、D三个系统写入库。最终请求延迟可能是3+300+450+200=953ms。速度很慢。
如果使用MQ,A系统连续发送三条消息到MQ队列中,假设耗时5ms,总共响应时间只要8ms。
但是这种调用并不适合所有的应用场景,如果对于用户的响应需要用到下游三个子系统的处理结果,那么现然MQ是不适用的。
流量削峰
应用系统如果遇到请求暴增的情况,系统可能宕机。用消息队列可以将大量请求缓存起来,分散到较长一段时间去处理,这样可以大大提升系统的稳定性。
如果不用MQ,系统如果负载超过阈值,就会阻止用户发请求,影响体验。。所以用MQ缓存起来,按照系统最大接受能力慢慢处理。
几种MQ的对比
- ActiveMQ 并没有经过大规模场景验证,用的不多,抛弃;
- RabbitMQ 开发语言是erlang,对java工程师很不友好。。。但是开源。如果不考虑二次开发,追求性能和稳定性就推荐使用;
- RocketMQ 基于java,稳定性和性能都不错,容易二次开发,推荐;
- Kafka 适用于大数据的实时计算、日志采集。是业内标准,推荐。
消息队列的缺点
缺点 1:系统可用性降低
当我们引入MQ进行解耦时,如果MQ挂了,会对业务造成影响。
解决办法:
RabbitMQ-普通集群
在多台机器上分别启动RabbitMQ实例
多个实例可以互相通信
创建的queue只会放在一个RabbitMQ上,其他实例都同步这个机器上的MQ的元数据
客户端请求的时候,如果连接的机器没有queue,那么当前实例会从queue所在实例拉取数据。
普通集群的问题:
并没有做多真正的高可用性;拉取数据的开销和单实例瓶颈问题。
RabbitMQ-镜像集群
多个实例启动后,每次客户端写消息到queue时,都会自动把消息同步到多个实例的queue。每个RabbitMQ节点上都有queue的消息数据和元数据。
RocketMQ-双主双从
缺点2: 系统复杂性提高
-
消息丢失怎么办?
丢失情况:- 消息生产者没有成功发送到MQ
- 消息发送给MQ broker后,broker宕机了导致内存数据丢失
- 消费者获得了消息,但是消费者还没处理就宕机了,此时MQ中已经删除了这个消息,消费者重启后不能再消费之前的消息。
处理方案:
- 消息发送给MQ broker后,返回一条确认收到信息
- MQ收到消息后进行持久化操作
- 消费者收到消息处理完毕后再进行ack确认,然后MQ再删除
-
重复消息如何处理?
重复消息的原因主要是网络不可达
具体原因:- 当一条消息已到达服务器,此时网络断了,导致服务器向客户端相应失败。所以客户端觉得没收到又发了一条。
- 和第一点差不多,只不过是MQ向消费者发消息,消费者宕机不应答,所以又发了一条。
处理方法:
- 发送者携带一个全局唯一消息ID
- 消费者获取消息后先根据ID再redis/db中查询是否已经在消费记录中
- 如果没有就正常消费,如果有这个ID就抛弃。
-
如何保证消息传递的顺序性?
1:1:1的架构----全局顺序结构-----不行
分段锁来实现
缺点3: 一致性问题
通过分布式事务解决