Java-第十九部分-消息队列-RocketMQ策略、面试题和部分启动源码

126 阅读5分钟

消息队列全文

策略

规避策略

  • 选择队列策略
  1. Topic可以创建在多个节点的Broker上
  • 发送失败,进行重试,默认是2
  • 如果重试还失败,进行规避策略

选择另一个broker进行尝试发送

故障延迟机制策略

  • 记录Broker发送时长,算出规避时长,在这个时间之前都会标记这个broker为故障
  • 指定的broker发送失败,不会发送给故障节点,轮询选择其他节点发送

MQFaultStrategy

  • 定义了故障延迟机制的规避时间
  • 经验值
// 发送时长达到了 latencyMax[i]ms 规避 notAvailableDuratio[i]ms
private long[] latencyMax = new long[]{50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L};
private long[] notAvailableDuration = new long[]{0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L};
  • 计算发送
private long computeNotAvailableDuration(long currentLatency) {
    for(int i = this.latencyMax.length - 1; i >= 0; --i) {
        if (currentLatency >= this.latencyMax[i]) {
            return this.notAvailableDuration[i];
        }
    }

    return 0L;
}

负载均衡

  • 生产者,轮询发送给各个broker的队列
  • 消费者
  1. 默认,依次均分
  2. ByCircle,每个消费者轮一下

面试题

为什么使用消息队列

  • 结合业务场景
  • 解耦
  1. A系统发送给BCD三个系统,如果新增一个E系统,改动比较大
  2. 模块和模块之间是否可以不同步调用,通过消息队列实现异步解耦
  • 异步,延迟减少
  • 削峰,消费者固定消费消息,不会被流量突增影响

消息队列缺点

  • 系统的外部依赖增多,系统可用性降低
  • 系统复杂性提高,要保证消息不丢失
  • 一致性问题,A系统处理完之后直接返回,但是有某个子系统消费失败,导致数据不一致

为什么选择RocketMQ

  • 性能,阿里技术支撑,性能高;可靠性好;可用性高,易扩展
  • 功能,功能完善
  • 易用,跨平台,跨语言,多协议介入

源码

  • 结构分层 image.png
  • 消费者和生产者本质上都是Netty的客户端,本质上都是MQClientInstance
  1. 消费者和生产者通过NRC请求功能号,向broker进行请求
  2. broker启动时会注册功能号 image.png

Broker启动流程

  • BrokerStartup启动类 image.png
  • 整体流程 image.png

流程

  • 启动netty的客户端和服务端
  1. 客户端对应生产者和消费者
  2. 服务端对应broker
final NettyServerConfig nettyServerConfig = new NettyServerConfig();
final NettyClientConfig nettyClientConfig = new NettyClientConfig();
  • 初始化
  1. 加载启动时broker/store/config文件夹下的各类主题配置
  2. 消费进度,订阅信息
  3. 加载消息存储文件this.messageStore,初始化commitLog/cosumequeue文件,通过mmap进行内存映射,文件存储在写时复制队列中
  4. 启动NettyRemotingServer,也就是broker的服务
  5. this.registerProcessor()注册功能处理器,处理客户端的请求,以功能号的形式注册,每个功能的都有唯一的功能号
  6. 开启schedule定时任务this.scheduledExecutorService.scheduleAtFixedRate
controller.initialize();
  • 添加钩子方法Runtime.getRuntime().addShutdownHook,关闭时释放资源
  • 启动controller.start();,启动各类组件
  1. 消息存储/远程服务/过滤监控/对外通信
  2. 每隔30秒向nameServer发送心跳包

生产者启动流程

image.png

流程

  • DefaultMQProducer.start()实际上调用this.defaultMQProducerImpl.start();
  • 检查当生产者的状态,第一次进都是刚启动CREATE_JUST
  • 检查配置this.checkConfig();,其实就是校验分组
  • 创建一个MQ的客户端实例,并通过clientId进行复用,注册到生产者缓存
  1. 从broker的角度出发,消费者和生产者都是一个客户端
  2. clientId,由本地IP+实例名称(一般是不变的)+unitName(默认是null)
  3. 一个服务只能有一个组的一个生产者
  • 真正启动生产者mQClientFactory.start();
  1. 启动NRC,Netty Remote Client
  2. 启动定时任务,获取路由地址/获取路由信息/进行主题更新/与broker进行心跳
  3. 启动服务,拉取消息的服务/负载均衡服务
  • 启动完成后,立即向broker发心跳

并发消费流程

image.png

  • 通过start中的定时任务this.startScheduledTask();,获取路由地址和路由信息
  • 进行负载均衡,通过this.rebalanceService.start();启动的守护线程,调用this.mqClientFactory.doRebalance();获取组的消费者列表
  1. this.rebalanceByTopic(topic, isOrder);根据广播模式和集群模式做不同的处理
  2. 集群模式,用主题和组名,通过brokernameserver获取所有该主题、该组的所有消费者信息
  • 获取分配策略AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;,并调用allocate根据策略,为消费者绑定队列
  1. 底层通过for循环,根据策略,获取指定的队列,并返回
  2. AllocateMessageQueueAveragely均分
  3. AllocateMessageQueueAveragelyByCircle取模的方式,轮流分
  • 获取对应Queue的消费偏移量this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);
  1. 遍历每一个mq,进行`this.computePullFromWhere(mq
  2. 如果是广播消费模式,通过本地信息计算;如果是集群,通过远端信息计算
  3. 通过NRC功能号进行获取QUERY_CONSUMER_OFFSET
  • 通过start中的线程this.pullMessageService.start();,调用pullMessage,最终调用this.pullAPIWrapper.pullKernelImpl拉取Queue消息
  1. this.mQClientFactory.getMQClientAPIImpl().pullMessage调用NRC的拉取方法,封装了请求头,包含topic、偏移量、数量等信息;根据不同的模式(同步、异步、oneway)去获取,默认是异步获取
  2. 请求PULL_MESSAGE功能号
  3. 推模式也是基于拉模式实现的
  • 更新Queue消息消费的Offset,启动定时任务来更新偏移量,start中的this.startScheduledTask();,会开启持久化消费进度的定时任务MQClientInstance.this.persistAllConsumerOffset();,每隔五秒进行
  1. persistConsumerOffset()根据广播还是集群选择持久化方式
  2. 集群,通过远程更新,调用this.updateConsumeOffsetToBroker(mq, offset.get());
  3. 最终也就是通过UPDATE_CONSUMER_OFFSET实现更新偏移量
  • shutDown,最终也就是通过NRC调用UNREGISTER_CLIENT功能号,并关闭其他组件守护线程