这里总结一些 MQ(Message Queue, 消息队列)的相关知识.
消息队列的优点
解耦
在传统模式下, 系统之间的耦合性太强, 比如系统 A 在代码中直接调用系统 B 和系统 C 的代码, 如果将来 D 系统接入, 系统 A 还需要修改代码.
如果将消息写入消息队列, 需要消息的系统自己从消息队列中订阅, 在 D 系统接入的时候系统 A 也不需要做任何修改, 达到了解耦的效果.
异步
在传统模式下, 一些非必要的业务逻辑以同步的方式运行, 需要等待上一个业务逻辑执行完毕才能开始执行下一个业务逻辑, 耗费等待的时间.
如果将消息写入消息队列, 非必要的业务逻辑就可以以异步的方式运行, 加快了服务响应的速度.
削峰
在传统模式下, 当并发量大的时候, 所有的请求都会直接怼到数据库, 造成数据库连接异常, 甚至宕机.
如果将消息写入消息队列, 则系统 A 可以慢慢地按照数据库能处理的并发量, 从消息队列中慢慢拉取消息. 在生产中, 这个短暂的高峰期积压是允许的.
消息队列的缺点
我们引入一个技术, 要对这个技术的弊端有充分的认识, 才能做好预防. 一个使用了 MQ 的项目, 如果连 MQ 的缺点都没有考虑过, 就把 MQ 引进去了, 那就会给自己的项目带来风险.
系统可用性降低
你想啊, 本来其他系统只要运行好好的, 那你的系统就是正常的. 现在你非要加个消息队列进去, 那消息队列挂了, 系统也就挂了. 用专业的术语来解释, 就是系统的可用性降低了.
系统复杂性增加
加了 MQ 之后要多考虑很多方面的问题, 比如数据的一致性问题, 如何避免消息被重复消费, 如何保证消息可靠传输等. 因此, 需要考虑的东西更多, 系统复杂性也就随着增加了.
消息队列的选型
既然在项目中用了 MQ, 肯定事先要对业界流行的 MQ 进行调研, 如果连每种 MQ 的优缺点都没了解清楚, 随便选用了某种 MQ, 就容易给项目挖坑. 另外, 如果面试官问为什么项目上选用这种 MQ 的时候, 你直接回答是领导决定的, 这种回答就很 LOW 了. 所以了解一下各种 MQ 的优缺点和使用的场景还是有必要的.
这里只简单说一下 ActiveMQ,RabbitMQ,RocketMQ 和 Kafka 四种 MQ 框架.
更新频率
要了解各个 MQ 方案的更新频率, 可以上各个 MQ 的官方社区去看.
简单了解一下就是, ActiveMQ 几个月才发一次版本, 据说研究重心在他们的下一代产品 Apollo;RabbitMQ 的版本发布比 ActiveMQ 频繁很多; RocketMQ 和 Kafka 也比 ActiveMQ 活跃的多.
性能对比
用一个表格简单比较下各个 MQ 的性能.
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
开发语言 | java | erlang | java | scala |
单机吞吐量 | 万级 | 万级 | 10 万级 | 10 万级 |
时效性 | ms 级 | us 级 | ms 级 | ms 级以内 |
可用性 | 高 (主从架构) | 高 (主从架构) | 非常高 (分布式架构) | 非常高 (分布式架构) |
功能特性 | 成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好 | 基于 erlang 开发,所以并发能力很强,性能极其好,延时很低; 管理界面较丰富 | MQ 功能比较完备,扩展性佳 | 只支持主要的 MQ 功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。 |
MQ 选型的简单总结
1. 中小型软件公司, 建议选 RabbitMQ. 一方面, erlang 语言天生具备高并发的特性, 而且他的管理界面用起来十分方便. 但是虽然 RabbitMQ 是开源的, 国内能定制化开发 erlang 的程序员却很少. 所幸的是 RabbitMQ 的社区十分活跃, 通过社区可以解决开发过程中遇到的大部分 Bug, 这点对于中小型公司来说十分重要. 不考虑 RocketMQ 和 Kafka 的原因是, 一方面中小型软件公司不如互联网公司, 数据量没那么大, 选消息中间件, 应首选功能比较完备的, 所以 Kafka 排除. 不考虑 RocketMQ 的原因是, RocketMQ 是阿里出品, 一旦哪一天阿里放弃维护 RocketMQ, 中小型公司一般是抽不出人来进行 RocketMQ 的定制化开发的, 因此不推荐.
2. 大型软件公司, 根据具体使用在 RocketMQ 和 Kafka 之间二选一. 一方面, 大型软件公司, 具备足够的资金搭建分布式环境, 也具备足够大的数据量. 针对 RocketMQ, 大型软件公司也可以抽出人手对 RocketMQ 进行定制化开发, 毕竟国内有能力改 Java 源码的人, 还是相当多的. 至于 Kafka, 根据业务场景选择, 如果有日志采集功能, 肯定是首选 Kafka 了. 具体该选哪个, 还是要看具体的使用场景.
保证消息队列的高可用
前面说过了, 引入消息队列后, 系统的可用性会降低. 在生产中, 没人会使用单机模式的消息队列, 因此了解消息队列的高可用是很必要的. 实际上, 要保证消息队列的高可用, 需要对消息队列的集群模式有深刻了解.
RocketMQ 保证消息队列的高可用
以 RocketMQ 为例, 他的集群就有多 master 模式, 多 master 多 slave 异步复制模式和多 master 多 slave 同步双写模式.
下面是一张 RocketMQ 多 master 多 slave 模式部署的架构图.
这样的实现方式其实和 Kafka 很像, 只是 Name Server 集群在 Kafka 中是用 Zookeeper 代替, 都是用来保存和发现 Master 和 Slave 用的. 通信过程如下: Producer 与 Name Server 集群中的其中一个节点 (随机选择) 建立长连接, 定期从 Name Server 集群获取 Topic 路由信息, 并向提供 Topic 服务的 Broker Master 建立长连接, 且定时向 Broker 发送消息. Producer 只能将消息发送到 Broker Master, 但是 Consumer 则不一样, 它同时和提供 Topic 服务的 Master 和 Slave 建立长连接, 既可以从 Broker Master 订阅消息, 也可以从 Broker Slave 订阅消息.
Kafka 保证消息队列的高可用
直接上 Kafka 的拓补架构图来看 Kafka 的集群模式.
如上图所示, 一个典型的 Kafka 集群中包含若干 Producer(可以是 web 前端产生的 Page View, 或者是服务器日志, 系统 CPU,Memory 等), 若干 Broker(Kafka 支持水平扩展, 一般来说, Broker 的数量越多, 集群的吞吐率就越高), 若干 Consumer Group, 以及一个 Zookeeper 集群. Kafka 通过 Zookeeper 管理集群配置, 选举 Leader, 以及在 Consumer Group 发生变化时进行 rebalance.Producer 使用 push 模式将消息发布到 Broker,Consumer 使用 pull 模式从 broker 订阅并消费消息.
RabbitMQ 保证消息队列的高可用
RabbitMQ 也有普通集群和镜像集群模式, 比较简单.
保证消息队列高可用性的简单总结
要保证消息队列的高可用, 需要从集群上入手, 具体就是当消息队列中的一个节点炸了, 其他的节点还能继续运行, 保证消息队列整体能正常运行.
另外, 如果有相关的面试题, 需要了解 MQ 集群架构并能叙述清楚架构中的逻辑关系.
保证消息不被重复消费
保证消息不被重复消费, 也就是保证消息队列的幂等性. 这个问题可以认为是消息队列领域的基本问题.
消息被重复消费的原因
无论是哪一种消息队列, 造成消息被重复消费的原因都是类似的. 在正常的情况下, 消费者在消费消息完毕后, 会发送一个确认信息给消息队列, 消息队列就知道该消息被消费了, 就会将该消息从消息队列中删除. 只是不同的消息队列发送的确认信息形式不同, 例如 RabbitMQ 是发送一个 ACK 确认消息, RocketMQ 是返回一个 CONSUME_SUCCESS 成功标志, Kafka 则实际上是有个 offset 的概念(每一个消息都有一个 offset,Kafka 消费过消息后, 需要提交 offset, 让消息队列知道自己已经消费过了). 而造成重复消费的原因, 就是因为网络传输等等故障, 确认信息没有传送到消息队列, 导致消息队列不知道自己已经消费过该消息了, 再次将该消息分发给其他的消费者.
消息被重复消费的解决方法
解决方法有很多, 其实就是单机服务里幂等性的问题, 这里简单列举几个方法.
1. 唯一索引. 如果拿到的消息是做数据库的 insert 操作, 可以给这个消息做一个唯一主键, 那么就算出现重复消费的情况, 也会因为主键冲突而取消操作, 有效避免数据库出现脏数据.
2. 原生幂等. 如果拿到的消息是做 Redis 的 set 的操作, 因为在 Redis 中 set 操作本来就算幂等操作, 即无论 set 几次结果都是一样的, 也就不怕消息被重复消费的问题了.
3. 全局状态机. 准备一个第三方介质, 来做一个消息消费的记录. 以 Redis 为例, 给消息分配一个全局 id, 只要消费过该消息, 将 < id,message > 以 K-V 的形式写入 Redis. 在消费者开始消费前, 先去 Redis 中查询有没有消费记录即可.
保证消息消费的可靠性传输
在使用消息队列的过程中, 应该做到消息不能多消费, 也不能少消费. 如果无法做到可靠性传输, 可能给公司带来千万级别的财产损失.
而要保证消息消费的可靠性传输, 每种 MQ 都要从三个角度来分析, 分别是生产者弄丢数据, 消息队列弄丢数据和消费者弄丢数据.
这个内容太复杂了, 暂时玩不转, 等以后再补充, 嘿嘿.
保证消息的顺序性
要保证消息的顺序性, 可以通过某种算法, 将需要保持先后顺序的消息放到同一个消息队列中(Kafka 中就是 Partition,RabbitMQ 中就是 Queue), 然后只用一个消费者去消费该队列即可.
另外, 如果为了吞吐量, 有多个消费者去消费, 这时候要保证消息的顺序性, 其实只要一个简单的重试就好了. 比如微博, 可以分为发微博, 写评论和删除微博这三个异步操作. 如果这时候有一个消费者先执行了写评论的操作, 但是这时候, 微博都还没发, 写评论一定是失败的, 就可以等一段时间再重试. 等另一个消费者, 先执行发微博的操作后, 再执行写评论的操作, 就可以成功.
实际上, 要保证消息的顺序性, 大多数情况下只需要保证入队有序就行, 出队以后的顺序交给消费者自己去保证, 没有固定的套路.
"慢慢大家会明白的, 无法跟喜欢的人在一起, 其实是人生的常态."
来源: https://www.cnblogs.com/yanggb/p/11722407.html