为什么用MQ?
解耦:
- A系统向BCD系统发送数据,调用接口发送,如果新来的E系统需要数据,旧的D系统不需要数据,那么就需要频繁的修改A系统的代码,并且还需要考虑BCD系统挂掉的问题,数据非常重要的话,是要重发呢?还是暂时存起来?
- 但是如果使用MQ,A系统只需要考虑把数据发送到MQ就不用操心别的了,不用管发送给谁,不用考虑人家是否调用成功/失败,是否超时?新来的系统需要就消费MQ的消息,不需要了就不消费
- 通过MQ这种pub/sub发布订阅模型,A系统就能和BCD系统彻底解欧,一个系统调用了多个系统,调用复杂,维护难度高,但是这个调用不需要同步调用接口才能实现,那么就可以使用MQ异步化解藕,
异步:
- A系统接收请求,需要入库123,同步定义的接口内部处理方式的处理时间为5(发送请求) + 300+500+800ms(处理请求),响应给用户的,用户等待时间过长
- 如果采用MQ异步的方式,A系统发送消息到MQ,然后直接A系统直接返回结果,剩下的入库操作123由其它服务异步完成,给用户那么只需5ms,响应时间非常短。
削峰:
- 某个时间段,每秒并发请求数量(QPS)会增加至5000左右,请求会打到mysql,mysql的并发2000就差不多了,这么大的并发量会把mysql打挂,导致系统崩溃
- 如果使用MQ,请求写入到MQ中,A系统根据自身消费能力去MQ中慢慢消费,这样的话服务mysql不会被打挂,但是MQ会有大量消息堆积
缺点:
- MQ挂掉,整个系统就完了,系统可用性降低了
- 发送消息如何保证没重复消费,如何处理消息丢失,如何保证消息处理的顺序性
- 异步处理时,你返回给用户成功,但是入库123操作有异常,你这到底是成功还是失败?数据一致性怎么保证?
消息队列选型?
- 吞吐量,达到10万级别
- 时效性,在ms级别
- 高可用性,分布式架构,一台挂了另一台能顶上
- 消息可靠性,经过参数优化配置可以做到0丢失
如何保证消息队列的高可用?
- Producer 与 NameServer集群中的其中一个节点(随机选择)建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master/Slave 建立长连接,且定时向 Broker 发送心跳。Producer 只能将消息发送到 Broker master,但是 Consumer 则不一样,它同时和提供 Topic 服务的 Master 和 Slave建立长连接,既可以从 Broker Master 订阅消息,也可以从 Broker Slave 订阅消息。
如何保证消息不被重复消费?
- 首先重复消费很正常,各种意外情况都有可能导致重复消费,这不是消息队列来保证的,而是我们自己要保证的,比如kafaka的消费数据后提交offset,然后系统重启可以根据offset继续消费,但是如果kill进程重启系统,有可能出现offset没提交到zookeeper中进而zookeeper也无法给kafaka,那么下次重启就会重复消费之前消费过的信息,如果消费消息是插入数据库行为就会导致数据重复插入
- 重复消费不可怕,只要做好幂等即可,比如插入数据时先判断一下是否已经消费过了,具体做就是根据业务场景来做了,比如数据唯一索引(唯一主键),redis的写入set集合,发送消息时有全局唯一id,这样同一条消息的全局唯一id一样,消费第二次时判断一下就能判断出来已经消费过了
如何保证消息可靠性(处理消息丢失问题)?
- 生产者到MQ丢失问题
- 事务机制,发送消息失败,生产者收到异常报错,可以回滚事务,尝试重发消息,发送消息成功可以提交事务,缺点就是事务是同步的,事务开启后到提交/回滚之前都是阻塞的
- 开启生产者的confirm机制,消息发送到MQ后,成功则返回ack消息,MQ接受失败则回调nack接口告知生产者失败,可以重试,好处就是confirm机制是异步的,发送消息后可以继续发送消息做其他操作,MQ会自动异步回调nack接口
- MQ自身丢失问题
- 开启持久化机制保存消息到磁盘中,除非极小概率发生还没持久化就挂掉的问题,可能性很小
- 可以配合confim机制,只有消息持久化到磁盘中才返回ack,如果没有ack/回调了nack接口,那么生产者还可以重试,重发消息
- 消费端丢失问题
- 也就是刚消费到数据,还没处理,进程挂了,比如重启了,那么MQ认为你消费了数据就相当于丢失了
- 利用MQ的ack机制,关闭自动ack,手动进行ack,只有处理完才ack,这样这边没处理完,不会ack,MQ那边不会丢失消息
如何保证消息消费的顺序?
-
mysql的binlog日志发送到mq,然后再消费出来执行数据同步,其中增删改的顺序就很重要
-
问题就在于一个queue绑定多个消费者consumer,同一条消息只会被一个消费者consumer消费,这样对于消费顺序有要求的场景就无法区分谁先消费,谁先处理,无法保证顺序
-
解决方案:
- 一个queue对应一个consumer,弄多个queue,这样发数据多份到每个queue,每一个消费者consumer消费的都是有序的
- 或者一个queue对一个consumer,维护多个内存队列
-
场景:
订单创建,订单付款,订单完成,同一个订单有序即可,不同订单不用有序,同一分区内的消息保证顺序,不同分区之间的消息顺序不做要求
-
RabbitMQ出现消费顺序错乱的情况
- 为了提高处理效率,一个queue存在多个consumer
- 一个queue只存在一个consumer,但是为了提高处理效率,consumer中使用了多线程进行处理----->不能解决
-
保证消息顺序性的方法:
- 将原来的一个queue拆分成多个queue,每个queue都有一个自己的consumer。该种方案的核心是生产者在投递消息的时候根据业务数据关键值(例如订单ID哈希值对订单队列数取模)来将需要保证先后顺序的同一类数据(同一个订单的数据) 发送到同一个queue当中,这样正常单线程消费就可以
- 一个queue就一个consumer,在consumer中维护多个内存队列,根据业务数据关键值(例如订单ID哈希值对内存队列数取模)将消息加入到不同的内存队列中,然后多个真正负责处理消息的线程去各自对应的内存队列当中获取消息进行消费。同分区有序相同