ali-ons-sdk
Version:
Aliyun Open Notification Service Client
779 lines (705 loc) • 28.3 kB
JavaScript
'use strict';
const assert = require('assert');
const is = require('is-type-of');
const utils = require('../utils');
const logger = require('../logger');
const MixAll = require('../mix_all');
const MQClient = require('../mq_client');
const MQProducer = require('../producer/mq_producer');
const sleep = require('mz-modules/sleep');
const ClientConfig = require('../client_config');
const ProcessQueue = require('../process_queue');
const PullStatus = require('../consumer/pull_status');
const PullSysFlag = require('../utils/pull_sys_flag');
const ConsumeFromWhere = require('./consume_from_where');
const MessageModel = require('../protocol/message_model');
const ConsumeType = require('../protocol/consume_type');
const ReadOffsetType = require('../store/read_offset_type');
const LocalFileOffsetStore = require('../store/local_file');
const LocalMemoryOffsetStore = require('../store/local_memory');
const RemoteBrokerOffsetStore = require('../store/remote_broker');
const AllocateMessageQueueAveragely = require('./rebalance/allocate_message_queue_averagely');
const Message = require('../message/message');
const MessageConst = require('../message/message_const');
const defaultOptions = {
logger,
persistent: false, // 是否持久化消费进度
isBroadcast: false, // 是否是广播模式(默认集群消费模式)
brokerSuspendMaxTimeMillis: 1000 * 15, // 长轮询模式,Consumer连接在Broker挂起最长时间
pullTimeDelayMillsWhenException: 3000, // 拉消息异常时,延迟一段时间再拉
pullTimeDelayMillsWhenFlowControl: 5000, // 进入流控逻辑,延迟一段时间再拉
consumerTimeoutMillisWhenSuspend: 1000 * 30, // 长轮询模式,Consumer超时时间(必须要大于brokerSuspendMaxTimeMillis)
consumerGroup: MixAll.DEFAULT_CONSUMER_GROUP,
consumeFromWhere: ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, // Consumer第一次启动时,从哪里开始消费
/**
* Consumer第一次启动时,如果回溯消费,默认回溯到哪个时间点,数据格式如下,时间精度秒:
* 20131223171201
* 表示2013年12月23日17点12分01秒
* 默认回溯到相对启动时间的半小时前
*/
consumeTimestamp: utils.timeMillisToHumanString(Date.now() - 1000 * 60 * 30),
pullThresholdForQueue: 500, // 本地队列消息数超过此阀值,开始流控
pullInterval: 0, // 拉取消息的频率, 如果为了降低拉取速度,可以设置大于0的值
consumeMessageBatchMaxSize: 1, // 消费一批消息,最大数
pullBatchSize: 32, // 拉消息,一次拉多少条
parallelConsumeLimit: 1, // 并发消费消息限制
postSubscriptionWhenPull: true, // 是否每次拉消息时,都上传订阅关系
allocateMessageQueueStrategy: new AllocateMessageQueueAveragely(), // 队列分配算法,应用可重写
maxReconsumeTimes: 16, // 最大重试次数
};
class MQPushConsumer extends ClientConfig {
constructor(options) {
assert(options && options.consumerGroup, '[MQPushConsumer] options.consumerGroup is required');
const mergedOptions = Object.assign({ initMethod: 'init' }, defaultOptions, options);
assert(mergedOptions.parallelConsumeLimit <= mergedOptions.pullBatchSize,
'[MQPushConsumer] options.parallelConsumeLimit must lte pullBatchSize');
super(mergedOptions);
// @example:
// pullFromWhichNodeTable => {
// '[topic="TEST_TOPIC", brokerName="qdinternet-03", queueId="1"]': 0
// }
this._pullFromWhichNodeTable = new Map();
this._subscriptions = new Map();
this._handles = new Map();
this._topicSubscribeInfoTable = new Map();
this._processQueueTable = new Map();
this._inited = false;
this._isClosed = false;
if (this.messageModel === MessageModel.CLUSTERING) {
this.changeInstanceNameToPID();
}
this._mqClient = MQClient.getAndCreateMQClient(this);
this._offsetStore = this.newOffsetStoreInstance();
this._mqClient.on('error', err => this._handleError(err));
this._offsetStore.on('error', err => this._handleError(err));
}
get logger() {
return this.options.logger;
}
get subscriptions() {
return this._subscriptions;
}
get processQueueTable() {
return this._processQueueTable;
}
get parallelConsumeLimit() {
return this.options.parallelConsumeLimit;
}
get consumerGroup() {
return this.options.consumerGroup;
}
get messageModel() {
return this.options.isBroadcast ? MessageModel.BROADCASTING : MessageModel.CLUSTERING;
}
get consumeType() {
return ConsumeType.CONSUME_PASSIVELY;
}
get consumeFromWhere() {
return this.options.consumeFromWhere;
}
get allocateMessageQueueStrategy() {
return this.options.allocateMessageQueueStrategy;
}
async init() {
this._mqClient.registerConsumer(this.consumerGroup, this);
await MQProducer.getDefaultProducer(this.options);
await this._mqClient.ready();
await this._offsetStore.load();
this.logger.info('[mq:consumer] consumer started');
this._inited = true;
// 订阅重试 TOPIC
if (this.messageModel === MessageModel.CLUSTERING) {
const retryTopic = MixAll.getRetryTopic(this.consumerGroup);
this.subscribe(retryTopic, '*', async msg => {
const originTopic = msg.retryTopic;
const originMsgId = msg.originMessageId;
const subscription = this._subscriptions.get(originTopic) || {};
const handler = subscription.handler;
if (!MixAll.isRetryTopic(originTopic) && handler) {
await handler(msg);
} else {
this.logger.warn('[MQPushConsumer] retry message no handler, originTopic: %s, originMsgId: %s, msgId: %s',
originTopic,
originMsgId,
msg.msgId);
}
});
}
}
/**
* close the consumer
*/
async close() {
this._isClosed = true;
await this.persistConsumerOffset();
this._pullFromWhichNodeTable.clear();
this._subscriptions.clear();
this._topicSubscribeInfoTable.clear();
this._processQueueTable.clear();
await this._mqClient.unregisterConsumer(this.consumerGroup);
await this._mqClient.close();
this.logger.info('[mq:consumer] consumer closed');
this.emit('close');
}
newOffsetStoreInstance() {
if (this.messageModel === MessageModel.BROADCASTING) {
if (this.options.persistent) {
return new LocalFileOffsetStore(this._mqClient, this.consumerGroup);
}
return new LocalMemoryOffsetStore(this._mqClient, this.consumerGroup);
}
return new RemoteBrokerOffsetStore(this._mqClient, this.consumerGroup);
}
/**
* subscribe
* @param {String} topic - topic
* @param {String} subExpression - tag
* @param {Function} handler - message handler
* @return {void}
*/
subscribe(topic, subExpression, handler) {
if (arguments.length === 2) {
handler = subExpression;
subExpression = null;
}
assert(is.asyncFunction(handler), '[MQPushConsumer] handler should be a asyncFunction');
assert(!this.subscriptions.has(topic), `[MQPushConsumer] ONLY one handler allowed for topic=${topic}`);
const subscriptionData = this.buildSubscriptionData(this.consumerGroup, topic, subExpression);
const tagsSet = subscriptionData.tagsSet;
const needFilter = !!tagsSet.length;
this.subscriptions.set(topic, {
handler,
subscriptionData,
});
(async () => {
try {
await this.ready();
// 如果 topic 没有路由信息,先更新一下
if (!this._topicSubscribeInfoTable.has(topic)) {
await this._mqClient.updateAllTopicRouterInfo();
await this._mqClient.sendHeartbeatToAllBroker();
await this._mqClient.doRebalance();
}
// 消息消费循环
while (!this._isClosed && this.subscriptions.has(topic)) {
await this._consumeMessageLoop(topic, needFilter, tagsSet, subExpression);
}
} catch (err) {
this._handleError(err);
}
})();
}
async _consumeMessageLoop(topic, needFilter, tagsSet, subExpression) {
const mqList = this._topicSubscribeInfoTable.get(topic);
let hasMsg = false;
if (mqList && mqList.length) {
for (const mq of mqList) {
const item = this._processQueueTable.get(mq.key);
if (item) {
const pq = item.processQueue;
while (pq.msgCount) {
hasMsg = true;
let msgs;
if (this.parallelConsumeLimit > pq.msgCount) {
msgs = pq.msgList.slice(0, pq.msgCount);
} else {
msgs = pq.msgList.slice(0, this.parallelConsumeLimit);
}
// 并发消费任务
const consumeTasks = [];
for (const msg of msgs) {
const handler = this._subscriptions.get(msg.topic).handler;
if (!msg.tags || !needFilter || tagsSet.includes(msg.tags)) {
consumeTasks.push(this.consumeSingleMsg(handler, msg, mq, pq));
} else {
this.logger.debug('[MQPushConsumer] message filter by tags=, msg.tags=%s', subExpression, msg.tags);
}
}
// 必须全部成功
try {
await Promise.all(consumeTasks);
} catch (err) {
continue;
}
// 注意这里必须是批量确认
const offset = pq.remove(msgs.length);
if (offset >= 0) {
this._offsetStore.updateOffset(mq, offset, true);
}
}
}
}
}
if (!hasMsg) {
await this.await(`topic_${topic}_changed`);
}
}
async consumeSingleMsg(handler, msg, mq, pq) {
// 集群消费模式下,如果消费失败,反复重试
while (!this._isClosed) {
try {
await handler(msg, mq, pq);
return;
} catch (err) {
err.message = `process mq message failed, topic: ${msg.topic}, msgId: ${msg.msgId}, ${err.message}`;
this.emit('error', err);
if (this.messageModel === MessageModel.CLUSTERING) {
// 发送重试消息
try {
// delayLevel 为 0 代表由服务端控制重试间隔
await this.sendMessageBack(msg, 0, mq.brokerName, this.consumerGroup);
return;
} catch (err) {
this.emit('error', err);
this.logger.error('[MQPushConsumer] send reconsume message failed, fallback to local retry, msgId: %s', msg.msgId);
// 重试消息发送失败,本地重试
await this._sleep(5000);
}
// 本地重试情况下需要给 reconsumeTimes +1
msg.reconsumeTimes++;
} else {
this.logger.warn('[MQPushConsumer] BROADCASTING consume message failed, drop it, msgId: %s', msg.msgId);
return;
}
}
}
}
/**
* construct subscription data
* @param {String} consumerGroup - consumer group name
* @param {String} topic - topic
* @param {String} subString - tag
* @return {Object} subscription
*/
buildSubscriptionData(consumerGroup, topic, subString) {
const subscriptionData = {
topic,
subString,
classFilterMode: false,
tagsSet: [],
codeSet: [],
subVersion: Date.now(),
};
if (is.nullOrUndefined(subString) || subString === '*' || subString === '') {
subscriptionData.subString = '*';
} else {
const tags = subString.split('||');
for (let tag of tags) {
tag = tag.trim();
if (tag) {
subscriptionData.tagsSet.push(tag);
subscriptionData.codeSet.push(utils.hashCode(tag));
}
}
}
return subscriptionData;
}
async persistConsumerOffset() {
const mqs = [];
for (const key of this._processQueueTable.keys()) {
if (this._processQueueTable.get(key)) {
mqs.push(this._processQueueTable.get(key).messageQueue);
}
}
await this._offsetStore.persistAll(mqs);
}
/**
* pull message from queue
* @param {MessageQueue} messageQueue - message queue
* @return {void}
*/
pullMessageQueue(messageQueue) {
const _self = this;
(async () => {
while (!_self._isClosed && _self._processQueueTable.has(messageQueue.key)) {
try {
await _self.executePullRequestImmediately(messageQueue);
await _self._sleep(_self.options.pullInterval);
} catch (err) {
if (!_self._isClosed) {
err.name = 'MQConsumerPullMessageError';
err.message = `[mq:consumer] pull message for queue: ${messageQueue.key}, occurred error: ${err.message}`;
_self._handleError(err);
await _self._sleep(_self.options.pullTimeDelayMillsWhenException);
}
}
}
})();
}
/**
* execute pull message immediately
* @param {MessageQueue} messageQueue - messageQueue
* @return {void}
*/
async executePullRequestImmediately(messageQueue) {
// close or queue removed
if (!this._processQueueTable.has(messageQueue.key)) {
return;
}
const pullRequest = this._processQueueTable.get(messageQueue.key);
const processQueue = pullRequest.processQueue;
// queue droped
if (processQueue.droped) {
return;
}
// flow control
const size = processQueue.msgCount;
if (size > this.options.pullThresholdForQueue) {
await this._sleep(this.options.pullTimeDelayMillsWhenFlowControl);
return;
}
processQueue.lastPullTimestamp = Date.now();
const data = this.subscriptions.get(messageQueue.topic);
const subscriptionData = data && data.subscriptionData;
if (!subscriptionData) {
this.logger.warn('[mq:consumer] execute pull request, but subscriptionData not found, topic: %s, queueId: %s', messageQueue.topic, messageQueue.queueId);
await this._sleep(this.options.pullTimeDelayMillsWhenException);
return;
}
let commitOffset = 0;
const subExpression = this.options.postSubscriptionWhenPull ? subscriptionData.subString : null;
const subVersion = subscriptionData.subVersion;
// cluster model
if (MessageModel.CLUSTERING === this.messageModel) {
const offset = await this._offsetStore.readOffset(pullRequest.messageQueue, ReadOffsetType.READ_FROM_MEMORY);
if (offset) {
commitOffset = offset;
}
}
const pullResult = await this.pullKernelImpl(messageQueue, subExpression, subVersion, pullRequest.nextOffset, commitOffset);
this.updatePullFromWhichNode(messageQueue, pullResult.suggestWhichBrokerId);
const originOffset = pullRequest.nextOffset;
// update next pull offset
pullRequest.nextOffset = pullResult.nextBeginOffset;
switch (pullResult.pullStatus) {
case PullStatus.FOUND:
{
const pullRT = Date.now() - processQueue.lastPullTimestamp;
this.logger.info('[MQPushConsumer] pull message success, found new message size: %d, topic: %s, consumerGroup: %s, messageQueue: %s cost: %dms.',
pullResult.msgFoundList.length, messageQueue.topic, this.consumerGroup, messageQueue.key, pullRT);
// submit to consumer
processQueue.putMessage(pullResult.msgFoundList);
this.emit(`topic_${messageQueue.topic}_changed`);
break;
}
case PullStatus.NO_NEW_MSG:
case PullStatus.NO_MATCHED_MSG:
this.logger.debug('[mq:consumer] no new message for topic: %s at message queue => %s', subscriptionData.topic, messageQueue.key);
this.correctTagsOffset(pullRequest);
break;
case PullStatus.OFFSET_ILLEGAL:
this.logger.warn('[mq:consumer] the pull request offset illegal, message queue => %s, the originOffset => %d, pullResult => %j', messageQueue.key, originOffset, pullResult);
this._offsetStore.updateOffset(messageQueue, pullRequest.nextOffset);
break;
default:
break;
}
}
async pullKernelImpl(messageQueue, subExpression, subVersion, offset, commitOffset) {
let sysFlag = PullSysFlag.buildSysFlag( //
commitOffset > 0, // commitOffset
true, // suspend
!!subExpression, // subscription
false // class filter
);
const result = await this.findBrokerAddress(messageQueue);
if (!result) {
throw new Error(`The broker[${messageQueue.brokerName}] not exist`);
}
// Slave不允许实时提交消费进度,可以定时提交
if (result.slave) {
sysFlag = PullSysFlag.clearCommitOffsetFlag(sysFlag);
}
const requestHeader = {
consumerGroup: this.consumerGroup,
topic: messageQueue.topic,
queueId: messageQueue.queueId,
queueOffset: offset,
maxMsgNums: this.options.pullBatchSize,
sysFlag,
commitOffset,
suspendTimeoutMillis: this.options.brokerSuspendMaxTimeMillis,
subscription: subExpression,
subVersion,
};
return await this._mqClient.pullMessage(result.brokerAddr, requestHeader, this.options.consumerTimeoutMillisWhenSuspend);
}
async findBrokerAddress(messageQueue) {
let findBrokerResult = this._mqClient.findBrokerAddressInSubscribe(
messageQueue.brokerName, this.recalculatePullFromWhichNode(messageQueue), false);
if (!findBrokerResult) {
await this._mqClient.updateTopicRouteInfoFromNameServer(messageQueue.topic);
findBrokerResult = this._mqClient.findBrokerAddressInSubscribe(
messageQueue.brokerName, this.recalculatePullFromWhichNode(messageQueue), false);
}
return findBrokerResult;
}
recalculatePullFromWhichNode(messageQueue) {
// @example:
// pullFromWhichNodeTable => {
// '[topic="TEST_TOPIC", brokerName="qdinternet-03", queueId="1"]': 0
// }
return this._pullFromWhichNodeTable.get(messageQueue.key) || MixAll.MASTER_ID;
}
correctTagsOffset(pullRequest) {
// 仅当已拉下的消息消费完的情况下才更新 offset
if (pullRequest.processQueue.msgCount === 0) {
this._offsetStore.updateOffset(pullRequest.messageQueue, pullRequest.nextOffset, true);
}
}
updatePullFromWhichNode(messageQueue, brokerId) {
this._pullFromWhichNodeTable.set(messageQueue.key, brokerId);
}
/**
* update subscription data
* @param {String} topic - topic
* @param {Array} info - info
* @return {void}
*/
updateTopicSubscribeInfo(topic, info) {
if (this._subscriptions.has(topic)) {
this._topicSubscribeInfoTable.set(topic, info);
}
}
/**
* whether need update
* @param {String} topic - topic
* @return {Boolean} need update?
*/
isSubscribeTopicNeedUpdate(topic) {
if (this._subscriptions && this._subscriptions.has(topic)) {
return !this._topicSubscribeInfoTable.has(topic);
}
return false;
}
/**
* rebalance
* @return {void}
*/
async doRebalance() {
for (const topic of this.subscriptions.keys()) {
await this.rebalanceByTopic(topic);
}
}
async rebalanceByTopic(topic) {
this.logger.info('[mq:consumer] rebalanceByTopic: %s, messageModel: %s', topic, this.messageModel);
const mqSet = this._topicSubscribeInfoTable.get(topic); // messageQueue list
if (!mqSet || !mqSet.length) {
this.logger.warn('[mq:consumer] doRebalance, %s, but the topic[%s] not exist.', this.consumerGroup, topic);
return;
}
let changed;
let allocateResult = mqSet;
if (this.options.isBroadcast) {
changed = await this.updateProcessQueueTableInRebalance(topic, mqSet);
} else {
const cidAll = await this._mqClient.findConsumerIdList(topic, this.consumerGroup);
this.logger.info('[mq:consumer] rebalance topic: %s, with consumer ids: %j', topic, cidAll);
if (cidAll && cidAll.length) {
// 排序
mqSet.sort(compare);
cidAll.sort();
allocateResult = this.allocateMessageQueueStrategy.allocate(this.consumerGroup, this._mqClient.clientId, mqSet, cidAll);
this.logger.info('[mq:consumer] allocate queue for group: %s, clientId: %s, result: %j', this.consumerGroup, this._mqClient.clientId, allocateResult);
changed = await this.updateProcessQueueTableInRebalance(topic, allocateResult);
}
}
if (changed) {
this.logger.info('[mq:consumer] do rebalance and message queue changed, topic: %s, mqSet: %j', topic, allocateResult);
this.emit(`topic_${topic}_queue_changed`);
}
}
/**
* update process queue
* @param {String} topic - topic
* @param {Array} mqSet - message queue set
* @return {void}
*/
async updateProcessQueueTableInRebalance(topic, mqSet) {
let changed = false;
// delete unnecessary queue
for (const key of this._processQueueTable.keys()) {
const obj = this._processQueueTable.get(key);
const messageQueue = obj.messageQueue;
const processQueue = obj.processQueue;
if (topic === messageQueue.topic) {
// not found in mqSet, that means the process queue is unnecessary.
if (!mqSet.some(mq => mq.key === messageQueue.key)) {
processQueue.droped = true;
await this.removeProcessQueue(messageQueue);
changed = true;
} else if (processQueue.isPullExpired && this.consumeType === ConsumeType.CONSUME_PASSIVELY) {
processQueue.droped = true;
await this.removeProcessQueue(messageQueue);
changed = true;
this.logger.warn('[MQPushConsumer] BUG doRebalance, %s, remove unnecessary mq=%s, because pull is pause, so try to fixed it',
this.consumerGroup, messageQueue.key);
}
}
}
for (const messageQueue of mqSet) {
if (this._processQueueTable.has(messageQueue.key)) {
continue;
}
const nextOffset = await this.computePullFromWhere(messageQueue);
// double check
if (this._processQueueTable.has(messageQueue.key)) {
continue;
}
if (nextOffset >= 0) {
const processQueue = new ProcessQueue();
changed = true;
this._processQueueTable.set(messageQueue.key, {
messageQueue,
processQueue,
nextOffset,
});
// start to pull this queue;
this.pullMessageQueue(messageQueue);
this.logger.info('[mq:consumer] doRebalance, %s, add a new messageQueue, %j, its nextOffset: %s', this.consumerGroup, messageQueue, nextOffset);
} else {
this.logger.warn('[mq:consumer] doRebalance, %s, new messageQueue, %j, has invalid nextOffset: %s', this.consumerGroup, messageQueue, nextOffset);
}
}
return changed;
}
/**
* compute consume offset
* @param {MessageQueue} messageQueue - message queue
* @return {Number} offset
*/
async computePullFromWhere(messageQueue) {
try {
const lastOffset = await this._offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE);
this.logger.info('[mq:consumer] read lastOffset => %s from store, topic="%s", brokerName="%s", queueId="%s"', lastOffset, messageQueue.topic, messageQueue.brokerName, messageQueue.queueId);
let result = -1;
switch (this.consumeFromWhere) {
case ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET_AND_FROM_MIN_WHEN_BOOT_FIRST:
case ConsumeFromWhere.CONSUME_FROM_MIN_OFFSET:
case ConsumeFromWhere.CONSUME_FROM_MAX_OFFSET:
case ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET:
// 第二次启动,根据上次的消费位点开始消费
if (lastOffset >= 0) {
result = lastOffset;
} else if (lastOffset === -1) { // 第一次启动,没有记录消费位点
// 重试队列则从队列头部开始
if (messageQueue.topic.indexOf(MixAll.RETRY_GROUP_TOPIC_PREFIX) === 0) {
result = 0;
} else { // 正常队列则从队列尾部开始
return await this._mqClient.maxOffset(messageQueue);
}
}
break;
case ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET:
// 第二次启动,根据上次的消费位点开始消费
if (lastOffset >= 0) {
result = lastOffset;
} else {
result = 0;
}
break;
case ConsumeFromWhere.CONSUME_FROM_TIMESTAMP:
// 第二次启动,根据上次的消费位点开始消费
if (lastOffset >= 0) {
result = lastOffset;
} else if (lastOffset === -1) { // 第一次启动,没有记录消费为点
// 重试队列则从队列尾部开始
if (messageQueue.topic.indexOf(MixAll.RETRY_GROUP_TOPIC_PREFIX) === 0) {
return await this._mqClient.maxOffset(messageQueue);
}
// 正常队列则从指定时间点开始
// 时间点需要参数配置
const timestamp = utils.parseDate(this.options.consumeTimestamp).getTime();
return await this._mqClient.searchOffset(messageQueue, timestamp);
}
break;
default:
break;
}
this.logger.info('[mq:consumer] computePullFromWhere() messageQueue => %s should read from offset: %s and lastOffset: %s', messageQueue.key, result, lastOffset);
return result;
} catch (err) {
err.mesasge = 'computePullFromWhere() occurred an exception, ' + err.mesasge;
this._handleError(err);
return -1;
}
}
/**
* 移除消费队列
* @param {MessageQueue} messageQueue - message queue
* @return {void}
*/
async removeProcessQueue(messageQueue) {
const processQueue = this._processQueueTable.get(messageQueue.key);
this._processQueueTable.delete(messageQueue.key);
if (processQueue) {
processQueue.droped = true;
await this.removeUnnecessaryMessageQueue(messageQueue, processQueue);
this.logger.info('[mq:consumer] remove unnecessary messageQueue, %s, Droped: %s', messageQueue.key, processQueue.droped);
}
}
/**
* remove unnecessary queue
* @param {MessageQueue} messageQueue - message queue
* @return {void}
*/
async removeUnnecessaryMessageQueue(messageQueue) {
await this._offsetStore.persist(messageQueue);
this._offsetStore.removeOffset(messageQueue);
// todo: consume later ?
}
async sendMessageBack(msg, delayLevel, brokerName, consumerGroup) {
const brokerAddr = brokerName ? this._mqClient.findBrokerAddressInPublish(brokerName)
: msg.storeHost;
const thatConsumerGroup = consumerGroup ? consumerGroup : this.consumerGroup;
try {
await this._mqClient.consumerSendMessageBack(
brokerAddr,
msg,
thatConsumerGroup,
delayLevel,
3000,
this.options.maxReconsumeTimes);
} catch (err) {
err.mesasge = 'sendMessageBack() occurred an exception, ' + thatConsumerGroup + ', ' + err.mesasge;
this._handleError(err);
let newMsg;
if (MixAll.isRetryTopic(msg.topic)) {
newMsg = msg;
} else {
newMsg = new Message(MixAll.getRetryTopic(thatConsumerGroup), '', msg.body);
newMsg.flag = msg.flag;
newMsg.properties = msg.properties;
newMsg.originMessageId = msg.originMessageId || msg.msgId;
newMsg.retryTopic = msg.topic;
newMsg.properties[MessageConst.PROPERTY_MAX_RECONSUME_TIMES] = this.options.maxReconsumeTimes;
}
newMsg.properties[MessageConst.PROPERTY_RECONSUME_TIME] = String(msg.reconsumeTimes + 1);
newMsg.delayTimeLevel = 3 + msg.reconsumeTimes;
await (await MQProducer.getDefaultProducer()).send(newMsg);
}
}
// * viewMessage(msgId) {
// const info = MessageDecoder.decodeMessageId(msgId);
// return yield this._mqClient.viewMessage(info.address, Number(info.offset.toString()), 3000);
// }
_handleError(err) {
err.message = 'MQPushConsumer occurred an error ' + err.message;
this.emit('error', err);
}
_sleep(timeout) {
return sleep(timeout);
}
}
module.exports = MQPushConsumer;
// Helper
// ------------------
function compare(mqA, mqB) {
if (mqA.topic === mqB.topic) {
if (mqA.brokerName === mqB.brokerName) {
return mqA.queueId - mqB.queueId;
}
return mqA.brokerName > mqB.brokerName ? 1 : -1;
}
return mqA.topic > mqB.topic ? 1 : -1;
}