RocketMQ简介

41 阅读7分钟

目录

  1. 思维导图
  2. 主要内容
    • 架构
    • 文件系统
    • 一次完整的通信流程
    • 高可靠
    • 高可用
    • 高性能
  3. 相关面试题

1. 思维导图

RocketMQ.png

2. 主要内容

2.1 架构组成

  • 使用发布-订阅模型
  • NameServer: 主要负责对于Topic-Broker关联信息的管理
  • Producer: 消息生产者
  • Broker: 消息中转角色,负责存储消息,转发消息
  • Consumer: 消息消费者,后台系统负责异步消费
    • Pull 模式: 定时主动从消息服务器拉取信息,然后消费
    • Push 模式:
      1. Consumer 后台线程,会去Broker拉取待处理消息(PullRequest)
      2. 如果Broker有待处理消息的话,那么就间隔一定时间再次从Broker】拉取消息的逻辑
      3. 如果Broker没有待处理消息的话,就hold住请求(ConcurrentHashMap, key=topic+queueid, val=PullRequest数组),等有新的消息到的时候才返回(长轮询机制)
      4. 消费者获取到消息之后,回调MessageListener的实现去消费消息,然后重新执行1的逻辑
    • Push模式选择基于Pull模式封装实现的原因:
      • 如果要实现Broker主动推送的Push,需要Broker对Consumer做负载均衡,而Broker是分布式的,实现难度较大

2.2 文件系统

CommitLog : 混合型的存储结构,即为Broker单个实例下多个Topic的消息实体内容都存储于一个文件中。缺点在于,会存在较多的随机读操作,因此读的效率偏低。同时消费消息需要依赖ConsumeQueue,构建该逻辑消费队列需要一定开销。 ConsumeQueue: 1. commitlog的索引,提升commitlog的查询性能, 2. 索引文件定长设计,路径HOME/store/consumequeue/{topic}/{queueId}/{fileName},单文件存储30w条,类似数组随机访问 3. offsettable.offset 记录已消费的索引偏移量 Indexfile:提供了一种可以通过key或时间区间来查询消息的方法, hashmap结 Broker端的后台服务线程—ReputMessageService不停地异步构建ConsumeQueue(逻辑消费队列)和IndexFile(索引文件)数据。

2.3 一次完整的通信流程

  • 发送消息
    1. Broker启动时,向NameServer注册信息
    2. 客户端调用producer发送消息时,会先检查本地是否有topic的路由信息,没有则从NameServer获取
    3. 路由信息,包括topic包含的队列列表和broker列表
    4. Producer端根据查询策略,选出其中一个队列,用于后续存储消息
    5. Producer向Broker发送rpc请求,将消息保存到broker端
  • 消息刷盘 6. Broker端收到消息后,将消息原始信息保存在CommitLog文件对应的MappedFile中,然后异步刷新到磁盘 7. ReputMessageServie线程异步的将CommitLog中MappedFile中的消息保存到ConsumerQueue和IndexFile中(ConsumerQueue和IndexFile只是原始文件的索引信息)
  • 消费消息 8. Consumer实例启动上线,向Broker发送心跳,通知Broker订阅关系,重新负载均衡( 分配MessageQueue) 9. Consumer向Broker请求拉取消息(group, topic, queueId, offset) 10. Broker根据offset*20,映射consumequeue到mmap文件,找到commitlog的偏移量 11. Broker根据commitlog的偏移量,映射commitlog到mmap文件,找到消息并返回 12. Broker更新偏移量

2.4 高可靠设计

  • 发送消息
    1. 同步发送,关注SendResult#sendStatus=SEND_OK
    2. 异步发送,实现SendCallback接口关注onSuccess函数回调
  • 消息落盘
    1. 同步刷盘(SYNC_FLUSH): 消息写入成功后(到 page cache 后),会立刻触发操作系统的 fsync 操作,把消息刷到磁盘中,这时候表现上就是消息发送需要等到真正刷到磁盘才会返回。
    2. 异步落盘(ASYNC_FLUSH): 写完 page cache 就结束, 大概率会持久化成功,但如果这时 broker 发生重启,有机会造成消息丢失。何时才刷盘?一来是由操作系统决定,二来 RocketMQ 也支持在异步刷盘的情况下,隔一段时间就强行触发一次刷盘。
  • 主从复制
    1. 同步双写(SYNC_MASTER): 消息写入 master 之后,写入的线程会等待 slave 的数据同步。由于 slave 同步的过程中会上报自己的同步的最大进度(消息文件里面的物理offset),当发现至少有一个 slave 的进度达到了和 master 一致,这条写入的线程才会返回。这意味着消息至少在两个 broker 上存在了。当然了,这里的存在也只能保证写入到了 page cache。
    2. 异步复制(ASYNC_MASTER): 异步复制是指消息写入 master 之后,就算成功,slave 自己会不断地同步 master 数据。注意,slave 同步数据不是以消息为维度的,而是以 commitLog 文件同步的方式去顺序同步的,也就是说如果 slave 落后很多的话,slave 没有同步完成前面的消息是不会同步这个最新的消息的。
    3. 一般而言,会建议采取 SYNC_MASTER+ASYNC_FLUSH 的方式,在消息的可靠性和性能间有一个较好的平衡。如果要保证不丢消息,是不是至少需要SYNC_FLUSH+ASYNC_MASTER?
  • 消费消息
    1. Broker没有收到ack回调或者收到的是ConsumeConcurrentlyStatus.RECONSUME_LATER,一段时间后会重新投递

2.5 高可用设计

  1. Name server轻量化,无状态设计,支持水平扩展
  2. 服务启动后从Name server获取Broker信息到本地缓存,Name server短时间不可用影响不大
  3. Broker高可用:主从复制+集群
  4. 消费高可用:Broker不可用,consumer自动从master切换到slave
  5. 发送高可用:message queue可以创建在多个broker组上,broker组a的master不可用,组b的master依然可用

2.6 高性能设计

  1. 顺序写,不区分topic统一写一个文件
  2. Broker端的后台服务线程—ReputMessageService不停地异步构建ConsumeQueue(逻辑消费队列)和IndexFile(索引文件)数据。
  3. pagecache:ConsumeQueue 顺序读取情况下,系统pagecache命中率很高
  4. ConsumeQueue/Indexfile定长存储,mmap操作内存直接映射磁盘文件,减少一次内核态<->用户态的内存复制

2.7 严格顺序消息

  • 消息投递
    1. 生产者实现MessageQueueSelector接口,使用Hash取模法来保证同一个业务主键投递到同一个Broker上
    2. 消息投递时必须使用同步方法
    3. Broker挂了/扩容引起服务器数量变化,会导致消息短暂乱序的情况
  • 消息存储
    1. broker端的分布式锁: 保证同一个consumerGroup下同一个messageQueue只会被分配给一个consumerClient
    2. messageQueue的本地synchronized锁: 保证同一时刻对于同一个队列只有一个线程去消费它
    3. ProccessQueue的本地consumeLock: 防止在消费消息的过程中,该消息队列因为发生负载均衡而被分配给其他客户端,进而导致的两个客户端重复消费消息的行为
  • 消息消费
    1. 消费者注册MessageListenerOrderly类型的回调接口实现顺序消费
  • 顺序消费的问题
    1. 使用了很多的锁,降低了吞吐量
    2. 前一个消息消费阻塞时后面消息都会被阻塞。如果遇到消费失败的消息,会自动对当前消息进行重试(每次间隔时间为1秒),无法自动跳过

3. 相关面试题

  • 为什么要使用消息队列呢? 解耦 异步 削峰
  • RocketMQ的消息模型是什么?
  • RoctetMQ基本架构了解吗?
  • 如何保证消息的可用性
  • 如何保证消息可靠性/不丢失呢?
  • 如何处理消息重复的问题呢
  • 延迟消息原理
  • 主从复制原理
  • RocketMQ 如何保证一条消息at least once?
    1. 生产者拿到成功的回执才认为发送成功
    2. 消费者确认消费成功了才回调成功,需要注意幂等性判断和异常处理
  • 亿级消息堆积能力支持?
    1. 1条消息4M算,1亿消息commitlog需要近40G硬盘容量
    2. consumequeue每条20字节,单文件30w条,空间5.7M, 总容量1.8G
    3. indexfile每条40字节,总容量3.7G
    4. 日志总容量45G左右,正常服务器的硬盘负荷内,并且因为混合存储,各个topic的读取性能比较稳定