【Java劝退师】RabbitMQ 知识脑图 - 消息队列

960 阅读8分钟

RabbitMQ

RabbitMQ

一、消息队列解决的问题

消息队列的调用通常都是异步的,只有在可以接受是异步调用(无法立刻得知处理结果)的情况下,再考虑采用消息队列来实现。

1. 系统解偶

实现系统与系统之间的松偶合,可以将主要流程与次要流程分离。

比如: 当用户在电商网站上购买商品,生成订单、扣减库存 为主要流程,发优惠券、增加用户积分 为次要流程,不需要立即处理完成,此时就可以将次要流程交给消息队列下游的服务去处理。

2. 流量削峰填谷

秒杀场景,将秒杀请求暂存于消息队列,业务系统响应 "秒杀结果正在处理中。。。",之后系统将持续消化消息队列中的消息,避免因流量洪峰的到来,导致系统崩溃。

但因为消息的发送与处理属于异步请求,所以用户将无法立即收到处理结果,这是在采用消息队列技术时必须要考量的缺点。

3. 实现最终一致性

使用消息队列调用其他的微服务,依靠消息队列的重试机制,保证一系列的服务调用可以正确地运行完成。

记得必须考虑幂等性问题,避免出现多次调用产生不同结果的情况。

4. 可持久化计时器

使用死信队列与TTL,当消息过期时,消息会放入死信队列,从死信队列中获取消息,达到计时器目的。

使用死信队列作为定时器来使用会存在一个问题,就是在正常队列中,只会检查头部的第一个消息是否过期,其他消息不检查,这就导致如果每个消息TTL有长有短,死信队列将会失去定时器的作用,此问题可以使用"延迟队列插件"来解决。

TTL : Time To Live 过期时间

设置方式 : (1) Queue 属性,队列中所有消息相同 (2) 消息自身

设置冲突时,以较小值为准

二、消息队列选型

1. RabbitMQ

优点:

  1. 支持AMQP协议 (支持绝大多数编程语言)
  2. 消息延迟在微秒级别

缺点:

  1. 消息堆积会导致性能大幅下降 (队列状态机制导致,当消息过多时,索引与消息本体都会被放到磁盘上)
  2. 性能差 (单机每秒吞吐量1W量级)
  3. 使用Erlang语言开发,二次开发代价高

2. RocketMQ

优点:

  1. Java开发,阅读源码、二次开发 方便
  2. 消息延迟在毫秒级别
  3. 性能、稳定性高 (单机吞吐量在10W量级)

缺点:

  1. 与周边系统集成、兼容不是很好

3. Kafka

优点:

  1. 可靠性、稳定性 高
  2. 与周边系统兼容性好(尤其是与 大数据、流式计算 相关)
  3. 可伸缩,支持分区、副本、容错

缺点:

  1. 延迟高 (异步、批处理),不适合电商场景

三、消息主体类型

  1. 简单文本 (TextMessage)
  2. 可串行化对象 (ObjectMessage)
  3. 属性集合 (MapMessage)
  4. 字节流 (BytesMessage)
  5. 原始值流 (StreamMessage)
  6. 无有效负载 (Message)

四、AMQP 概念

消息发送过程

Publisher 指定消息的 RoutingKey,并将消息发送到 Exchange 中,Exchange 会根据 Bindings 查找此 RoutingKey 应该路由到哪些(0~∞个) Queue 中,并将消息路由过去,Consumer 从 Queue 中获取消息,并对消息进行处理 。

角色

  1. Publisher: 消息发送者。将指定 RoutingKey 的消息发送到 Exchange 中,则 Queue 可以收到对应的消息。
  2. Server: MQ服务的实例。也称为 Broker。
  3. Virtual Host: 虚拟主机。一个 Server 下可以有多个虚拟主机,主要用途为隔离项目
  4. Exchange: 交换器。接收 Producer 发来的消息,并将消息路由到对应的 Queue 中。
  5. Routing Key: 路由键。指定消息的路由规则。Routing Key、Bindings、Exchange 三者须要互相配合使用。
  6. Bindings: 绑定。指定 Exchange 与 Queue 之间的绑定关系。
  7. Queue: 消息队列。存储 Message 的容器。
  8. Message: 消息。Publisher 想要告诉 Consumer 的消息。
  9. Consumer: 消息消费者。负责处理由 Publisher 发送过来的消息。

AMQP 在传输数据时使用 TCP/IP 流协议进行传输,而 TCP/IP 是没有办法界定数据侦(一个完整数据的范围),所以会在每个数据侦头部加上该数据侦的大小。

五、交换器类型

  1. Fanout: 广播,不需要考虑 RoutingKey 与 Bindings。将消息路由到所有与该交换器绑定的队列中。

    (发布/订阅模式: 在线消费者都能收到消息,如果消费者不在线,则无法接收消息)

  2. Direct: 将消息路由到 RoutingKey 与 BindingKey 完全匹配的队列中。

    针对单一队列,属于队列模式(进到该队列的一条消息只会由一个消费者获得)

    但如果有多个不同的队列,都绑定同一个 BindingKey ,则多个队列都会收到消息

  3. Topic: Direct类型的扩展,可以使用 " * "、"#" 关键字进行模糊匹配。" * " 匹配一个单词,"#" 匹配0或多个单词。

    (发布/订阅模式: 在线消费者都能收到消息,如果消费者不在线,则无法接收消息)

    RoutingKey 与 BindingKey 使用 "."分单词表示,EX: eroupe.user.news

六、消费者模式

  1. 推模式: 相当于监听器模式
  2. 拉模式: 消费者主动获取消息

七、存储机制

消息类型

  1. 持久化消息
  • 消息到达队列时直接在磁盘进行备份

  1. 非持久化消息
  • 当内存压力大时,将消息转存到磁盘上。MQ重启后丢失

消息文件

  1. 索引
  • 纪录 (1) 消息位置 (2) 消息是否已被消费者接收 (3) 消息是否已被消费者ACK。文件后坠 .idx

  • 当消息本体较小(默认4096B)时,消息本体会直接存储在索引中。

  1. 消息本体
  • 键值对型式存储,所有队列使用同一块存储空间。文件后坠 .rdq

消息删除

  • 删除消息时,只是从ETS表(Erlang Term Storage 负责记录消息在文档中的映射关系) 删除纪录,并不会对文档中的消息进行删除

  • 只有当垃圾数据的比例大于 grabage_fraction 时(默认0.5),才会触发垃圾回收,将两个消息本体的文件进行合并

队列状态

  1. alpha: 索引、消息 都存内存
  2. beta: 索引放内存,消息存磁盘
  3. gama: 索引、消息 都放磁盘

八、消息可靠性

保证发送者的消息有投递到"队列"中

  1. 异常捕获

    不是 100% 可靠

  2. Channel事务

    开销大,不推荐

  3. 发送端确认

    信道(Channel)设为confirm模式,同步阻塞。可以使用 批处理、异步回调 来提高效率。

保证消息被"消费者"成功消费

设置 Listener 的 ACK Mode

消费者消费消息后,需要发送 ACK 给Broker,否则将重发消息直到消息过期。

  1. NONE模式:

    消费者自行捕获异常,有丢失数据的风险。

  2. AUTO模式:

    抛出异常,则将消息放回Queue中,重新发送消息。

  3. MANUAL模式:

    消费者手动调用 Channel 方法返回ACK。

九、持久化

MQ重启后不丢失

  1. Exchange 持久化: durable参数 = true
  2. Queue 持久化: durable参数 = ture
  3. 消息持久化: deliveryMode = 2

十、MQ服务器安全

阻塞生产者

不让生产者推消息到MQ中

  1. 磁盘可用空间
  2. 磁盘内存比
  3. 可用内存

阻塞消费者

不继续推消息到消费者中 (对推模式有效,拉模式无效,不支持NONE-ACK模式)

Channel 设置 basicQoS,当未确认消息达到上限后,则不继续发送消息。

保证消息幂等性

RabbitMQ 仅支持 "最多一次""最少一次" 的消息可靠传输

必须保证消息多次运行的结果与只运行一次的结果相同

  1. 数据库唯一索引

    当向数据库插入相同的订单号,报错,事务回滚。

  2. 前置检查

    先查找数据库中是否有对应的纪录,没有则利用排他锁完成操作(避免并发问题)。

  3. 消息唯一ID

    对每条消息都生成唯一ID,消费前先判断在分布式缓存中是否存在。

接口幂等性

上游在调用时,传递一个GUID,如果发现已经处理过,则返回【和上次相同的处理结果】

十一、 提高性能

提升下游吞吐量

  1. 优化服务性能
  2. 增加消费者节点
  3. 增加并发消费线程数

分布式架构

具体集群搭建使用 HAProxy 作集群模式的负载均衡,结合镜像队列模式作高可用

  1. 主备模式

    • 只有一个节点在工作(备份节点不能读写),且需要一个共享存储 - 不推荐

  2. 铲子模式 (Shovel)

    • 利用插件实现跨机房复制

  3. 集群模式

    • 所有节点存储 (1) 队列 (2) 交换器 (3)绑定 (4) vhost 的元数据信息

    • 各节点存储各自的消息分片

  4. 镜像队列模式

    针对集群模式的高可用功能补充

    • 当节点失效,将自动切换到集群中的另一个镜像节点

    • 使用多副本冗于,保证消息不丢失

    • 读写都在Master,Slave仅用于替代Master