Java Web系列文章汇总贴: Java Web知识总结汇总
MQ作用
- 解耦(减少系统间耦合)
- 异步(高性能保证)
- 削峰(高可用保证)
MQ缺点
- 系统可用性降低(MQ的故障)
- 系统复杂性变高(服务与MQ的通讯问题)
- 一致性问题(返回用户成功,后面消息没有全部处理成功)
MQ对比
相同点:
高可用,消息不丢。
差异:
- ActiveMQ:单机吞吐量 每秒万级,偶尔丢消息,社区不活跃。
- RabbitMQ:吞吐量万级,管理界面很好,低时延,社区活跃,基于erLang开发,不易维护。适合中小型公司。
- RocketMQ:吞吐量十万,性能很好,分布式扩展性好。适合大型公司。
- Kafka:吞吐量十万,实时计算,日志采集,易于扩展。适合大数据计算。
MQ高可用
RabbitMQ:镜像复制,每个节点存储所有的元数据与消息
Kafka:多broker、多分区(leader,follower、选举机制)保证
MQ消息消费幂等性保证
出现消费者重复消费原因(Kafka)
消费者消费完某条消息后宕机,此时未上报偏移量。消费者重启后再次拉取消息,即拉取了之前的消息,此时需要消费者自身保证幂等性。
幂等性保证
1、消息给予唯一标识id,处理(如入库)前,需要先判断之前处理的消息key的缓存中是否包含该key,即之前是否处理过该消息,处理过则不重复处理。
2、根据业务灵活选用其他方案。如入库场景,可以使用主键的唯一性维护等。
详细参考:
RabbitMQ之消息确认机制(事务+Confirm)
MQ消息丢失
以下从三个角度考虑该问题
1、生产者发送消息到MQ丢失
RabbitMQ
问题:
网络故障、MQ故障无法接收数据
解决方案:
需要生产者感知消息是否被MQ接收到,失败则重试。两种方式:
- 生产者同步发送消息,失败则重试(简单易行,但是吞吐量会降低)
- 生产者异步发送消息,Confirm机制保证事务,MQ接收到消息后,通知生产者,生产者根据具体消息做出相应
Kafka
问题:
网络故障、MQ故障无法接收数据、分区进行主备切换(leader收到消息后挂了,数据未同步到follower,选举leader后新的leader丢失了数据)
解决方案:
每个分区有多个副本,消息在写入所有分区后才认为写入成功,写入失败就一直重试。
需要设置4个参数:
- 给这个topic设置replication.factor参数,这个值必须大于1,要求每个分区(partition)至少有2个副本
- 在Kafka服务端设置min.insync.replicas参数:这个值必须大于1,这个要求一个leader必须有至少感知到有一个follower还跟自己联系,没掉队,这样才能保证leader挂了还有一个follower
- 在producer端设置acks=all:这个是要求每条数据,必须是写入replica之后,才能认为是写成功了。
- 在producer端设置retries=MAX:要求一旦写入失败,就无限重试,卡在这里了。
2、MQ中消息丢失
RabbitMQ
问题:
消息在内存中,或消息过期被丢弃
解决方案:
消息永久不过期,消息持久化到磁盘中。具体步骤大概是:
- 创建queue的时候将其设置为持久化的,这样可以保证RabbitMQ持久化queue中的元数据,但是不会持久化queue里面的数据
- 发送消息时将消息的deliveryMode设置为2,将消息设置为持久化,此时RabbitMQ就会把消息持久化到磁盘,这样RabbitMQ挂了,重启后也能从磁盘恢复queue的数据
详细参考:
RabbitMQ消息持久化
Kafka
问题:
消息在内存中,或消息过期被丢弃
解决方案:
消息永久不过期,消息持久化到磁盘。每个分区有多个副本,保证高可用。
3、消费者消费消息丢失
问题:
消费者未真正消费完消息,就把消费记录上报给MQ,这样消费者挂掉后重启,会从后面的消息偏移量开始消息,则未真正消费的数据就丢失了。
RabbitMQ & Kafka解决方案:
关闭autoACK,不进行消息的偏移量自动提交,真正消费完消息才提交消费偏移量
MQ消息有序
RabbitMQ
问题:
一个queue,多个consumer,明显会乱序
解决方案:
- 拆分为多个queue,每个queue一个consumer,就是多一些queue而已
- 就一个queue,对应一个consumer,然后consumer内部用内存队列做排队,然后分发给不同的worker来处理
Kafka
问题:
生成消息指定key,保证需要保证有序的消息被分配到同一个分区,Kafka的一个分区对应一个消费者消费,所以能保证有序。但是,一个消费者往往内部有多线程处理消息,这样就乱序了。
解决方案:
消费者中使用多个内存队列,一个线程对应一个内存队列进行消费。保证需要有序的的消息维护在同一个内存队列,这样就能保证消费的有序性。
MQ消息大量积压,如何处理
原因
- 消费者故障
- 消费者依赖的组件故障,导致消费者挂掉
解决方案
需要临时紧急扩容,具体步骤:
- 先恢复consumer的问题,确保其恢复消费速度,然后将现有consumer都停掉
- 新建一个topic,partition数量扩展为原来的10倍,临时建立好原来10倍或20倍的queue数量
- 然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的业务处理,直接均匀轮询写入临时建立好的10倍数量的queue
- 接着使用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据
- 这种做法相当于临时将queue和consumer资源扩大10倍,以正常10倍速度消费积压数据
- 如果当前磁盘已经快爆掉,也可以临时丢弃数据,使用工具收集丢弃的数据,等凌晨再补上数据了
如何设计一个MQ
- 首先MQ要支持可伸缩行,需要的时候快速扩容,就能增加吞吐量和容量,如何做呢?可以参考Kafka的设计理念,broker->topic->partition,每个partition放一个机器,就存一部分数据。如果资源不够了,只需要给topic增加partition,然后做数据迁移,增加机器,就可以存放更多数据,增加吞吐量
- 考虑MQ的数据落地磁盘,即使进程挂掉,数据也不会丢,怎么做呢?顺序写磁盘,这样就没有磁盘随机读写的寻址开销,这样磁盘读写的性能就很高了,这也是Kafka思路。
- 其次考虑MQ的高可用,怎么做?参考Kafka的高可用机制,多副本->leader&follower->broker挂了重新选举leader即可对外服务
- 考虑数据的0丢失,怎么做?也可Kafka的0丢失方案,生产者失败重试,MQ存储高可用,消费者不autoACK,消费完才提交偏移量