/* * (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.http; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.taobao.gecko.core.util.StringUtils; import com.taobao.metamorphosis.Message; import com.taobao.metamorphosis.client.MetaClientConfig; import com.taobao.metamorphosis.client.Shutdownable; import com.taobao.metamorphosis.client.consumer.FetchManager; import com.taobao.metamorphosis.client.consumer.FetchRequest; import com.taobao.metamorphosis.client.consumer.InnerConsumer; import com.taobao.metamorphosis.client.consumer.MessageIterator; import com.taobao.metamorphosis.client.consumer.MessageListener; import com.taobao.metamorphosis.client.consumer.RecoverStorageManager; import com.taobao.metamorphosis.client.consumer.SimpleFetchManager; import com.taobao.metamorphosis.client.consumer.SubscribeInfoManager; import com.taobao.metamorphosis.client.consumer.TopicPartitionRegInfo; import com.taobao.metamorphosis.client.consumer.storage.MysqlOffsetStorage; import com.taobao.metamorphosis.client.consumer.storage.OffsetStorage; import com.taobao.metamorphosis.cluster.Partition; import com.taobao.metamorphosis.exception.MetaClientException; import com.taobao.metamorphosis.network.HttpStatus; import com.taobao.metamorphosis.utils.MetaStatLog; import com.taobao.metamorphosis.utils.StatConstants; /** * ʵ����SimpleMessageConsumerһ�£����˷�������ʹ��HTTPЭ���Լ�client�Լ�ָ������ľ�̬LB��ʽ * */ public class SimpleHttpConsumer extends SimpleHttpClient implements Shutdownable, InnerConsumer { static final Log logger = LogFactory.getLog(SimpleHttpConsumer.class); private final HttpClientConfig config; private final ScheduledExecutorService scheduledExecutorService; private final OffsetStorage offsetStorage; private final SubscribeInfoManager subscribeInfoManager; private final RecoverStorageManager recoverStorageManager; private final FetchManager fetchManager; /** * ���ĵ�topic��Ӧ��partition,offset����Ϣ, * ����HTTP�ͻ���û��rerebalance����completeSubscribe֮���û�в���Ķ�; * �����¼��TopicPartitionRegInfo֮offset����ʱ��ı�������������˱����Ѿ���Thread Safe */ private final HashMap<String, HashMap<Partition, TopicPartitionRegInfo>> topicRegistry = new HashMap<String, HashMap<Partition, TopicPartitionRegInfo>>(); public SimpleHttpConsumer(final HttpClientConfig config) { super(config); this.config = config; this.subscribeInfoManager = new SubscribeInfoManager(); this.offsetStorage = new MysqlOffsetStorage(config.getDataSource()); this.recoverStorageManager = new RecoverStorageManager(new MetaClientConfig(), this.subscribeInfoManager); this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { SimpleHttpConsumer.this.commitOffsets(); } }, config.getCommitOffsetPeriodInMills(), config.getCommitOffsetPeriodInMills(), TimeUnit.MILLISECONDS); this.fetchManager = new SimpleFetchManager(config, this); this.fetchManager.resetFetchState(); } private void commitOffsets() { this.offsetStorage.commitOffset(this.config.getGroup(), this.getTopicPartitionRegInfos()); } private List<TopicPartitionRegInfo> getTopicPartitionRegInfos() { final List<TopicPartitionRegInfo> rt = new ArrayList<TopicPartitionRegInfo>(); for (final HashMap<Partition, TopicPartitionRegInfo> subMap : this.topicRegistry.values()) { final Collection<TopicPartitionRegInfo> values = subMap.values(); if (values != null) { rt.addAll(values); } } return rt; } /** * ��ȡָ��topic�ͷ����������Ϣ * * @throws MetaClientException */ public MessageIterator get(final String topic, final Partition partition, final long offset, final int maxsize) throws MetaClientException, InterruptedException { this.checkRequest(topic, partition, offset, maxsize); final FetchRequest request = new FetchRequest(null, 0, new TopicPartitionRegInfo(topic, partition, offset), maxsize); return this.fetch(request, 10000, TimeUnit.MILLISECONDS); } final private void checkRequest(final String topic, final Partition partition, final long offset, final int maxsize) throws MetaClientException { if (topic == null || topic.isEmpty()) { throw new MetaClientException("Topic null"); } if (offset < 0) { throw new MetaClientException("Offset must be positive"); } if (partition == null) { throw new MetaClientException("partition must be positive"); } if (maxsize < 0) { throw new MetaClientException("maxsize must be positive"); } } /** * * ����ָ������Ϣ������MessageListener��������Ϣ�ﵽ��ʱ������֪ͨMessageListener����ע�⣬ * ���ô˷���������ʹ���Ĺ�ϵ������Ч�� ֻ���ڵ���complete���������Ч * */ public void subscribe(final String topic, final Partition partition, final int maxSize, final MessageListener messageListener) throws MetaClientException { this.checkState(); if (StringUtils.isBlank(topic)) { throw new IllegalArgumentException("Blank topic"); } if (messageListener == null) { throw new IllegalArgumentException("Null messageListener"); } // add it to group manager this.subscribeInfoManager.subscribe(topic, this.config.getGroup(), maxSize, messageListener); HashMap<Partition, TopicPartitionRegInfo> partitionTopicInfo = this.topicRegistry.get(topic); if (partitionTopicInfo == null) { partitionTopicInfo = new HashMap<Partition, TopicPartitionRegInfo>(); this.topicRegistry.put(topic, partitionTopicInfo); } TopicPartitionRegInfo topicPartitionRegInfo = this.offsetStorage.load(topic, this.config.getGroup(), partition); if (topicPartitionRegInfo == null) { // ��ʼ����ʱ��Ĭ��ʹ��0,TODO ���ܲ������� this.offsetStorage.initOffset(topic, this.config.getGroup(), partition, 0);// Long.MAX_VALUE topicPartitionRegInfo = new TopicPartitionRegInfo(topic, partition, 0); } partitionTopicInfo.put(partition, topicPartitionRegInfo); this.fetchManager.addFetchRequest(new FetchRequest(null, 0L, topicPartitionRegInfo, maxSize)); } /** * ʹ���Ѿ����ĵ�topic��Ч,�˷������ܵ���һ��,�ٴε�����Ч�����׳��쳣 */ public void completeSubscribe() throws MetaClientException { this.checkState(); // this.fetchManager.resetFetchState(); this.fetchManager.startFetchRunner(); } @Override public void shutdown() throws MetaClientException { super.shutdown(); if (this.fetchManager.isShutdown()) { return; } try { this.fetchManager.stopFetchRunner(); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } finally { this.scheduledExecutorService.shutdownNow(); // �ر�ǰ�ύoffsets this.commitOffsets(); this.offsetStorage.close(); } } @Override public MessageIterator fetch(final FetchRequest fetchRequest, final long timeout, final TimeUnit timeUnit) throws MetaClientException, InterruptedException { final long start = System.currentTimeMillis(); final boolean success = false; // URI�ķ����/get/{brokerID}?topic&partition&group, {brokerID}����HTTP // LB����ΪHTTP client��֪����Ψһ��ڣ�ѡ���Ӧ��Broker server // �� HTTP LB����HAProxy���������brokerID·�ɵ���Ӧ��Borker server final String uri = "/get/" + fetchRequest.getPartitionObject().getBrokerId() + "?topic=" + fetchRequest.getTopic() + "&partition=" + fetchRequest.getPartition() + "&offset=" + fetchRequest.getOffset() + "&group=" + this.config.getGroup() + "&maxsize=" + fetchRequest.getMaxSize(); final GetMethod httpget = new GetMethod(uri); try { this.httpclient.executeMethod(httpget); if (httpget.getStatusCode() == HttpStatus.Success) { return new MessageIterator(fetchRequest.getTopic(), httpget.getResponseBody()); } else if (httpget.getStatusCode() == HttpStatus.NotFound) { return null; } else { throw new MetaClientException(httpget.getResponseBodyAsString()); } } catch (final HttpException e) { logger.error(e.getMessage(), e); throw new MetaClientException(e); } catch (final IOException e) { logger.error(e.getMessage(), e); throw new MetaClientException(e); } finally { httpget.releaseConnection(); final long duration = System.currentTimeMillis() - start; if (duration > 200) { MetaStatLog.addStatValue2(null, StatConstants.GET_TIME_STAT, fetchRequest.getTopic(), duration); } if (!success) { MetaStatLog.addStat(null, StatConstants.GET_FAILED_STAT, fetchRequest.getTopic()); } } } private void checkState() { if (this.fetchManager.isShutdown()) { throw new IllegalStateException("Consumer has been shutdown"); } } @Override public MessageListener getMessageListener(final String topic) { try { return this.subscribeInfoManager.getMessageListener(topic, this.config.getGroup()); } catch (final MetaClientException e) { throw new RuntimeException(e); } } @Override public void appendCouldNotProcessMessage(final Message message) throws IOException { // Ŀǰ�Ĵ����ǽ������ش洢�������� this.recoverStorageManager.append(this.config.getGroup(), message); } @Override public long offset(final FetchRequest fetchRequest) throws MetaClientException { final long start = System.currentTimeMillis(); final boolean success = false; // URI�ķ����/offset/{brokerID}?topic&partition&group, {brokerID}����HTTP // LB����ΪHTTP client��֪����Ψһ��ڣ�ѡ���Ӧ��Broker server final String uri = "/offset/" + fetchRequest.getPartitionObject().getBrokerId() + "?topic=" + fetchRequest.getTopic() + "&partition=" + fetchRequest.getPartition() + "&offset=" + fetchRequest.getOffset(); final GetMethod httpget = new GetMethod(uri); try { this.httpclient.executeMethod(httpget); if (httpget.getStatusCode() == HttpStatus.Success) { return Long.parseLong(httpget.getResponseBodyAsString()); } else { throw new MetaClientException(httpget.getResponseBodyAsString()); } } catch (final HttpException e) { logger.error(e.getMessage(), e); throw new MetaClientException(e); } catch (final IOException e) { logger.error(e.getMessage(), e); throw new MetaClientException(e); } finally { httpget.releaseConnection(); final long duration = System.currentTimeMillis() - start; if (duration > 200) { MetaStatLog.addStatValue2(null, StatConstants.GET_TIME_STAT, fetchRequest.getTopic(), duration); } if (!success) { MetaStatLog.addStat(null, StatConstants.GET_FAILED_STAT, fetchRequest.getTopic()); } } } }