消息队列总结

消息队列

为什么使用消息队列

先说一下消息队列常见的使用场景吧,其实场景有很多,但是比较核心的有 3 个:解耦异步削峰

解耦

问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GiOf9HR2-1618106328448)(https://github.com/Gaotrees/advanced-java/raw/master/images/mq-1.png)]

一个系统A为多个系统B,C,D提供数据,如果某个系统挂掉,该怎么办?存消息还是重发?

解决措施

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ai5YCmld-1618106328449)(https://github.com/Gaotrees/advanced-java/raw/master/images/mq-2.png)]

使用MQ,A系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可。如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。

总结:通过一个 MQ,Pub/Sub 发布订阅消息这么一个模型,A 系统就跟其它系统彻底解耦了。

异步

问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DnWW5o5y-1618106328450)(https://github.com/Gaotrees/advanced-java/raw/master/images/mq-3.png)]

A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,这是请求将近1秒,很慢。

解决措施:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0tc31JWu-1618106328451)(https://github.com/Gaotrees/advanced-java/raw/master/images/mq-4.png)]

如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms,对于用户而言,其实感觉上就是点个按钮,8ms 以后就直接返回了。

削峰

问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IAAN4JIQ-1618106328452)(https://github.com/Gaotrees/advanced-java/raw/master/images/mq-5.png)]

每秒并发请求数量突然会暴增到 5k+ 条,是系统是直接基于 MySQL 的,大量的请求涌入 MySQL,直接把SQL打死了,系统崩溃。

解决措施

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3dL9zJjC-1618106328453)(https://github.com/Gaotrees/advanced-java/raw/master/images/mq-6.png)]

使用 MQ,每秒 5k 个请求写入 MQ,A 系统每秒钟最多处理 2k 个请求,因为 MySQL 每秒钟最多处理 2k 个。A 系统从 MQ 中慢慢拉取请求,每秒钟就拉取 2k 个请求,不要超过自己每秒能处理的最大请求数量就 ok

缺点

系统可用性降低

系统引入的外部依赖越多,越容易挂掉。MQ 一挂,整套系统崩溃。

系统复杂度提高

怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?

一致性问题

A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。

如何保证消息队列的高可用?

RabbitMQ 的高可用性

单机模式

单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿的😄,没人生产用单机模式。

普通集群模式(无高可用性)

没做到所谓的分布式,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个 queue 所在实例消费数据,前者有数据拉取的开销,后者导致单实例性能瓶颈

而且如果那个放 queue 的实例宕机了,会导致接下来其他实例就无法从那个实例拉取,如果你开启了消息持久化,让 RabbitMQ 落地存储消息的话,消息不一定会丢,得等这个实例恢复了,然后才可以继续从这个 queue 拉取数据。

镜像集群模式(高可用性)

在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像

如何保证消息不被重复消费?(消息队列如何保证幂等性)

  • 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。

  • 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。

  • 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。

如何保证消息的可靠性传输?

消息丢失的3种情况

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tKsmjVsa-1618106328453)(https://github.com/Gaotrees/advanced-java/raw/master/images/rabbitmq-message-lose.png)]

生产者弄丢了数据
问题

生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题,都有可能。

解决措施
事务机制:

开启事务,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重试发送消息;如果收到了消息,那么可以提交事务channel.txCommit

但是这样的话吞吐量下来了。

confirm 模式

可以开启 confirm 模式,在生产者那里设置开启 confirm 模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 ack 消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你的一个 nack 接口,告诉你这个消息接收失败,你可以重试。

RabbitMQ 弄丢了数据

必须开启 RabbitMQ 的持久化,就是消息写入之后会持久化到磁盘。

怕是 RabbitMQ 自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。

设置持久化有两个步骤

  • 创建 queue 的时候将其设置为持久化
    这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。
  • 第二个是发送消息的时候将消息的 deliveryMode 设置为 2
    就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。

必须要同时设置这两个持久化才行

消费端弄丢了数据
问题:

主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,RabbitMQ 认为你都消费了,这数据就丢了。

解决措施

这个时候得用 RabbitMQ 提供的 ack 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 ack,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 ack 一把。

总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zbqJQ4Nb-1618106328454)(https://github.com/Gaotrees/advanced-java/raw/master/images/rabbitmq-message-lose-solution.png)]

如何保证消息的顺序性?

问题

应出来了增删改 3 条 binlog 日志,接着这三条 binlog 发送到 MQ 里面,再消费出来依次执行不然本来是:增加、修改、删除;你愣是换了顺序给执行成删除、修改、增加。

RabbitMQ:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BqCH8yNv-1618106328454)(https://github.com/Gaotrees/advanced-java/raw/master/images/rabbitmq-order-01.png)]

解决方案

拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SiKqA5YQ-1618106328454)(https://github.com/Gaotrees/advanced-java/raw/master/images/rabbitmq-order-02.png)]

如何解决消息队列的延时以及过期失效问题?

大量消息在 mq 里积压了几个小时了还没解决?

临时紧急扩容
  • 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。

  • 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。

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

  • 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。

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

mq 中的消息过期失效了

RabbtiMQ 是可以设置过期时间的,如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。

手动写程序,将丢失的数据一点点查出来,重新灌入MQ。

mq 都快写满了

临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,然后走第二个方案。

让你写一个消息队列,该如何进行架构设计?

mq 得支持可伸缩性,需要的时候快速扩容,就可以增加吞吐量和容量。设计分布式系统,增加机器,不就可以存放更多数据,提供更高的吞吐量了。

mq 的数据落地磁盘,落磁盘才能保证别进程挂了数据就丢了,做法:顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值