RocketMQ源码(一):NameServer的启动
RocketMQ源码(二):broker的启动(一)
RocketMQ源码(三):broker的启动(二)
RocketMQ源码(四):producer的启动
RocketMQ源码(五):producer发送消息
RocketMQ源码(六):broker接收消息
RocketMQ源码(八):consumer消息拉取(一)
RocketMQ源码(九):consumer消息拉取(二)
consumer一共有三个实现类
- DefaultMQPullConsumer(deprecated)
- DefaultLitePullConsumer
- DefaultMQPushConsumer
前两种是通过pull的方式去拉取消息,第三种是push的方式。但是,其本质都是通过pull的方式拉取消息,也就是客户端去broker获取消息。
因为push的模式下虽然消息的实时性比较好,但是如果要考虑到客户端的消费速度慢于broker的投递速度导致消息在内存中积压,就会使程序变得很复杂。因此一般情况下都是使用pull模式来消费数据。
DefaultMQPullConsumer在源码中已经被标注为Deprecated,根据注释看到DefaultMQPullConsumer将在2022年移除,因此这里主要是分析DefaultLitePullConsumer的工作原理。
DefaultLitePullConsumer相比DefaultMQPullConsumer整体上要简便一点,但是缺少了消息重试机制,只能通过seek()方法重置consumerOffset来实现消息的重复消费,而DefaultMQPullConsumer有sendMessageBack。litePull相比push模式以及老的pull方法比较大的一个区别是一个消费者默认可以用20个线程去拉取消息。
public class LitePullConsumerSubscribe {
public static volatile boolean running = true;
public static void main(String[] args) throws Exception {
DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer("lite_pull_consumer_test");
litePullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
litePullConsumer.subscribe("TopicTest", "*");
litePullConsumer.setNamesrvAddr("127.0.0.1:9876");
litePullConsumer.start();
try {
while (running) {
List messageExts = litePullConsumer.poll();
System.out.printf("%s%n", messageExts);
}
} finally {
litePullConsumer.shutdown();
}
}
}
从org.apache.rocketmq.example.simple.LitePullConsumerSubscribe可以看到如果使用DefaultLitePullConsumer进行消息的消费,接下来就对整个过程中使用到的一些方法进行分析。
首先来认识一下DefaultLitePullConsumer
private final DefaultLitePullConsumerImpl defaultLitePullConsumerImpl;
// 消息消费组
private String consumerGroup;
// 长轮询模式下,consumer请求在broker端最长挂起时间
private long brokerSuspendMaxTimeMillis = 1000 * 20;
// 消息消费者拉取消息最大的超时时间,必须大于 brokerSuspendMaxTimeMillis
private long consumerTimeoutMillisWhenSuspend = 1000 * 30;
// 客户端与 Broker 建立网络连接的最大超时时间
private long consumerPullTimeoutMillis = 1000 * 10;
// 消息组消费模型:集群模式或者广播模式
private MessageModel messageModel = MessageModel.CLUSTERING;
// 消息消费负载队列变更事件
private MessageQueueListener messageQueueListener;
// 消息消费进度存储器
private OffsetStore offsetStore;
// 消息消费队列负载策略
private AllocateMessageQueueStrategy allocateMessageQueueStrategy = new AllocateMessageQueueAveragely();
private boolean unitMode = false;
// 设置是否提交消息消费进度
private boolean autoCommit = true;
// 每一个消费者拉取消息的线程数,相比于push模式很大的区别点
private int pullThreadNums = 20;
private static final long MIN_AUTOCOMMIT_INTERVAL_MILLIS = 1000;
// 自动汇报消息位点的间隔时间
private long autoCommitIntervalMillis = 5 * 1000;
// 一次消息拉取最多返回的消息条数
private int pullBatchSize = 10;
// 单个队列积压的消息条数触发限流的阔值
private long pullThresholdForAll = 10000;
// 单个消息处理队列中最大消息偏移量与最小偏移量的差值触发限流的阔值
private int consumeMaxSpan = 2000;
// 单个队列积压的消息总大小触发限流的阔值
private int pullThresholdForQueue = 1000;
// 单个队列挤压的消息总大小触发限流的阔值
private int pullThresholdSizeForQueue = 100;
// 一次消息拉取默认超时时间
private long pollTimeoutMillis = 1000 * 5;
// topic 路由信息更新频率
private long topicMetadataCheckIntervalMillis = 30 * 1000;
// 初次启动时从什么位置开始消费
private ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET;
// 如果初次启动时 consumeFromWhere 策略选择为基于时间戳,通过该属性设置定位的时间
private String consumeTimestamp = UtilAll.timeMillisToHumanString3(System.currentTimeMillis() - (1000 * 60 * 30));
可以看到的是DefaultLitePullConsumer基本是设置了一些属性,具体的功能实现都是调用的DefaultLitePullConsumerImpl,接下来看看它的一些主要方法
// 启动consumer
@Override
public void start() throws MQClientException {
setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup));
this.defaultLitePullConsumerImpl.start();
}
// 关闭consumer
@Override
public void shutdown() {
this.defaultLitePullConsumerImpl.shutdown();
}
// 使用subExpression表达式订阅topic,根据负载均衡算法计算分配的队列
@Override
public void subscribe(String topic, String subExpression) throws MQClientException {
this.defaultLitePullConsumerImpl.subscribe(withNamespace(topic), subExpression);
}
// 强制分配消费队列,跳过负载均衡算法
@Override
public void assign(Collection messageQueues) {
defaultLitePullConsumerImpl.assign(queuesWithNamespace(messageQueues));
}
// 从内存中获取消息
@Override
public List poll() {
return defaultLitePullConsumerImpl.poll(this.getPollTimeoutMillis());
}
// 重置消费offset
@Override
public void seek(MessageQueue messageQueue, long offset) throws MQClientException {
this.defaultLitePullConsumerImpl.seek(queueWithNamespace(messageQueue), offset);
}
// 停止从broker拉取对应MessageQueue的消息
@Override
public void pause(Collection messageQueues) {
this.defaultLitePullConsumerImpl.pause(queuesWithNamespace(messageQueues));
}
// 从NameServer获取topic下所有的messageQueue
@Override
public Collection fetchMessageQueues(String topic) throws MQClientException {
return this.defaultLitePullConsumerImpl.fetchMessageQueues(withNamespace(topic));
}
// 监听topic的messageQueue变化事件
@Override
public void registerTopicMessageQueueChangeListener(String topic,
TopicMessageQueueChangeListener topicMessageQueueChangeListener) throws MQClientException {
this.defaultLitePullConsumerImpl.registerTopicMessageQueueChangeListener(withNamespace(topic), topicMessageQueueChangeListener);
}
// 异步提交所有队列的offset
@Override
public void commitSync() {
this.defaultLitePullConsumerImpl.commitAll();
}
// 设置是否自动提交offset
@Override
public void setAutoCommit(boolean autoCommit) {
this.autoCommit = autoCommit;
}
对DefaultLitePullConsumer有了基本认识后,还是根据LitePullConsumerSubscribe这个demo类来分析DefaultLitePullConsumer的一些常规操作流程
这里主要分成两部分来看:
- consumer的启动
- 消息的拉取
这里先看看consumer的启动过程,首先看看DefaultLitePullConsumer的实例化方法。
public DefaultLitePullConsumer(final String consumerGroup) {
this(null, consumerGroup, null);
}
public DefaultLitePullConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook) {
this.namespace = namespace;
this.consumerGroup = consumerGroup;
defaultLitePullConsumerImpl = new DefaultLitePullConsumerImpl(this, rpcHook);
}
public DefaultLitePullConsumerImpl(final DefaultLitePullConsumer defaultLitePullConsumer, final RPCHook rpcHook) {
this.defaultLitePullConsumer = defaultLitePullConsumer;
this.rpcHook = rpcHook;
// 消息拉取线程池
this.scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(
this.defaultLitePullConsumer.getPullThreadNums(),
new ThreadFactoryImpl("PullMsgThread-" + this.defaultLitePullConsumer.getConsumerGroup())
);
// 监视topic的messageQueue变化线程
this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "MonitorMessageQueueChangeThread");
}
});
// 发生异常时拉取消息的延迟时间
this.pullTimeDelayMillsWhenException = defaultLitePullConsumer.getPullTimeDelayMillsWhenException();
}
接下来是调用DefaultLitePullConsumer的subscribe方法,订阅某个topic的消息
public synchronized void subscribe(String topic, String subExpression) throws MQClientException {
try {
if (topic == null || topic.equals("")) {
throw new IllegalArgumentException("Topic can not be null or empty.");
}
// 设置订阅类型
setSubscriptionType(SubscriptionType.SUBSCRIBE);
// 创建订阅信息
SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(defaultLitePullConsumer.getConsumerGroup(),
topic, subExpression);
// 保存订阅关系
this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData);
// 注册 messageQueue 的变化监听,触发消息拉取
this.defaultLitePullConsumer.setMessageQueueListener(new MessageQueueListenerImpl());
assignedMessageQueue.setRebalanceImpl(this.rebalanceImpl);
if (serviceState == ServiceState.RUNNING) {
this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
// 再次确认是否更新订阅信息
updateTopicSubscribeInfoWhenSubscriptionChanged();
}
} catch (Exception e) {
throw new MQClientException("subscribe exception", e);
}
}
SubscriptionType一共有三种
- NONE 未知
- SUBSCRIBE 订阅,负载均衡算法分配消费的队列
- ASSIGN 分配,指定消费队列,跳过负载均衡算法
首先看下是怎么创建订阅信息的
public static SubscriptionData buildSubscriptionData(final String consumerGroup, String topic,
String subString) throws Exception {
// 创建SubscriptionData,保存订阅信息
SubscriptionData subscriptionData = new SubscriptionData();
subscriptionData.setTopic(topic);
subscriptionData.setSubString(subString);
// 处理订阅表达式
if (null == subString || subString.equals(SubscriptionData.SUB_ALL) || subString.length() == 0) {
subscriptionData.setSubString(SubscriptionData.SUB_ALL);
} else {
// 根据 || 分割订阅的多个tag
String[] tags = subString.split("\\|\\|");
if (tags.length > 0) {
for (String tag : tags) {
if (tag.length() > 0) {
String trimString = tag.trim();
// 保存tag和tag的hashcode
if (trimString.length() > 0) {
subscriptionData.getTagsSet().add(trimString);
subscriptionData.getCodeSet().add(trimString.hashCode());
}
}
}
} else {
throw new Exception("subString split error");
}
}
return subscriptionData;
}
方法比较简单
回到subscribe中,这里定义了一个MessageQueueListenerImpl,这个类就是消息拉取的具体实现,后续会用到
这里还有个判断逻辑是serviceState == ServiceState.RUNNING是,代表着程序先调用了DefaultLitePullConsumer的start方法,然后才调用了subscribe,这个时候就需要向broker发送心跳,并且更新订阅关系。
接下来看看DefaultLitePullConsumer的start方法
public void start() throws MQClientException {
setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup));
this.defaultLitePullConsumerImpl.start();
}
public synchronized void start() throws MQClientException {
switch (this.serviceState) {
case CREATE_JUST:
// 和producer启动一样,先设置状态为 启动失败
this.serviceState = ServiceState.START_FAILED;
// 校验一些基础配置
this.checkConfig();
// 如果是集群模式且instanceName=DEFAULT,修改instanceName为pid
if (this.defaultLitePullConsumer.getMessageModel() == MessageModel.CLUSTERING) {
this.defaultLitePullConsumer.changeInstanceNameToPID();
}
// 初始化 MQClientFactory
initMQClientFactory();
// 初始化 RebalanceImpl,主要是属性设置
initRebalanceImpl();
// 实例化 PullAPIWrapper
initPullAPIWrapper();
// 实例化 offsetStore
initOffsetStore();
// 同 producer 的启动过程
mQClientFactory.start();
// 从 NameServer 获取 messageQueue,和本地的比较
// 如果变化则通知 TopicMessageQueueChangeListener
startScheduleTask();
this.serviceState = ServiceState.RUNNING;
log.info("the consumer [{}] start OK", this.defaultLitePullConsumer.getConsumerGroup());
// 用于处理 subscribe 或 assign 方法在 start 前就已经执行
// 并且保存 topic 的 messageQueue 信息到 messageQueuesForTopic
operateAfterRunning();
break;
case RUNNING:
case START_FAILED:
case SHUTDOWN_ALREADY:
throw new MQClientException("The PullConsumer service state not OK, maybe started once, "
+ this.serviceState
+ FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
null);
default:
break;
}
}
这个过程中初始化了一些属性,initMQClientFactory和producer启动中的一样,initPullAPIWrapper是用于注册钩子方法,因此继续向下看initRebalanceImpl方法
private void initRebalanceImpl() {
this.rebalanceImpl.setConsumerGroup(this.defaultLitePullConsumer.getConsumerGroup());
this.rebalanceImpl.setMessageModel(this.defaultLitePullConsumer.getMessageModel());
this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultLitePullConsumer.getAllocateMessageQueueStrategy());
this.rebalanceImpl.setmQClientFactory(this.mQClientFactory);
}
private void initOffsetStore() throws MQClientException {
if (this.defaultLitePullConsumer.getOffsetStore() != null) {
this.offsetStore = this.defaultLitePullConsumer.getOffsetStore();
} else {
switch (this.defaultLitePullConsumer.getMessageModel()) {
case BROADCASTING:
this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultLitePullConsumer.getConsumerGroup());
break;
case CLUSTERING:
this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultLitePullConsumer.getConsumerGroup());
break;
default:
break;
}
this.defaultLitePullConsumer.setOffsetStore(this.offsetStore);
}
// 用于 LocalFileOffsetStore 从本地磁盘加载消费进度
this.offsetStore.load();
}
初始化了一个rebalanceImpl和offsetStore,接下来的重头戏是mQClientFactory的start方法
public void start() throws MQClientException {
synchronized (this) {
switch (this.serviceState) {
case CREATE_JUST:
this.serviceState = ServiceState.START_FAILED;
// If not specified,looking address from name server
if (null == this.clientConfig.getNamesrvAddr()) {
this.mQClientAPIImpl.fetchNameServerAddr();
}
// 开启 netty 服务
this.mQClientAPIImpl.start();
// 开启一些定时任务
this.startScheduledTask();
// 开启拉取消息服务,PUSH模式的消费者使用,pull模式下线程会一直等待
this.pullMessageService.start();
// consumer平衡消费组和消息队列的关系
this.rebalanceService.start();
// Start push service
this.defaultMQProducer.getDefaultMQProducerImpl().start(false);
log.info("the client factory [{}] start OK", this.clientId);
this.serviceState = ServiceState.RUNNING;
break;
case START_FAILED:
throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null);
default:
break;
}
}
}
startScheduledTask开启的一批定时任务中有一个定时器是
// 持久化 consumer 的消费进度
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
MQClientInstance.this.persistAllConsumerOffset();
} catch (Exception e) {
log.error("ScheduledTask persistAllConsumerOffset exception", e);
}
}
}, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS);
继续分析persistAllConsumerOffset方法
private void persistAllConsumerOffset() {
// 获取所有的消费者
Iterator> it = this.consumerTable.entrySet().iterator();
while (it.hasNext()) {
Entry entry = it.next();
MQConsumerInner impl = entry.getValue();
impl.persistConsumerOffset();
}
}
这里主要看下DefaultLitePullConsumerImpl对persistConsumerOffset的实现
public void persistConsumerOffset() {
try {
checkServiceState();
Set mqs = new HashSet();
// 判断当前 consumer 的订阅类型
// 如果是订阅就取处理中的队列
if (this.subscriptionType == SubscriptionType.SUBSCRIBE) {
Set allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet();
mqs.addAll(allocateMq);
}
// 如果是分配的
else if (this.subscriptionType == SubscriptionType.ASSIGN) {
Set assignedMessageQueue = this.assignedMessageQueue.getAssignedMessageQueues();
mqs.addAll(assignedMessageQueue);
}
// 持久化队列消费 offset 到远端或者是本地
this.offsetStore.persistAll(mqs);
} catch (Exception e) {
log.error("Persist consumer offset error for group: {} ", this.defaultLitePullConsumer.getConsumerGroup(), e);
}
}
根据subscriptionType获取到对应的MessageQueue集合后,调用offsetStore.persistAll
集群模式下offsetStore就是RemoteBrokerOffsetStore,所以继续看RemoteBrokerOffsetStore对persistAll的实现
public void persistAll(Set mqs) {
if (null == mqs || mqs.isEmpty())
return;
// 持久化消息队列
final HashSet unusedMQ = new HashSet();
// offsetTable中的数据来源是当DefaultLitePullConsumer设置autoCommit为true是,在poll方法中会自动更新offsetTable中的offset
for (Map.Entry entry : this.offsetTable.entrySet()) {
MessageQueue mq = entry.getKey();
AtomicLong offset = entry.getValue();
if (offset != null) {
if (mqs.contains(mq)) {
try {
// 把offset更新到broker去
this.updateConsumeOffsetToBroker(mq, offset.get());
log.info("[persistAll] Group: {} ClientId: {} updateConsumeOffsetToBroker {} {}",
this.groupName,
this.mQClientFactory.getClientId(),
mq,
offset.get());
} catch (Exception e) {
log.error("updateConsumeOffsetToBroker exception, " + mq.toString(), e);
}
} else {
unusedMQ.add(mq);
}
}
}
// 移除不适用的消息队列
if (!unusedMQ.isEmpty()) {
for (MessageQueue mq : unusedMQ) {
this.offsetTable.remove(mq);
log.info("remove unused mq, {}, {}", mq, this.groupName);
}
}
}
这里就是自动提交Offset的相关实现,继续看是如何实现的
private void updateConsumeOffsetToBroker(MessageQueue mq, long offset) throws RemotingException,
MQBrokerException, InterruptedException, MQClientException {
updateConsumeOffsetToBroker(mq, offset, true);
}
public void updateConsumeOffsetToBroker(MessageQueue mq, long offset, boolean isOneway) throws RemotingException,
MQBrokerException, InterruptedException, MQClientException {
// 查询Broker地址信息
FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInAdmin(mq.getBrokerName());
// 如果没有查询到,就从NameServer获取topic的路由信息
if (null == findBrokerResult) {
this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
findBrokerResult = this.mQClientFactory.findBrokerAddressInAdmin(mq.getBrokerName());
}
if (findBrokerResult != null) {
UpdateConsumerOffsetRequestHeader requestHeader = new UpdateConsumerOffsetRequestHeader();
requestHeader.setTopic(mq.getTopic());
requestHeader.setConsumerGroup(this.groupName);
requestHeader.setQueueId(mq.getQueueId());
requestHeader.setCommitOffset(offset);
if (isOneway) {
this.mQClientFactory.getMQClientAPIImpl().updateConsumerOffsetOneway(
findBrokerResult.getBrokerAddr(), requestHeader, 1000 * 5);
} else {
this.mQClientFactory.getMQClientAPIImpl().updateConsumerOffset(
findBrokerResult.getBrokerAddr(), requestHeader, 1000 * 5);
}
} else {
throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}
}
public void updateConsumerOffsetOneway(
final String addr,
final UpdateConsumerOffsetRequestHeader requestHeader,
final long timeoutMillis
) throws RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException,
InterruptedException {
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, requestHeader);
this.remotingClient.invokeOneway(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis);
}
可以看到最终是向broker发送了一个RequestCode=UPDATE_CONSUMER_OFFSET的请求
回到MQClientInstance的start方法中,接下来是pullMessageService.start()和push模式的消息拉取相关,暂不分析
然后是rebalanceService.start()
public void run() {
log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
// 默认间隔20s执行一次
this.waitForRunning(waitInterval);
this.mqClientFactory.doRebalance();
}
log.info(this.getServiceName() + " service end");
}
// 循环遍历每个消费组获取 MQConsumeInner 对象
// 并执行其 doRebalance 方法
public void doRebalance() {
for (Map.Entry entry : this.consumerTable.entrySet()) {
MQConsumerInner impl = entry.getValue();
if (impl != null) {
try {
impl.doRebalance();
} catch (Throwable e) {
log.error("doRebalance exception", e);
}
}
}
}
接下来继续看DefaultLitePullConsumerImpl的doRebalance实现
public void doRebalance() {
if (this.rebalanceImpl != null) {
this.rebalanceImpl.doRebalance(false);
}
}
// isOrder这里是false
public void doRebalance(final boolean isOrder) {
// 获取topic的订阅关系
// 如果没有执行DefaultLitePullConsumer的subscribe方法,也就不会有订阅关系,也就不会负载均衡
Map subTable = this.getSubscriptionInner();
if (subTable != null) {
for (final Map.Entry entry : subTable.entrySet()) {
final String topic = entry.getKey();
try {
// 对每一个topic进行负载均衡
this.rebalanceByTopic(topic, isOrder);
} catch (Throwable e) {
if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
log.warn("rebalanceByTopic Exception", e);
}
}
}
}
// 移除 MessageQueue,如果 MesageQueue 的 topic 不在订阅的主题中
this.truncateMessageQueueNotMyTopic();
}
接下来继续看负载均衡的具体实现
private void rebalanceByTopic(final String topic, final boolean isOrder) {
switch (messageModel) {
// 广播模式
case BROADCASTING: {
Set mqSet = this.topicSubscribeInfoTable.get(topic);
if (mqSet != null) {
boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet, isOrder);
if (changed) {
this.messageQueueChanged(topic, mqSet, mqSet);
log.info("messageQueueChanged {} {} {} {}",
consumerGroup,
topic,
mqSet,
mqSet);
}
} else {
log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
}
break;
}
// 集群模式
case CLUSTERING: {
// 主题的消息消费队列
Set mqSet = this.topicSubscribeInfoTable.get(topic);
// 主题的当前消费组的消费者id列表
List cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);
if (null == mqSet) {
if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
}
}
if (null == cidAll) {
log.warn("doRebalance, {} {}, get consumer id list failed", consumerGroup, topic);
}
// 主要是对主题的消息队列排序、消费者ID进行排序,然后利用分配算法,计算当前消费者ID(mqClient.clientId) 分配出需要拉取的消息队列
if (mqSet != null && cidAll != null) {
List mqAll = new ArrayList();
mqAll.addAll(mqSet);
Collections.sort(mqAll);
Collections.sort(cidAll);
AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;
// 根据 队列分配策略 分配消息队列
List allocateResult = null;
try {
allocateResult = strategy.allocate(
this.consumerGroup,
this.mQClientFactory.getClientId(),
mqAll,
cidAll);
} catch (Throwable e) {
log.error("AllocateMessageQueueStrategy.allocate Exception. allocateMessageQueueStrategyName={}", strategy.getName(),
e);
return;
}
Set allocateResultSet = new HashSet();
if (allocateResult != null) {
allocateResultSet.addAll(allocateResult);
}
// 更新主题的消息消费处理队列,并返回消息队列负载是否改变
boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);
if (changed) {
log.info(
"rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}",
strategy.getName(), consumerGroup, topic, this.mQClientFactory.getClientId(), mqSet.size(), cidAll.size(),
allocateResultSet.size(), allocateResultSet);
// 通知 MessageQueueListener ,分配的队列信息变更
this.messageQueueChanged(topic, mqSet, allocateResultSet);
}
}
break;
}
default:
break;
}
}
这里主要关注集群模式下的处理方式,主要做了三件事
- 根据负载均衡算法,获取分配到的队列
- 判断负责消费的队列信息是否变更
- 发布消费的队列信息变更事件
负载均衡算法具体不分析,接下来看看updateProcessQueueTableInRebalance
private boolean updateProcessQueueTableInRebalance(final String topic, final Set mqSet,
final boolean isOrder) {
boolean changed = false;
// 遍历消息队列-处理队列缓存
Iterator> it = this.processQueueTable.entrySet().iterator();
while (it.hasNext()) {
Entry next = it.next();
MessageQueue mq = next.getKey();
ProcessQueue pq = next.getValue();
// 只处理 mq 的主题与该主题相关的 ProcessQueue
if (mq.getTopic().equals(topic)) {
// 如果 mq 不在当期主题的处理范围内
if (!mqSet.contains(mq)) {
// 首先设置该消息队列为丢弃
pq.setDropped(true);
// 判断是否需要移除
if (this.removeUnnecessaryMessageQueue(mq, pq)) {
it.remove();
changed = true;
log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq);
}
}
// 距离上次拉取,超过最长等待时间
else if (pq.isPullExpired()) {
switch (this.consumeType()) {
// 如果是 pull 模式
case CONSUME_ACTIVELY:
break;
// 如果是 push 模式
case CONSUME_PASSIVELY:
pq.setDropped(true);
if (this.removeUnnecessaryMessageQueue(mq, pq)) {
it.remove();
changed = true;
log.error("[BUG]doRebalance, {}, remove unnecessary mq, {}, because pull is pause, so try to fixed it",
consumerGroup, mq);
}
break;
default:
break;
}
}
}
}
// 增加 不在processQueueTable && 存在于mqSet 里的消息队列。
List pullRequestList = new ArrayList();
for (MessageQueue mq : mqSet) {
// 处理新分配过来的队列
if (!this.processQueueTable.containsKey(mq)) {
if (isOrder && !this.lock(mq)) {
log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq);
continue;
}
// 在内存中移除 MessageQueue 的 offerset
this.removeDirtyOffset(mq);
ProcessQueue pq = new ProcessQueue();
// 计算下一个拉取偏移量
long nextOffset = this.computePullFromWhere(mq);
if (nextOffset >= 0) {
// 创建一个拉取任务
ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
if (pre != null) {
log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);
} else {
log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
PullRequest pullRequest = new PullRequest();
pullRequest.setConsumerGroup(consumerGroup);
pullRequest.setNextOffset(nextOffset);
pullRequest.setMessageQueue(mq);
pullRequest.setProcessQueue(pq);
pullRequestList.add(pullRequest);
changed = true;
}
} else {
log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq);
}
}
}
// 立刻执行拉取消息请求
this.dispatchPullRequest(pullRequestList);
return changed;
}
值得一提的是computePullFromWhere方法,看下这里是如何得知下一次消费开始的offset
public long computePullFromWhere(MessageQueue mq) {
// 获取consumeFromWhere配置
ConsumeFromWhere consumeFromWhere = litePullConsumerImpl.getDefaultLitePullConsumer().getConsumeFromWhere();
long result = -1;
switch (consumeFromWhere) {
case CONSUME_FROM_LAST_OFFSET: {
long lastOffset = litePullConsumerImpl.getOffsetStore().readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE);
if (lastOffset >= 0) {
result = lastOffset;
} else if (-1 == lastOffset) {
// 重试队列
if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { // First start, no offset
result = 0L;
}
// 否则获取最大的offset
else {
try {
result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq);
} catch (MQClientException e) {
result = -1;
}
}
} else {
result = -1;
}
break;
}
case CONSUME_FROM_FIRST_OFFSET: {
long lastOffset = litePullConsumerImpl.getOffsetStore().readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE);
if (lastOffset >= 0) {
result = lastOffset;
} else if (-1 == lastOffset) {
result = 0L;
} else {
result = -1;
}
break;
}
case CONSUME_FROM_TIMESTAMP: {
long lastOffset = litePullConsumerImpl.getOffsetStore().readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE);
if (lastOffset >= 0) {
result = lastOffset;
} else if (-1 == lastOffset) {
if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
try {
result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq);
} catch (MQClientException e) {
result = -1;
}
} else {
try {
long timestamp = UtilAll.parseDate(this.litePullConsumerImpl.getDefaultLitePullConsumer().getConsumeTimestamp(),
UtilAll.YYYYMMDDHHMMSS).getTime();
result = this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp);
} catch (MQClientException e) {
result = -1;
}
}
} else {
result = -1;
}
break;
}
}
return result;
}
public long readOffset(final MessageQueue mq, final ReadOffsetType type) {
if (mq != null) {
switch (type) {
// 先从内存中读取,如果内存中不存在,再尝试从远程中读取
case MEMORY_FIRST_THEN_STORE:
// 从内存中读取
case READ_FROM_MEMORY: {
AtomicLong offset = this.offsetTable.get(mq);
if (offset != null) {
return offset.get();
} else if (ReadOffsetType.READ_FROM_MEMORY == type) {
return -1;
}
}
// 从远程读取然后更新到本地
case READ_FROM_STORE: {
try {
long brokerOffset = this.fetchConsumeOffsetFromBroker(mq);
AtomicLong offset = new AtomicLong(brokerOffset);
this.updateOffset(mq, offset.get(), false);
return brokerOffset;
}
// No offset in broker
catch (MQBrokerException e) {
return -1;
}
//Other exceptions
catch (Exception e) {
log.warn("fetchConsumeOffsetFromBroker exception, " + mq, e);
return -2;
}
}
default:
break;
}
}
return -1;
}
可以看到不管是哪种consumeFromWhere配置,都是默认先从本地或broker获取最新的offset消费进度。不一样的是没有获取到消费进度时,每种consumeFromWhere的处理方式。
回到RebalanceImpl的rebalanceByTopic方法,继续看RebalanceLitePullImpl的messageQueueChanged方法
public void messageQueueChanged(String topic, Set mqAll, Set mqDivided) {
MessageQueueListener messageQueueListener = this.litePullConsumerImpl.getDefaultLitePullConsumer().getMessageQueueListener();
if (messageQueueListener != null) {
try {
messageQueueListener.messageQueueChanged(topic, mqAll, mqDivided);
} catch (Throwable e) {
log.error("messageQueueChanged exception", e);
}
}
}
这里和消息拉取相关,后续分析
到此consumer的启动过程就分析完了