/* * (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(); } }