一、RocketMq消费方式
mq的消费代码位置:
由代码截图可以看出消费者消费有两种消费,一种是推,一种是拉。
1)Push方式:由消息中间件(MQ消息服务器代理)主动地将消息推送给消费者;采用Push方式,可以尽可能实时地将消息发送给消费者进行消费。但是,在消费者的处理消息的能力较弱的时候(比如,消费者端的业务系统处理一条消息的流程比较复杂,其中的调用链路比较多导致消费时间比较久。概括起来地说就是“慢消费问题”),而MQ不断地向消费者Push消息,消费者端的缓冲区可能会溢出,导致异常;
(2)Pull方式:由消费者客户端主动向消息中间件(MQ消息服务器代理)拉取消息;采用Pull方式,如何设置Pull消息的频率需要重点去考虑,举个例子来说,可能1分钟内连续来了1000条消息,然后2小时内没有新消息产生(概括起来说就是“消息延迟与忙等待”)。如果每次Pull的时间间隔比较久,会增加消息的延迟,即消息到达消费者的时间加长,MQ中消息的堆积量变大;若每次Pull的时间间隔较短,但是在一段时间内MQ中并没有任何消息可以消费,那么会产生很多无效的Pull请求的RPC开销,影响MQ整体的网络性能;
mq的消费我们消费方式的测试类入手:
代码地址:org.apache.rocketmq.client.consumer.DefaultMQPullConsumerTest
- consumer的初始化,init()
- consumer的终止,terminate()
- consume的拉取消息;testPullMessage_Success
- consume的拉取消息未发现,testPullMessage_NotFound
- consume的异步拉取消息;testPullMessageAsync_Success
MQ的消息消费方式是拉取消息的方式,消息消费有两种消费方式:广播模式与集群模式,
广播模式;每一个消费方式去拉取订阅主题下所有消息队列的消息,集群模式;在集群模式下,同一个消费组内,有多个消费者,同一个主题存在多个消息队列,消息队列负载,通常的做法是同一个时间内只允许被一个消费者消费,一个消费者可以同时消费多个消息对列。
从MQclient的启动流程来看,RocketMq使用一个单独的线程PullMessageService来负责代码的拉取;
二、PullMessageService
初始化一个newSingleThreadScheduledExecutor线程池
private final ScheduledExecutorService scheduledExecutorService = Executors
.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "PullMessageServiceScheduledThread");
}
});
定时先线程池执行任务:
调用DefaultMQPushConsumerImpl impl 的pullMessage;
schedule中的pullRequestQueue如何实现放入PullRequest的,PullMessageService有两种实现方式;
1、延时放入executePullRequestLater 2、立即放入 executePullRequestImmediately
2.1、PullRequset
PullRequset的核心属性:
String consumerGroup :消费者组
MessageQueue :待拉取的消费队列
ProcessQueue processQueue : 消息处理队列
long nextOffset :拉取偏移量
boolean lockedFirst :是否被锁定;
2.2、ProcessQueue实现机制
// 锁最大存活时间。可以通过-Drocketmq.broker.rebalance.lockMaxLiveTime设置,默认为60s。
public final static long REBALANCE_LOCK_MAX_LIVE_TIME =
Long.parseLong(System.getProperty("rocketmq.client.rebalance.lockMaxLiveTime", "30000"));
public final static long REBALANCE_LOCK_INTERVAL = Long.parseLong(System.getProperty("rocketmq.client.rebalance.lockInterval", "20000"));
// 客户端拉取消息最大空闲时间
private final static long PULL_MAX_IDLE_TIME = Long.parseLong(System.getProperty("rocketmq.client.pull.pullMaxIdleTime", "120000"));
private final InternalLogger log = ClientLogger.getLog();
// 消息存储map的读写锁
private final ReadWriteLock lockTreeMap = new ReentrantReadWriteLock();
// 消息队列的索引下标以及消息体
private final TreeMap<Long, MessageExt> msgTreeMap = new TreeMap<Long, MessageExt>();
private final AtomicLong msgCount = new AtomicLong();
private final AtomicLong msgSize = new AtomicLong();
private final Lock lockConsume = new ReentrantLock();
/**
* A subset of msgTreeMap, will only be used when orderly consume
*/
private final TreeMap<Long, MessageExt> consumingMsgOrderlyTreeMap = new TreeMap<Long, MessageExt>();
private final AtomicLong tryUnlockTimes = new AtomicLong(0);
// 消息队列的最大偏移量
private volatile long queueOffsetMax = 0L;、
// 当前ProcessQueue包含的最大偏移量
private volatile boolean dropped = false;
// 上一次拉取消息的时间搓
private volatile long lastPullTimestamp = System.currentTimeMillis();
// 上一次消费的时间搓
private volatile long lastConsumeTimestamp = System.currentTimeMillis();
private volatile boolean locked = false;
private volatile long lastLockTimestamp = System.currentTimeMillis();
private volatile boolean consuming = false;
private volatile long msgAccCnt = 0;
2.2、pullMessage的基本流程
消息拉取主要三个步骤
- 消息拉取客户端请求封装
- 消息服务器查找并返回消息
- 消息拉取客户端处理返回消息
1、客户端封装消息拉取请求
(1)拉取消息的前置判断
(2)拉取消息的限流判断
(3)、拉取主题订阅消息
final SubscriptionData subscriptionData = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());
if (null == subscriptionData) {
this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
log.warn("find the consumer's subscription failed, {}", pullRequest);
return;
}
(4)、调用pullAPIWrapper.pullKernelImpl与系统交互
this.pullAPIWrapper.pullKernelImpl(
pullRequest.getMessageQueue(),
subExpression,
subscriptionData.getExpressionType(),
subscriptionData.getSubVersion(),
pullRequest.getNextOffset(),
this.defaultMQPushConsumer.getPullBatchSize(),
sysFlag,
commitOffsetValue,
BROKER_SUSPEND_MAX_TIME_MILLIS,
CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,
CommunicationMode.ASYNC,
pullCallback
);
MessageQueue mq:从那个消息队列拉取消息
String subExpression:消息过滤表达式
String expressionType:消息表达式类型
long subVersion:
long offset:消息拉取偏移量
int maxNums:本次拉取消息的最大条数
int sysFlag:拉取系统标记
long commitOffset:当前messageQueue的消费进度
long brokerSuspendMaxTimeMillis:消息拉取过程中允许broker挂起的时间,默认15s。
long timeoutMillis:消息拉取的超时时间
CommunicationMode communicationMode:消息拉取的模式,异步拉取
PullCallback pullCallback:从broker拉取消息后的回调方法
(6)根据brokerName,brokerId从MQClientInstance中获取broker的地址
FindBrokerResult findBrokerResult =
this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
this.recalculatePullFromWhichNode(mq), false);
if (null == findBrokerResult) {
this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
findBrokerResult =
this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
this.recalculatePullFromWhichNode(mq), false);
}
// 请求拉取消息
PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
brokerAddr,
requestHeader,
timeoutMillis,
communicationMode,
pullCallback);
2、消息服务端Broker组装消息
final GetMessageResult getMessageResult =
this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), requestHeader.getTopic(),
requestHeader.getQueueId(), requestHeader.getQueueOffset(), requestHeader.getMaxMsgNums(), messageFilter);
String group:消费组名称
String topic:主题名称
int queueId:队列id
long offset:待拉取队列便宜量
int maxMsgNums:最大拉取条数
3、消息拉取客户端处理消息
消息拉取拉取客户端的入口MQClientAPIImpl#pullMessageAsync;
RocketMq整体拉取流程:
文章参考:RocketMq架构设计与实现原理