/*
* (C) 2007-2012 Alibaba Group Holding Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Authors:
* wuhua <wq163@163.com> , boyan <killme2008@gmail.com>
*/
package com.taobao.metamorphosis.client.consumer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.taobao.gecko.core.util.ConcurrentHashSet;
import com.taobao.gecko.service.exception.NotifyRemotingException;
import com.taobao.metamorphosis.Message;
import com.taobao.metamorphosis.MessageAccessor;
import com.taobao.metamorphosis.cluster.Partition;
import com.taobao.metamorphosis.consumer.ConsumerMessageFilter;
import com.taobao.metamorphosis.consumer.MessageIterator;
import com.taobao.metamorphosis.exception.InvalidMessageException;
import com.taobao.metamorphosis.exception.MetaClientException;
import com.taobao.metamorphosis.utils.MetaStatLog;
import com.taobao.metamorphosis.utils.StatConstants;
/**
* ��Ϣץȡ��������ʵ��
*
* @author boyan(boyan@taobao.com)
* @date 2011-9-13
*
*/
public class SimpleFetchManager implements FetchManager {
private volatile boolean shutdown = false;
private Thread[] fetchThreads;
private FetchRequestRunner[] requestRunners;
private volatile int fetchRequestCount;
private FetchRequestQueue requestQueue;
private final ConsumerConfig consumerConfig;
private static final ThreadLocal<TopicPartitionRegInfo> currentTopicRegInfo =
new ThreadLocal<TopicPartitionRegInfo>();
private final InnerConsumer consumer;
public static final Byte PROCESSED = (byte) 1;
private final static int CACAHE_SIZE = Integer.parseInt(System.getProperty(
"metaq.consumer.message_ids.lru_cache.size", "4096"));
private static MessageIdCache messageIdCache = new ConcurrentLRUHashMap(CACAHE_SIZE);
/**
* Set new message id cache to prevent duplicated messages for the same
* consumer group.
*
* @since 1.4.6
* @param newCache
*/
public static void setMessageIdCache(MessageIdCache newCache) {
messageIdCache = newCache;
}
MessageIdCache getMessageIdCache() {
return messageIdCache;
}
public SimpleFetchManager(final ConsumerConfig consumerConfig, final InnerConsumer consumer) {
super();
this.consumerConfig = consumerConfig;
this.consumer = consumer;
}
/**
* Returns current thread processing message's TopicPartitionRegInfo.
*
* @since 1.4.6
* @return
*/
public static TopicPartitionRegInfo currentTopicRegInfo() {
return currentTopicRegInfo.get();
}
@Override
public int getFetchRequestCount() {
return this.fetchRequestCount;
}
@Override
public boolean isShutdown() {
return this.shutdown;
}
@Override
public void stopFetchRunner() throws InterruptedException {
this.shutdown = true;
this.interruptRunners();
// �ȴ������������
if (this.requestQueue != null) {
while (this.requestQueue.size() < this.fetchRequestCount) {
this.interruptRunners();
}
}
this.fetchRequestCount = 0;
}
private void interruptRunners() {
// ���������
if (this.fetchThreads != null) {
for (int i = 0; i < this.fetchThreads.length; i++) {
Thread thread = this.fetchThreads[i];
FetchRequestRunner runner = this.requestRunners[i];
if (thread != null) {
runner.shutdown();
runner.interruptExecutor();
thread.interrupt();
try {
thread.join(100);
}
catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}
@Override
public void resetFetchState() {
this.fetchRequestCount = 0;
this.requestQueue = new FetchRequestQueue();
this.fetchThreads = new Thread[this.consumerConfig.getFetchRunnerCount()];
this.requestRunners = new FetchRequestRunner[this.consumerConfig.getFetchRunnerCount()];
for (int i = 0; i < this.fetchThreads.length; i++) {
FetchRequestRunner runner = new FetchRequestRunner();
this.requestRunners[i] = runner;
this.fetchThreads[i] = new Thread(runner);
this.fetchThreads[i].setName(this.consumerConfig.getGroup() + "-fetch-Runner-" + i);
}
}
@Override
public void startFetchRunner() {
// ����������Ŀ����ֹͣ��ʱ��Ҫ���
this.fetchRequestCount = this.requestQueue.size();
this.shutdown = false;
for (final Thread thread : this.fetchThreads) {
thread.start();
}
}
@Override
public void addFetchRequest(final FetchRequest request) {
this.requestQueue.offer(request);
}
FetchRequest takeFetchRequest() throws InterruptedException {
return this.requestQueue.take();
}
static final Log log = LogFactory.getLog(SimpleFetchManager.class);
class FetchRequestRunner implements Runnable {
private static final int DELAY_NPARTS = 10;
private volatile boolean stopped = false;
void shutdown() {
this.stopped = true;
}
@Override
public void run() {
while (!this.stopped) {
try {
final FetchRequest request = SimpleFetchManager.this.requestQueue.take();
this.processRequest(request);
}
catch (final InterruptedException e) {
// take��Ӧ�жϣ�����
}
}
}
void processRequest(final FetchRequest request) {
try {
final MessageIterator iterator = SimpleFetchManager.this.consumer.fetch(request, -1, null);
final MessageListener listener =
SimpleFetchManager.this.consumer.getMessageListener(request.getTopic());
final ConsumerMessageFilter filter =
SimpleFetchManager.this.consumer.getMessageFilter(request.getTopic());
this.notifyListener(request, iterator, listener, filter, SimpleFetchManager.this.consumer
.getConsumerConfig().getGroup());
}
catch (final MetaClientException e) {
this.updateDelay(request);
this.LogAddRequest(request, e);
}
catch (final InterruptedException e) {
this.reAddFetchRequest2Queue(request);
}
catch (final Throwable e) {
this.updateDelay(request);
this.LogAddRequest(request, e);
}
}
private long lastLogNoConnectionTime;
private void LogAddRequest(final FetchRequest request, final Throwable e) {
if (e instanceof MetaClientException && e.getCause() instanceof NotifyRemotingException
&& e.getMessage().contains("��������")) {
// ���30���ӡһ��
final long now = System.currentTimeMillis();
if (this.lastLogNoConnectionTime <= 0 || now - this.lastLogNoConnectionTime > 30000) {
log.error("��ȡ��Ϣʧ��,topic=" + request.getTopic() + ",partition=" + request.getPartition(), e);
this.lastLogNoConnectionTime = now;
}
}
else {
log.error("��ȡ��Ϣʧ��,topic=" + request.getTopic() + ",partition=" + request.getPartition(), e);
}
this.reAddFetchRequest2Queue(request);
}
private void getOffsetAddRequest(final FetchRequest request, final InvalidMessageException e) {
try {
final long newOffset = SimpleFetchManager.this.consumer.offset(request);
request.resetRetries();
if (!this.stopped) {
request.setOffset(newOffset, request.getLastMessageId(), request.getPartitionObject().isAutoAck());
}
}
catch (final MetaClientException ex) {
log.error("��ѯoffsetʧ��,topic=" + request.getTopic() + ",partition=" + request.getPartition(), e);
}
finally {
this.reAddFetchRequest2Queue(request);
}
}
public void interruptExecutor() {
for (Thread thread : this.executorThreads) {
if (!thread.isInterrupted()) {
thread.interrupt();
}
}
}
private final ConcurrentHashSet<Thread> executorThreads = new ConcurrentHashSet<Thread>();
private void notifyListener(final FetchRequest request, final MessageIterator it,
final MessageListener listener, final ConsumerMessageFilter filter, final String group) {
if (listener != null) {
if (listener.getExecutor() != null) {
try {
listener.getExecutor().execute(new Runnable() {
@Override
public void run() {
Thread currentThread = Thread.currentThread();
FetchRequestRunner.this.executorThreads.add(currentThread);
try {
FetchRequestRunner.this.receiveMessages(request, it, listener, filter, group);
}
finally {
FetchRequestRunner.this.executorThreads.remove(currentThread);
}
}
});
}
catch (final RejectedExecutionException e) {
log.error(
"MessageListener�̳߳ط�æ����������Ϣ,topic=" + request.getTopic() + ",partition="
+ request.getPartition(), e);
this.reAddFetchRequest2Queue(request);
}
}
else {
this.receiveMessages(request, it, listener, filter, group);
}
}
}
private void reAddFetchRequest2Queue(final FetchRequest request) {
SimpleFetchManager.this.addFetchRequest(request);
}
/**
* ������Ϣ���������̣�<br>
* <ul>
* <li>1.�ж��Ƿ�����Ϣ���Դ������û����Ϣ���������ݵ������Դ��������ж��Ƿ���Ҫ����maxSize</li>
* <li>2.�ж���Ϣ�Ƿ����Զ�Σ���������趨����������������Ϣ���������ߡ���������Ϣ�����ڱ������Ի��߽���notify��Ͷ</li>
* <li>3.������Ϣ�������̣������Ƿ��Զ�ack��������д���:
* <ul>
* <li>(1)�����Ϣ���Զ�ack��������ѷ����쳣������offset���ӳ����ѵȴ�����</li>
* <li>(2)�����Ϣ���Զ�ack�������������������offset</li>
* <li>(3)�����Ϣ���Զ�ack���������������ack����offset��Ϊtmp offset��������tmp offset</li>
* <li>(4)�����Ϣ���Զ�ack���������������rollback��������offset������tmp offset</li>
* <li>(5)�����Ϣ���Զ�ack���������������ackҲ��rollback��������offset������tmp offset</li>
* </ul>
* </li>
* </ul>
*
* @param request
* @param it
* @param listener
*/
private void receiveMessages(final FetchRequest request, final MessageIterator it,
final MessageListener listener, final ConsumerMessageFilter filter, final String group) {
if (it != null && it.hasNext()) {
if (this.processWhenRetryTooMany(request, it)) {
return;
}
final Partition partition = request.getPartitionObject();
if (this.processReceiveMessage(request, it, listener, filter, partition, group)) {
return;
}
this.postReceiveMessage(request, it, partition);
}
else {
// ���Զ������������ȡ�����ݣ�������Ҫ����maxSize
if (SimpleFetchManager.this.isRetryTooManyForIncrease(request) && it != null && it.getDataLength() > 0) {
request.increaseMaxSize();
log.warn("���棬��" + request.getRetries() + "������ȡtopic=" + request.getTopic() + ",partition="
+ request.getPartitionObject() + "����Ϣ������maxSize=" + request.getMaxSize() + " Bytes");
}
// һ��Ҫ�ж�it�Ƿ�Ϊnull,����������������βʱ(����null)Ҳ������Retries����,�ᵼ���Ժ���������Ϣʱ����recover
if (it != null) {
request.incrementRetriesAndGet();
}
this.updateDelay(request);
this.reAddFetchRequest2Queue(request);
}
}
/**
* �����Ƿ���Ҫ���������Ĵ���
*
* @param request
* @param it
* @param listener
* @param partition
* @return
*/
private boolean processReceiveMessage(final FetchRequest request, final MessageIterator it,
final MessageListener listener, final ConsumerMessageFilter filter, final Partition partition,
final String group) {
int count = 0;
List<Long> inTransactionMsgIds = new ArrayList<Long>();
while (it.hasNext()) {
final int prevOffset = it.getOffset();
try {
final Message msg = it.next();
// If the message is processed before,don't process it
// again.
if (this.isProcessed(msg.getId(), group)) {
continue;
}
MessageAccessor.setPartition(msg, partition);
boolean accept = this.isAcceptable(request, filter, group, msg);
if (accept) {
currentTopicRegInfo.set(request.getTopicPartitionRegInfo().clone(it));
try {
listener.recieveMessages(msg);
}
finally {
currentTopicRegInfo.remove();
}
}
// rollback message if it is in rollback only state.
if (MessageAccessor.isRollbackOnly(msg)) {
it.setOffset(prevOffset);
break;
}
if (partition.isAutoAck()) {
count++;
this.markProcessed(msg.getId(), group);
}
else {
// �ύ���ع�����������ѭ��
if (partition.isAcked()) {
count++;
// mark all in transaction messages were processed.
for (Long msgId : inTransactionMsgIds) {
this.markProcessed(msgId, group);
}
this.markProcessed(msg.getId(), group);
break;
}
else if (partition.isRollback()) {
break;
}
else {
inTransactionMsgIds.add(msg.getId());
// �����ύҲ���ǻع�������������
count++;
}
}
}
catch (InterruptedException e) {
// Receive messages thread is interrupted
it.setOffset(prevOffset);
log.error("Process messages thread was interrupted,topic=" + request.getTopic() + ",partition="
+ request.getPartition(), e);
break;
}
catch (final InvalidMessageException e) {
MetaStatLog.addStat(null, StatConstants.INVALID_MSG_STAT, request.getTopic());
// ��Ϣ��Ƿ�����ȡ��Чoffset�����·����ѯ
this.getOffsetAddRequest(request, e);
return true;
}
catch (final Throwable e) {
// ��ָ���Ƶ���һ����Ϣ
it.setOffset(prevOffset);
log.error(
"Process messages failed,topic=" + request.getTopic() + ",partition=" + request.getPartition(),
e);
// ����ѭ����������Ϣ�쳣������Ϊֹ
break;
}
}
MetaStatLog.addStatValue2(null, StatConstants.GET_MSG_COUNT_STAT, request.getTopic(), count);
return false;
}
private boolean isProcessed(final Long id, String group) {
if (messageIdCache != null) {
return messageIdCache.get(this.cacheKey(id, group)) != null;
}
else {
return false;
}
}
private String cacheKey(final Long id, String group) {
return group + id;
}
private void markProcessed(final Long msgId, String group) {
if (messageIdCache != null) {
messageIdCache.put(this.cacheKey(msgId, group), PROCESSED);
}
}
private boolean isAcceptable(final FetchRequest request, final ConsumerMessageFilter filter,
final String group, final Message msg) {
if (filter == null) {
return true;
}
else {
try {
return filter.accept(group, msg);
}
catch (Exception e) {
log.error("Filter message failed,topic=" + request.getTopic() + ",group=" + group + ",filterClass="
+ filter.getClass().getCanonicalName());
// If accept throw exception,we think we can't accept
// this message.
return false;
}
}
}
private boolean processWhenRetryTooMany(final FetchRequest request, final MessageIterator it) {
if (SimpleFetchManager.this.isRetryTooMany(request)) {
try {
final Message couldNotProecssMsg = it.next();
MessageAccessor.setPartition(couldNotProecssMsg, request.getPartitionObject());
MetaStatLog.addStat(null, StatConstants.SKIP_MSG_COUNT, couldNotProecssMsg.getTopic());
SimpleFetchManager.this.consumer.appendCouldNotProcessMessage(couldNotProecssMsg);
}
catch (final InvalidMessageException e) {
MetaStatLog.addStat(null, StatConstants.INVALID_MSG_STAT, request.getTopic());
// ��Ϣ��Ƿ�����ȡ��Чoffset�����·����ѯ
this.getOffsetAddRequest(request, e);
return true;
}
catch (final Throwable t) {
this.LogAddRequest(request, t);
return true;
}
request.resetRetries();
// �����������ܴ������Ϣ
if (!this.stopped) {
request.setOffset(request.getOffset() + it.getOffset(), it.getPrevMessage().getId(), true);
}
// ǿ�������ӳ�Ϊ0
request.setDelay(0);
this.reAddFetchRequest2Queue(request);
return true;
}
else {
return false;
}
}
private void postReceiveMessage(final FetchRequest request, final MessageIterator it, final Partition partition) {
// ���offset��Ȼû��ǰ�����������Դ���
if (it.getOffset() == 0) {
request.incrementRetriesAndGet();
}
else {
request.resetRetries();
}
// ���Զ�ackģʽ
if (!partition.isAutoAck()) {
// ����ǻع�,��ع�offset���ٴη�������
if (partition.isRollback()) {
request.rollbackOffset();
partition.reset();
this.addRequst(request);
}
// ����ύ���������ʱoffset���洢
else if (partition.isAcked()) {
partition.reset();
this.ackRequest(request, it, true);
}
else {
// �����ǣ�������ʱoffset
this.ackRequest(request, it, false);
}
}
else {
// �Զ�ackģʽ
this.ackRequest(request, it, true);
}
}
private void ackRequest(final FetchRequest request, final MessageIterator it, final boolean ack) {
long msgId = it.getPrevMessage() != null ? it.getPrevMessage().getId() : -1;
request.setOffset(request.getOffset() + it.getOffset(), msgId, ack);
this.addRequst(request);
}
private void addRequst(final FetchRequest request) {
final long delay = this.getRetryDelay(request);
request.setDelay(delay);
this.reAddFetchRequest2Queue(request);
}
private long getRetryDelay(final FetchRequest request) {
final long maxDelayFetchTimeInMills = SimpleFetchManager.this.getMaxDelayFetchTimeInMills();
final long nPartsDelayTime = maxDelayFetchTimeInMills / DELAY_NPARTS;
// �ӳ�ʱ��Ϊ������ӳ�ʱ��/10*���Դ���
long delay = nPartsDelayTime * request.getRetries();
if (delay > maxDelayFetchTimeInMills) {
delay = maxDelayFetchTimeInMills;
}
return delay;
}
// ��ʱ��ѯ
private void updateDelay(final FetchRequest request) {
final long delay = this.getNextDelay(request);
request.setDelay(delay);
}
private long getNextDelay(final FetchRequest request) {
final long maxDelayFetchTimeInMills = SimpleFetchManager.this.getMaxDelayFetchTimeInMills();
// ÿ��1/10����,���MaxDelayFetchTimeInMills
final long nPartsDelayTime = maxDelayFetchTimeInMills / DELAY_NPARTS;
long delay = request.getDelay() + nPartsDelayTime;
if (delay > maxDelayFetchTimeInMills) {
delay = maxDelayFetchTimeInMills;
}
return delay;
}
}
boolean isRetryTooMany(final FetchRequest request) {
return request.getRetries() > this.consumerConfig.getMaxFetchRetries();
}
boolean isRetryTooManyForIncrease(final FetchRequest request) {
return request.getRetries() > this.consumerConfig.getMaxIncreaseFetchDataRetries();
}
long getMaxDelayFetchTimeInMills() {
return this.consumerConfig.getMaxDelayFetchTimeInMills();
}
}