1. DefaultMQPushConsumer属性
public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsumer {
private final InternalLogger log = ClientLogger.getLog();
// 消费者实现对象
protected final transient DefaultMQPushConsumerImpl defaultMQPushConsumerImpl;
// 消费者组名
private String consumerGroup;
// 消费模式:默认集群模式,还支持 广播模式
private MessageModel messageModel = MessageModel.CLUSTERING;
// 当从broker获取当前组内 该 queue 的offset 不存在时,consumeFromWhere 才有效
// 默认:CONSUME_FROM_LAST_OFFSET 表示从队列的最后offset开始消费,当队列内再有一条新的msg加入进去时,消费者才会去消息
private ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET;
// 当ConsumeFromWhere配置的是CONSUME_FROM_TIMESTAMP 的时候,并且服务器中不存在 group 内该queue 的offset 时,会使用该时间戳
// 值:消费者创建时间 - 30秒 转换成 格式: 年月日小时分钟秒 (20131223171201)
private String consumeTimestamp = UtilAll.timeMillisToHumanString3(System.currentTimeMillis() - (1000 * 60 * 30));
// 主题下的队列分配策略,rbl对象依赖该算法。
private AllocateMessageQueueStrategy allocateMessageQueueStrategy;
// 订阅信息集合,key :主题 val:过滤表达式 (一般都是tag)
private Map<String /* topic */, String /* sub expression */> subscription = new HashMap<String, String>();
// 消息监听器,消息处理逻辑 全部由它提供(具体怎么消费)..
// 两种接口:1. MessageListenerConcurrently 2. MessageListenerOrderly
private MessageListener messageListener;
// 消费者本地消费进度存储(一般这里都是 RemoteBrokerOffsetStore)
private OffsetStore offsetStore;
// 消费服务线程池 线程数最小值,默认:20
private int consumeThreadMin = 20;
// 消费服务线程池 线程数最大值,默认:20
private int consumeThreadMax = 20;
// 该参数已经作废了..
private long adjustThreadPoolNumsThreshold = 100000;
// 本地队列快照(processQueue)中,第一条消息 和 最后一条消息 它俩之间的offset跨度 不能超过 2000 (队列流控使用)
private int consumeConcurrentlyMaxSpan = 2000;
// 本地队列快照(processQueue)中,消息数量限制 (队列流控使用)
private int pullThresholdForQueue = 1000;
// 本地队列快照(processQueue)中,消息总size 不能超过 “100”mib (队列流控使用)
private int pullThresholdSizeForQueue = 100;
// 主题流控,消费者消费的指定主题的所有消息 在本地 不能超过 “pullThresholdForTopic” 值。 -1 表示不限制
private int pullThresholdForTopic = -1;
// 主题流控,消费者消费的指定主题的所有消息 在本地 不能超过 “pullThresholdSizeForTopic”mib大小。 -1 表示不限制
private int pullThresholdSizeForTopic = -1;
// 消费者 两次拉取请求时间间隔
private long pullInterval = 0;
// 消费任务最多可消费的消息数量(默认是:1,每一个消费任务 只消费一条消息)
private int consumeMessageBatchMaxSize = 1;
// 一次拉请求,最多可以从服务器拉取的消息数量
private int pullBatchSize = 32;
// 拉请求时,是否提交本地“订阅数据(过滤信息)”,默认不提交(心跳时,订阅数据已经同步到broker,没必要再提交了)
private boolean postSubscriptionWhenPull = false;
private boolean unitMode = false;
// 消息最大重试次数, (-1)
private int maxReconsumeTimes = -1;
private long suspendCurrentQueueTimeMillis = 1000;
// 消费者本地如果某条消息 “15” min 还未被消费,则需要回退该消息
private long consumeTimeout = 15;
2. DefaultMQPushConsumerImpl属性
public class DefaultMQPushConsumerImpl implements MQConsumerInner {
private final InternalLogger log = ClientLogger.getLog();
// 门面对象(config)
private final DefaultMQPushConsumer defaultMQPushConsumer;
// rbl对象,职责:分配订阅主题的队列给当前消费者,20秒钟一个周期执行rbl算法(客户端实例触发)
private final RebalanceImpl rebalanceImpl = new RebalancePushImpl(this);
// 过滤消息hook
private final ArrayList<FilterMessageHook> filterMessageHookList = new ArrayList<FilterMessageHook>();
// 消费者启动时间
private final long consumerStartTimestamp = System.currentTimeMillis();
// 消息执行hook,在消息处理前 和 处理后 分别执行 hook.before hook.after 系列方法 ,留给用户扩展的接口。
private final ArrayList<ConsumeMessageHook> consumeMessageHookList = new ArrayList<ConsumeMessageHook>();
// ...
private final RPCHook rpcHook;
// 消费者状态
private volatile ServiceState serviceState = ServiceState.CREATE_JUST;
// 客户端实例(整个进程内只有一个客户端实例对象)
private MQClientInstance mQClientFactory;
// 拉消息API封装(这里为什么要封装? 内部有推荐下次pull时 主机的算法,服务器broker 返回结果中,包装着 下次 pull 推荐的brokerId,
// 根据本次请求数据 冷热 来进行推荐。)
private PullAPIWrapper pullAPIWrapper;
// 是否暂停
private volatile boolean pause = false;
// 是否顺序消费
private boolean consumeOrderly = false;
// 消息监听器
private MessageListener messageListenerInner;
// 消息进度存储器
private OffsetStore offsetStore;
// 消费消息服务
// 1. ConsumeMessageConcurrentlyService
// 2. ConsumeMessageOrderlyService
private ConsumeMessageService consumeMessageService;
// 队列流控次数(打印日志使用..默认每1000次流控,进行一次日志打印)
private long queueFlowControlTimes = 0;
// 流控使用 (控制打印日志..)
private long queueMaxSpanFlowControlTimes = 0;
3. DefaultMQPushConsumerImpl start()
public synchronized void start() throws MQClientException {
switch (this.serviceState) {
case CREATE_JUST:
log.info("the consumer [{}] start beginning. messageModel={}, isUnitMode={}", this.defaultMQPushConsumer.getConsumerGroup(),
this.defaultMQPushConsumer.getMessageModel(), this.defaultMQPushConsumer.isUnitMode());
// 首先将启动状态设置为“失败”,启动成功之后,立马就改为“启动成功”
this.serviceState = ServiceState.START_FAILED;
// 检查配置(很多细节,自己去看)
this.checkConfig();
// 拷贝“订阅信息”到“rbl对象”
this.copySubscription();
if (this.defaultMQPushConsumer.getMessageModel() == MessageModel.CLUSTERING) {
// 修改消费者 实例名称:pid
this.defaultMQPushConsumer.changeInstanceNameToPID();
}
// 获取客户端实例对象(进程内只有一个该实例)
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPushConsumer, this.rpcHook);
// 初始化 rbl 对象
this.rebalanceImpl.setConsumerGroup(this.defaultMQPushConsumer.getConsumerGroup());
this.rebalanceImpl.setMessageModel(this.defaultMQPushConsumer.getMessageModel());
// 将队列分配策略对象,赋值给rbl
this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPushConsumer.getAllocateMessageQueueStrategy());
this.rebalanceImpl.setmQClientFactory(this.mQClientFactory);
// 创建 拉消息API 对象(内部封装了查询推荐主机算法)
this.pullAPIWrapper = new PullAPIWrapper(
mQClientFactory,
this.defaultMQPushConsumer.getConsumerGroup(), isUnitMode());
// 将 “过滤hook列表”注册到 pullApi 内,消息拉取下来之后,会执行该hook,再进行一次自定义的过滤
this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList);
if (this.defaultMQPushConsumer.getOffsetStore() != null) {
this.offsetStore = this.defaultMQPushConsumer.getOffsetStore();
} else {
// 一般走这里
switch (this.defaultMQPushConsumer.getMessageModel()) {
case BROADCASTING:
this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
break;
case CLUSTERING:// 只考虑“集群模式”
// 创建 offsetStore 实例
this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
break;
default:
break;
}
// 将创建的 store 对象 回写 到 门面对象
this.defaultMQPushConsumer.setOffsetStore(this.offsetStore);
}
// 集群模式:load方法 什么也不干..
this.offsetStore.load();
if (this.getMessageListenerInner() instanceof MessageListenerOrderly) {
// 是否顺序消费 属性 设置为 true ,表示是顺序消费
this.consumeOrderly = true;
// 顺序消费的话,创建的消费服务,是顺序消费服务(ConsumeMessageOrderlyService)
this.consumeMessageService =
new ConsumeMessageOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner());
} else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) {
// 是否顺序消费 属性 设置为 false,表示当前是 并发消费
this.consumeOrderly = false;
// 并发消费的话,创建的消费服务,是并发消费服务(ConsumeMessageConcurrentlyService)
this.consumeMessageService =
new ConsumeMessageConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner());
}
// 启动消费服务(回头讲..)
this.consumeMessageService.start();
// 将消费者注册到客户端实例内
// 为什么注册?客户端实例给消费者提供了什么服务?
// 1.心跳服务(把订阅数据同步到关注主题的broker)
// 2.拉消息服务(内部PullMessageService 启动线程,基于 PullRequestQueue 工作,消费者负载均衡分配到队列后 会向该队列提交 pullRequest,后面细聊)
// 3.队列负载服务(每20秒,调用一次consumer.doRebalance() 接口)
// 4.消息进度持久化
// 5.动态调整消费者 消费服务线程池(ps:看源码得知,这块逻辑被注释掉了...)
boolean registerOK = mQClientFactory.registerConsumer(this.defaultMQPushConsumer.getConsumerGroup(), this);
if (!registerOK) {
this.serviceState = ServiceState.CREATE_JUST;
this.consumeMessageService.shutdown(defaultMQPushConsumer.getAwaitTerminationMillisWhenShutdown());
throw new MQClientException("The consumer group[" + this.defaultMQPushConsumer.getConsumerGroup()
+ "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),
null);
}
// 启动客户端实例
mQClientFactory.start();
log.info("the consumer [{}] start OK.", this.defaultMQPushConsumer.getConsumerGroup());
// 消费者状态设置为:RUNNING 运行中
this.serviceState = ServiceState.RUNNING;
break;
case RUNNING:
case START_FAILED:
case SHUTDOWN_ALREADY:
throw new MQClientException("The PushConsumer service state not OK, maybe started once, "
+ this.serviceState
+ FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
null);
default:
break;
}
// 从nameserver 强制加载 主题路由数据,生成 主题的 set<mq> 交给 rbl 的 table。
this.updateTopicSubscribeInfoWhenSubscriptionChanged();
// 检查服务器是否支持“消息过滤模式”(一般咱们都是 tag 过滤,服务器默认支持),如果不支持,这里会抛异常
this.mQClientFactory.checkClientInBroker();
// 向所有已知的broker节点,发送心跳
this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
// 唤醒rbl 线程,让rbl线程 立马去做 触发负载均衡的事情。
this.mQClientFactory.rebalanceImmediately();
}
具体做了几件事:
4. 负载均衡的原理
入口是 MQClientInstance里面的rebalanceService -> 查询注册到MQClientInstance所有的消费者 ->消费者的doReblance方法
-
获取当前消费者 想要消费的主题, 这个SubscriptionInner就是消费者在订阅数据时候的配置
-
从topicSubscribeInfoTable(这里具体在哪里生成? MqInstance有个定时任务去拉取主题路由数据,并同时生成主题发布数据,同时也会生成主题订阅数据,也就是topicSubscribeInfoTable)找到该主题对应的Set
-
获取当前消费者下的所有消费者id,然后对消费者id排序,保证每个消费者的视图是一样的
-
调用分配策略,最终给当前消费者 分配一个 set
-
如果负载均衡之后发现分给消费者的messageQueue变更了,如果变少了,从processQueueTable中将这条 <messagequeue, processQueue>删除,如果队列数目变多了,新建一个<messageQueue,processQueue>, processQueue作为 messageQueue的快照数据, 暂存从 messageQueue中拿取的数据
-
准备为拉消息服务做嫁衣:将 messageQueue和processQueue封装到 PullRequest,并且放到阻塞队列中,PullMessageServie基于这个阻塞队列从 messageQueue拉取数据
5.拉消息的逻辑
- 入口是MQClientInstance里面的PullMessageService,它也是继承了 serviceThread的一个后台线程,PullMessageService从阻塞队列中take -> DefaultMQPushConsumerImpl.pullMessage(重点就是看这个流程) -> pullAPIWrapper.pullKernelImpl -> MQClientAPIImpl.pullMessage 网络层调用获取message数据
- 设置 客户端回调PullCallback:
- 构建 sys Flag:拉消息的时候,是否提交本地offset、是否允许服务器长轮训、是否使用类过滤
- pullAPIWrapper.pullKernelImpl :使用网络层发起网络请求,从存储层拉取数据
- MQClientAPIImpl.pullMessage:发起网络请求,从存储层拉取数据, 当存储层返回数据之后,交给 上面的pullCallback.onSucess方法
- pullCallback.onSuceess:调用pullAPIWrapper.processPullResult,
再然后就是根据拉取的不同状态:
1. 如果是拉取到数据了,更新下一个拉取offset、将拉取到的数据放到mq的processQueue、将拉取下来的messageQueue、processQueue 提交给_ConsumeMessageService,内部会封装成消费任务,提交给消费线程池去处理_
2. 如果是拉取到,但是被过滤掉了
6. 并发消费的逻辑
入口是 pullCallback的处理逻辑:
● 后续的处理逻辑:
a. 根据配置的最大批量消费消息的数量,将拉取到的消息分割,比如拉取到的数据为32,最大提交任务的消息数目为1,那就切成32个1的ConsumeRequest�任务,提交给消费任务线程池
b. 消费者线程池调度到ConsumeRequest任务,也就是说走到它的run方法里面去 1、获取到外部传入的MessageListenerConcurrently 2、创建消费上下文对象 ConsumeConcurrentlyContext 3、将 上下文对象和 message消息 传入到listener,listener里面处理完成后,肯定会返回 success、fail、later等,并且调用它的consumeMessage方法(这就是我们自己控制的地方了) 4、processConsumeResult�:会拿到上面的success、fail、later等状态进行处理