若是面试官再问你消息队列,就把这篇甩给他!



消息队列连环炮

  1. 项目里怎么样使用 MQ 的?mysql

  2. 为何要使用消息队列?面试

  3. 消息队列有什么优势和缺点?redis

  4. kafka,activemq,rabbitmq,rocketmq 都有什么去呗?sql

  5. 如何保证消息队列高可用?数据库

  6. 如何保证消息不被重复消费?缓存

  7. 如何保证消息的可靠性传输?性能优化

  8. 如何保证消息的顺序性?微信

  9. 写一个消息队列架构设计?网络

消息队列技术选型

解决的问题:多线程

  • 解耦

  • 异步

  • 削峰

不用 MQ 系统耦合场景

A 系统产生了一个比较关键的数据,不少系统须要 A 系统将数据发过来,强耦合(B,C,D,E 系统可能参数不同、一会须要一会不须要数据,A 系统要不断修改代码维护)

A 系统还要考虑 B、C、D、E 系统是否挂了,是否访问超时?是否重试?

使用 MQ 系统解耦场景

  1. 维护这个代码,不须要考虑人家是否调用成功,失败超时

  2. 若是新系统须要数据,直接从 MQ 里消费便可,若是某个系统不须要这条数据就取消对 MQ 消息的消费便可。

总结:经过一个 MQ 的发布订阅消息模型(Pub/Sub), 系统 A 跟其余系统就完全解耦了。

不用 MQ 同步高延迟请求场景

通常互联网类的企业,对用户的直接操做,通常要求每一个请求都必须在 200ms之内,对用户几乎是无感知的。

使用 MQ 进行异步化以后的接口性能优化

提升高延时接口

没有用 MQ 时高峰期系统被打死的场景

高峰期每秒 5000 个请求,每秒对 MySQL 执行 5000 条 SQL(通常MySQL每秒 2000 个请求差很少了),若是MySQL被打死,而后整个系统就崩溃,用户就没办法使用系统了。可是高峰期过了以后,每秒钟可能就 50 个请求,对整个系统没有任何压力。

使用 MQ 进行削峰的场景

5000 个请求写入到 MQ 里面,系统 A 每秒钟最多只能处理 2000 个请求(MySQL 每秒钟最多处理 2000 个请求),系统 A 从 MQ 里慢慢拉取请求,每秒钟拉取 2000 个请求。MQ,每秒钟 5000 个请求进来,结果只有 2000 个请求出去,结果致使在高峰期(21小时),可能有几十万甚至几百万的请求积压在 MQ 中,这个是正常的,由于过了高峰期以后,每秒钟就 50 个请求,可是系统 A 仍是会按照每秒 2000 个该请求的速度去处理。只要高峰期一过,系统 A 就会快速的将积压的消息给解决掉。

算一笔帐,每秒积压在 MQ 里消息有 3000 条,一分钟就会积压 18W 条消息,一个小时就会积压 1000 万条消息。等高峰期一过,差很少须要 1 个多小时就能够把 1000W 条积压的消息给处理掉

架构中引入 MQ 后存在的问题

  • 系统可用性下降

MQ 可能挂掉,致使整个系统崩溃

  • 系统复杂性变高

可能发重复消息,致使插入重复数据;消息丢了;消息顺序乱了;系统 B,C,D 挂了,致使 MQ 消息积累,磁盘满了;

  • 一致性问题

原本应该A,B,C,D 都执行成功了再返回,结果A,B,C 执行成功 D 失败

Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点


建议:中小型公司 RabbitMQ 大公司:RocketMQ 大数据实时计算:Kafka

消息队列高可用

RabbtitMQ 高可用

RabbitMQ有三种模式:单机模式 、普通集群模式、镜像集群模式

  • 单机模式

demo级

  • 普通集群模式(非高可用)

队列的元数据存在于多个实例中,可是消息不存在多个实例中,每次多台机器上启动多个 rabbitmq 实例,每一个机器启动一个。

  • 优势:能够多个机器消费消息,能够提升消费的吞吐量

  • 缺点:可能会在 rabbitmq 内部产生大量的数据传输 ;可用性基本没保障,queue 所在机器宕机,就没办法消费了

没有高可用性可言

  • 镜像集群模式(高可用,非分布式)

队列的元数据和消息都会存在于多个实例中,每次写消息到 queue的时候,都会自动把消息到多个实例的 queue 里进行消息同步。也就 是每一个节点上都有这个 queue 的一个完整镜像(这个 queue的所有数据)。任何一个节点宕机了,其余节点还包含这个 queue的完整数据,其余 consumer 均可以到其余活着的节点上去消费数据都是 OK 的。

缺点:不是分布式的,若是这个 queue的数据量很大,大到这个机器上的容量没法容纳 。

开启镜像集群模式方法:管理控制台,Admin页面下,新增一个镜像集群模式的策略,指定的时候能够要求数据同步到全部节点,也能够要求同步到指定数量的节点,而后你再次建立 queue 的时候 ,应用这个策略,就 会自动将数据同步到其余的节点上去。

  • Kafka 高可用架构

broker进程就是kafka在每台机器上启动的本身的一个进程。每台机器+机器上的broker进程,就能够认为是 kafka集群中的一个节点。

你建立一个 topic,这个topic能够划分为多个 partition,每一个 partition 能够存在于不一样的 broker 上,每一个 partition就存放一部分数据。

这就是自然的分布式消息队列,也就是说一个 topic的数据,是分散放在 多个机器上的,每一个机器就放一部分数据。

分布式的真正含义是每一个节点只放一部分数据,而不是完整数据(完整数据就是HA、集群机制)
Kafka 0.8版本以前是没有 HA 机制的,任何一个 broker 宕机了,那么就缺失一部分数据。

Kafka 0.8之后,提供了 HA 机制,就是 replica 副本机制。

每一个 partition的数据都会同步到其余机器上,造成本身的多个 replica 副本。而后全部 replica 会选举一个 leader。那么生产者、消费者都会和这个 leader 打交道,而后其余 replica 就是 follow。写的时候,leader 负责把数据同步到全部 follower上去,读的时候就直接读 leader 上的数据便可。

若是某个 broker宕机了,恰好也是 partition的leader,那么此时会选举一个新的 leader出来,你们继续读写那个新的 leader便可,这个就 是所谓的高可用性。更多面试题:面试题内容聚合

leader和follower的同步机制:

写数据的时候,生产者就写 leader,而后 leader将数据落地写本地磁盘,接着其余 follower 本身主动从 leader来pull数据。一旦全部 follower同步好数据了,就会发送 ack给 leader,leader收到全部 follower的 ack以后,就会返回写成功的消息给生产者。

消费的时候,只会从 leader去读,可是只有一个消息已经被全部 follower都同步成功返回 ack的时候,这个消息才会被消费者读到。

消息队列重复数据

MQ 只能保证消息不丢,不能保证重复发送

Kafka 消费端可能出现的重复消费问题

每条消息都有一个 offset 表明 了这个消息的顺序的序号,按照数据进入 kafka的顺序,kafka会给每条数据分配一个 offset,表明了这个是数据的序号,消费者从 kafka去消费的时候,按照这个顺序去消费,消费者会去提交 offset,就是告诉 kafka已经消费到 offset=153这条数据了 ;zk里面就记录了消费者当前消费到了 offset =几的那条消息;假如此时消费者系统被重启,重启以后,消费者会找kafka,让kafka把上次我消费到的那个地方后面的数据继续给我传递过来。更多面试题:面试题内容聚合

重复消息缘由:(主要发生在消费者重启后)

消费者不是说消费完一条数据就立马提交 offset的,而是定时按期提交一次 offset。消费者若是再准备提交 offset,可是还没提交 offset的时候,消费者进程重启了,那么此时已经消费过的消息的 offset并无提交,kafka也就不知道你已经消费了 offset= 153那条数据,这个时候kafka会给你发offset=152,153,154的数据,此时 offset = 152,153的消息重复消费了

保证 MQ 重复消费幂等性

幂等:一个数据或者一个请求,给你重复来屡次,你得确保对应的数据是不会改变的,不能出错。
思路:

  • 拿数据要写库,首先检查下主键,若是有数据,则不插入,进行一次update

  • 若是是写 redis,就没问题,反正每次都是 set ,自然幂等性

  • 生产者发送消息的时候带上一个全局惟一的id,消费者拿到消息后,先根据这个id去 redis里查一下,以前有没消费过,没有消费过就处理,而且写入这个 id 到 redis,若是消费过了,则不处理。

  • 基于数据库的惟一键

保证 MQ 消息不丢

MQ 传递很是核心的消息,好比:广告计费系统,用户点击一次广告,扣费一块钱,若是扣费的时候消息丢了,则会不断少钱,聚沙成塔,对公司是一个很大的损失。

RabbitMQ可能存在的数据丢失问题


  • 生产者写消息的过程当中,消息都没有到 rabbitmq,在网络传输过程当中就丢了。或者消息到了 rabbitmq,可是人家内部出错了没保存下来。

  • RabbitMQ 接收到消息以后先暂存在主机的内存里,结果消费者还没来得及消费,RabbitMQ本身挂掉了,就致使暂存在内存里的数据给搞丢了。

  • 消费者消费到了这个消费,可是还没来得及处理,本身就挂掉了,RabbitMQ 觉得这个消费者已经处理完了。

问题 1解决方案:

事务机制:(通常不采用,同步的,生产者发送消息会同步阻塞卡住等待你是成功仍是失败。会致使生产者发送消息的吞吐量降下来)

    channel.txSelect
try {
    //发送消息
catch(Exception e){
    channel.txRollback;
    //再次重试发送这条消息

    channel.txCommit;

confirm机制:(通常采用这种机制,异步的模式,不会阻塞,吞吐量会比较高)

  • 先把 channel 设置成 confirm 模式

  • 发送一个消息到 rabbitmq

  • 发送完消息后就不用管了

  • rabbitmq 若是接收到了这条消息,就会回调你生产者本地的一个接口,通知你说这条消息我已经收到了

  • rabbitmq 若是在接收消息的时候报错了,就会回调你的接口,告诉你这个消息接收失败了,你能够再次重发。

public void ack(String messageId){

}

public void nack(String messageId){
    //再次重发一次这个消息
}

问题 2 解决方案:

持久化到磁盘

  • 建立queue的时候将其设置为持久化的,这样就能够保证 rabbitmq持久化queue的元数据,可是不会持久化queue里的数据

  • 发送消息的时候将 deliveryMode 设置为 2,将消息设置为持久化的,此时 rabbitmq就会将消息持久化到磁盘上去。必须同时设置 2 个持久化才行。

  • 持久化能够跟生产者那边的 confirm机制配合起来,只有消息被持久化到磁盘以后,才会通知生产者 ack了 ,因此哪怕是在持久化到磁盘以前 ,rabbitmq挂了,数据丢了,生产者收不到 ack,你也能够本身重发。

缺点:可能会有一点点丢失数据的可能,消息恰好写到了 rabbitmq中,可是还没来得及持久化到磁盘上,结果不巧, rabbitmq挂了,会致使内存里的一点点数据会丢失。更多面试题:面试题内容聚合

问题 3 解决方案:

缘由:消费者打开了 autoAck机制(消费到一条消息,还在处理中,还没处理完,此时消费者自动 autoAck了,通知 rabbitmq说这条消息已经消费了,此时不巧,消费者系统宕机了,那条消息丢失了,还没处理完,并且 rabbitmq还觉得这个消息已经处理掉了)

解决方案:关闭 autoAck,本身处理完了一条消息后,再发送 ack给 rabbitmq,若是此时还没处理完就宕机了,此时rabbitmq没收到你发的ack消息,而后 rabbitmq 就会将这条消息从新分配给其余的消费者去处理。

Kafka 可能存在的数据丢失问题

消费端弄丢数据

缘由:消费者消费到那条消息后,自动提交了 offset,kafka觉得你已经消费好了这条消息,结果消费者挂了,这条消息就丢了。

例子:消费者消费到数据后写到一个内存 queue里缓存下,消息自动提交 offset,重启了系统,结果会致使内存 queue 里还没来得及处理的数据丢失。

解决方法:kafka会自动提交 offset,那么只要关闭自动提交 offset,在处理完以后本身手动提交,能够保证数据不会丢。可是此时确实仍是会重复消费,好比恰好处理完,还没提交 offset,结果本身挂了,此时确定会重复消费一次 ,作好幂等便可。

Kafka 丢掉消息

缘由:kafka 某个 broker 宕机,而后从新选举 partition 的 leader时,此时其余的 follower 恰好还有一些数据没有同步,结果此时 leader挂了,而后选举某个 follower成 leader以后,就丢掉了以前leader里未同步的数据。更多面试题:面试题内容聚合

例子:kafka的leader机器宕机,将 follower 切换为 leader以后,发现数据丢了
解决方案:(保证 kafka broker端在 leader发生故障,或者leader切换时,数据不会丢)

  • 给 topic设置 replication.factor ,这个值必须大于 1,保证每一个 partition 必须至少有 2 个副本

  • 在 kafka 服务端设置 min.insync.replicas 参数,这个值必须大于 1,这个是要求一个leader至少感知到有至少一个follower还跟本身保持联系,没掉队,这样才能确保 leader挂了还有一个follower,保证至少一个 follower能和leader保持正常的数据同步。

  • 在 producer 端设置 acks =all,这个是要求每条数据,必须是写入全部 replica 以后,才能认为是写成功了。不然会生产者会一直重试,此时设置 retries = MAX(很大的重试的值),要求一旦写入失败,就卡在这里(避免消息丢失)

  • kafka 生产者丢消息

按 2 的方案设置了 ack =all,必定不会丢。它会要求 leader 接收到消息,全部的 follower 都同步 到了消息以后,才认为本次写成功。若是没知足这个条件,生产者会无限次重试 。

消息队列顺序性

背景:mysql binlog 同步的系统,在mysql里增删改一条数据,对应出来了增删改 3 条binlog,接着这 3 条binlog发送到 MQ 里面,到消费出来依次执行,起码是要保证顺序的吧,否则顺序变成了 删除、修改、增长。日同步数据达到上亿,mysql->mysql,好比大数据 team,须要同步一个mysql库,来对公司的业务系统的数据作各类复杂的操做。

场景:

  • rabbitmq,一个queue,多个consumer,这不明显乱了

  • kafka,一个topic,一个partition,一个consumer,内部多线程,这不也乱了

RabbitMQ 消息顺序错乱

RabbitMQ 如何保证消息顺序性

须要保证顺序的数据放到同一个queue里

Kafka 消息顺序错乱

写入一个 partition中的数据必定是有顺序的。

生产者在写的时候,能够指定一个 key,好比订单id做为key,那么订单相关的数据,必定会被分发到一个 partition中区,此时这个 partition中的数据必定是有顺序的。Kafka 中一个 partition 只能被一个消费者消费。消费者从partition中取出数据的时候 ,必定是有顺序的。

Kafka 保证消息顺序性

若是消费者单线程消费+处理,若是处理比较耗时,处理一条消息是几十ms,一秒钟只能处理几十条数据,这个吞吐量过低了。确定要用多线程去并发处理,压测消费者4 核 8G 单机,32 条线程,最高每秒能够处理上千条消息

消息队列延迟以及过时失效

消费端出了问题,不消费了或者消费极其慢。接着坑爹了,你的消息队列集群的磁盘都快写满了 ,都没人消费,怎么办?积压了几个小时,rabbitmq设置了消息过时时间后就没了,怎么办?

例如:

  • 每次消费以后都要写 mysql,结果mysql挂了,消费端 hang 不动了。

  • 消费者本地依赖的一个东西挂了,致使消费者挂了。

  • 长时间没处理消费,致使 mq 写满了。

场景:几千万条数据再 MQ 里积压了七八个小时

快速处理积压的消息

一个消费者一秒是 1000 条,一秒 3 个消费者是 3000 条,一分钟是 18W 条,1000 多 W 条须要一个小时恢复。

步骤:

  • 先修复 consumer 的问题,确保其恢复消费速度,而后将现有的 consumer 都停掉

  • 新建一个topic,partition是原来的 10 倍,临时创建好原先 10 倍或者 20 倍的 queue 数量

  • 而后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费以后不作耗时的处理,直接均匀轮询写入临时创建好的 10 倍数量的 queue

  • 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据

  • 这种作法至关 因而临时将 queue 资源和 consumer 资源扩大 10 倍,以正常 10 倍速度

  • 等快速消费完积压数据以后,恢复原先部署架构 ,从新用原先的 consumer机器消费消息

原来 3 个消费者须要 1 个小时能够搞定,如今 30 个临时消费者须要 10 分钟就能够搞定。

若是用的 rabbitmq,而且设置了过时时间,若是此消费在 queue里积压超过必定的时间会被 rabbitmq清理掉,数据直接搞丢。
这个时候开始写程序,将丢失的那批 数据查出来,而后从新灌入mq里面,把白天丢的数据补回来。

若是消息积压mq,长时间没被处理掉,致使mq快写完满了,你临时写一个程序,接入数据来消费,写到一个临时的mq里,再让其余消费者慢慢消费 或者消费一个丢弃一个,都不要了,快速消费掉全部的消息,而后晚上补数据。

如何设计消息队列中间件架构

  • mq要支持可伸缩性,快速扩容。设计一个分布式的 MQ,broker->topic->partition,每一个 partition 放一个机器,就存一部分数据。若是如今资源不够,给 topic 增长 partition ,而后作数据迁移,增长机器。

  • mq数据落磁盘,避免进程挂了数据丢了,顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这个就是 kafka的思路。

  • mq高可用性。多副本->leader & follower-> broker 挂了从新选举 leader 对外提供服务

  • 支持数据 0 丢失。

   




点我一块儿多人运动


本文分享自微信公众号 - 码农沉思录(code-thinker)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索