consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.println(new String(msg.getBody())+" Thread:"+Thread.currentThread()+" QueueID:"+msg.getQueueId());
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
在使用MessageListenerOrderly()时,发现只有当consumer第一次启动时使用了单线程去顺序消费
,但随后就不再使用单线程,这和源码注释是相悖的(One queue by one thread)
顺序消息测试15 Thread:Thread[ConsumeMessageThread_1,5,main] QueueID:1
顺序消息测试16 Thread:Thread[ConsumeMessageThread_1,5,main] QueueID:1
顺序消息测试17 Thread:Thread[ConsumeMessageThread_1,5,main] QueueID:1
顺序消息测试18 Thread:Thread[ConsumeMessageThread_1,5,main] QueueID:1
顺序消息测试19 Thread:Thread[ConsumeMessageThread_1,5,main] QueueID:1
顺序消息测试0 Thread:Thread[ConsumeMessageThread_2,5,main] QueueID:1
顺序消息测试1 Thread:Thread[ConsumeMessageThread_3,5,main] QueueID:1
顺序消息测试2 Thread:Thread[ConsumeMessageThread_4,5,main] QueueID:1
顺序消息测试3 Thread:Thread[ConsumeMessageThread_5,5,main] QueueID:1
顺序消息测试4 Thread:Thread[ConsumeMessageThread_6,5,main] QueueID:1
顺序消息测试5 Thread:Thread[ConsumeMessageThread_7,5,main] QueueID:1
在百度后,在RocketMQ的顺序消息(顺序消费)文章中得到了答案
实际上,每一个消费者的的消费端都是采用线程池实现多线程消费的模式,即消费端是多线程消费。虽然MessageListenerOrderly被称为有序消费模式,但是仍然是使用的线程池去消费消息。
MessageListenerConcurrently是拉取到新消息之后就提交到线程池去消费,而MessageListenerOrderly则是通过加分布式锁和本地锁保证同时只有一条线程去消费一个队列上的数据。
MessageListenerOrderly的加锁机制:
1.消费者在进行某个队列的消息拉取时首先向Broker服务器申请队列锁,如果申请到锁,则拉取消息,否则放弃消息拉取,等到下一个队列负载周期(20s)再试。这一个锁使得一个MessageQueue同一个时刻只能被一个消费客户端消费,防止因为队列负载均衡导致消息重复消费。
2.假设消费者对messageQueue的加锁已经成功,那么会开始拉取消息,拉取到消息后同样会提交到消费端的线程池进行消费。但在本地消费之前,会先获取该messageQueue对应的锁对象,每一个messageQueue对应一个锁对象,获取到锁对象后,使用synchronized阻塞式的申请线程级独占锁。这一个锁使得来自同一个messageQueue的消息在本地的同一个时刻只能被一个消费客户端中的一个线程顺序的消费。
3.在本地加synchronized锁成功之后,还会判断如果是广播模式,则直接进行消费,如果是集群模式,则判断如果messagequeue没有锁住或者锁过期(默认30000ms),那么延迟100ms后再次尝试向Broker 申请锁定messageQueue,锁定成功后重新提交消费请求。
public void lockAll() {
HashMap> brokerMqs = this.buildProcessQueueTableByBrokerName();
Iterator>> it = brokerMqs.entrySet().iterator();
while (it.hasNext()) {
Entry> entry = it.next();
final String brokerName = entry.getKey();
final Set mqs = entry.getValue();
if (mqs.isEmpty())
continue;
FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true);
if (findBrokerResult != null) {
LockBatchRequestBody requestBody = new LockBatchRequestBody();
requestBody.setConsumerGroup(this.consumerGroup);
requestBody.setClientId(this.mQClientFactory.getClientId());
requestBody.setMqSet(mqs);
try {
Set lockOKMQSet =
this.mQClientFactory.getMQClientAPIImpl().lockBatchMQ(findBrokerResult.getBrokerAddr(), requestBody, 1000);
for (MessageQueue mq : lockOKMQSet) {
ProcessQueue processQueue = this.processQueueTable.get(mq);
if (processQueue != null) {
if (!processQueue.isLocked()) {
log.info("the message queue locked OK, Group: {} {}", this.consumerGroup, mq);
}
processQueue.setLocked(true);
processQueue.setLastLockTimestamp(System.currentTimeMillis());
}
}
for (MessageQueue mq : mqs) {
if (!lockOKMQSet.contains(mq)) {
ProcessQueue processQueue = this.processQueueTable.get(mq);
if (processQueue != null) {
processQueue.setLocked(false);
log.warn("the message queue locked Failed, Group: {} {}", this.consumerGroup, mq);
}
}
}
} catch (Exception e) {
log.error("lockBatchMQ exception, " + mqs, e);
}
}
}
}