/* * (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.extension.producer; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.taobao.common.store.Store; import com.taobao.common.store.journal.JournalStore; import com.taobao.metamorphosis.Message; import com.taobao.metamorphosis.client.MetaClientConfig; import com.taobao.metamorphosis.cluster.Partition; import com.taobao.metamorphosis.exception.GetRecoverStorageErrorException; import com.taobao.metamorphosis.exception.UnknowCodecTypeException; import com.taobao.metamorphosis.utils.IdWorker; import com.taobao.metamorphosis.utils.NamedThreadFactory; import com.taobao.metamorphosis.utils.codec.Deserializer; import com.taobao.metamorphosis.utils.codec.Serializer; import com.taobao.metamorphosis.utils.codec.impl.Hessian1Deserializer; import com.taobao.metamorphosis.utils.codec.impl.Hessian1Serializer; import com.taobao.metamorphosis.utils.codec.impl.JavaDeserializer; import com.taobao.metamorphosis.utils.codec.impl.JavaSerializer; /** * ��Ϣ�����ڱ��ش���,�����ڻ��ֶ�recover * * @author �޻� * @since 2011-8-8 ����10:40:56 */ public class LocalMessageStorageManager implements MessageRecoverManager { protected static final String SPLIT = "@"; /** * ��ʾ��topic@partitionΪ��λ��store map */ protected final ConcurrentHashMap<String/* topic@partition */, FutureTask<Store>> topicStoreMap = new ConcurrentHashMap<String, FutureTask<Store>>(); /** * ��ʾ����ִ�еĻָ������map */ protected final ConcurrentHashMap<String/* topic@partition */, FutureTask<Boolean>> topicRecoverTaskMap = new ConcurrentHashMap<String, FutureTask<Boolean>>(); private final Serializer serializer; protected final Deserializer deserializer; static final Log log = LogFactory.getLog(LocalMessageStorageManager.class); private final IdWorker idWorker = new IdWorker(0); public static final String DEFAULT_META_LOCALMESSAGE_PATH = System.getProperty("meta.localmessage.path", System.getProperty("user.home") + File.separator + ".meta_localmessage"); public String META_LOCALMESSAGE_PATH; private final String META_LOCALMESSAGE_CODEC_TYPE = System.getProperty("meta.localmessage.codec", "java"); // �ָ���Ϣ���̳߳� protected final ThreadPoolExecutor threadPoolExecutor; private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); public LocalMessageStorageManager(final MetaClientConfig metaClientConfig) { this(metaClientConfig, DEFAULT_META_LOCALMESSAGE_PATH, null); } public LocalMessageStorageManager(final MetaClientConfig metaClientConfig, final String path, final MessageRecoverer messageRecoverer) { super(); this.META_LOCALMESSAGE_PATH = StringUtils.isNotBlank(path) ? path : DEFAULT_META_LOCALMESSAGE_PATH; this.messageRecoverer = messageRecoverer; if (this.META_LOCALMESSAGE_CODEC_TYPE.equals("java")) { this.serializer = new JavaSerializer(); this.deserializer = new JavaDeserializer(); } else if (this.META_LOCALMESSAGE_CODEC_TYPE.equals("hessian1")) { this.serializer = new Hessian1Serializer(); this.deserializer = new Hessian1Deserializer(); } else { throw new UnknowCodecTypeException(this.META_LOCALMESSAGE_CODEC_TYPE); } // ��������Ϣ��RecoverThreadCountһ�� this.threadPoolExecutor = new ThreadPoolExecutor(metaClientConfig.getRecoverThreadCount(), metaClientConfig.getRecoverThreadCount(), 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>( 100), new NamedThreadFactory("SendRecover-thread"), new ThreadPoolExecutor.CallerRunsPolicy()); this.makeDataDir(); this.loadStores(); // ��ʱȥ���Իָ�һ������,����ij��topic��ʱ��û����Ϣʱ���ػ������Ϣһֱ����ѹ�ڱ��� this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { log.info("��ʼ���Է��ͱ��ػ������Ϣ..."); LocalMessageStorageManager.this.recover(); } }, 0, metaClientConfig.getRecoverMessageIntervalInMills(), TimeUnit.MILLISECONDS); } private void loadStores() { final File dataPath = new File(this.META_LOCALMESSAGE_PATH); final File[] files = dataPath.listFiles(); for (final File subFile : files) { if (subFile.isDirectory()) { final String name = subFile.getName(); final String[] tmps = name.split(SPLIT); if (tmps.length != 2) { continue; } log.info("����local message storage " + name + " ..."); this.getOrCreateStore(tmps[0], new Partition(tmps[1])); } } } @Override public void recover() { final Set<String> names = this.topicStoreMap.keySet(); if (names == null || names.size() == 0) { log.info("SendRecoverû����Ҫ�ָ�����Ϣ"); return; } if (this.messageRecoverer != null) { for (final String name : names) { final String[] tmps = name.split(SPLIT); final String topic = tmps[0]; final Partition partition = new Partition(tmps[1]); final int count = this.getMessageCount(topic, partition); log.info(name + "��Ҫ�ָ�������:" + count); if (count > 0) { if (!this.recover(topic, partition, this.messageRecoverer)) { log.info("SendRecover���ͻָ�������������,����Ҫ��������,name=" + name); } } } } else { log.warn("messageRecoverer��δ����"); } } /** * �����ָ�һ������һ����������Ϣ,�ɶ�ε���(��֤��ij����Ļָ��������ֻ��һ��������) * * @param topic * @param partition * @param recoverer * �ָ���������Ϣ�Ĵ����� * @return �Ƿ������ύ�˻ָ����� * */ @Override public boolean recover(final String topic, final Partition partition, final MessageRecoverer recoverer) { final String name = this.generateKey(topic, partition); final FutureTask<Boolean> recoverTask = new FutureTask<Boolean>(new Callable<Boolean>() { @Override public Boolean call() throws Exception { final AtomicLong count = new AtomicLong(0); try { final Store store = LocalMessageStorageManager.this.getOrCreateStore(topic, partition); this.innerRecover(store, recoverer, count, name); } catch (final Throwable e) { log.error("SendRecover������Ϣ�ָ�ʧ��,name=" + name, e); } finally { log.info("SendRecoverִ������Ƴ����ͻָ�����,name=" + name + ",�ָ���Ϣ" + count.get() + "��"); LocalMessageStorageManager.this.topicRecoverTaskMap.remove(name); } return true; } private void innerRecover(final Store store, final MessageRecoverer recoverer, final AtomicLong count, final String name) throws IOException, Exception { final Iterator<byte[]> it = store.iterator(); while (it.hasNext()) { final byte[] key = it.next(); final Message msg = (Message) LocalMessageStorageManager.this.deserializer.decodeObject(store.get(key)); recoverer.handle(msg); try { store.remove(key); count.incrementAndGet(); if (count.get() % 20000 == 0) { log.info("SendRecover " + name + "�ѻָ���Ϣ����:" + count.get()); } } catch (final IOException e) { log.error("SendRecover remove message failed", e); } } } }); final FutureTask<Boolean> ret = this.topicRecoverTaskMap.putIfAbsent(name, recoverTask); if (ret == null) { this.threadPoolExecutor.submit(recoverTask); return true; } else { if (log.isDebugEnabled()) { log.debug("SendRecover���ͻָ�������������,����Ҫ��������,name=" + name); } return false; } } protected Store getOrCreateStore(final String topic, final Partition partition) { // Ϊtopic�ȴ���һ��RandomPartiton������store this.getOrCreateStore0(topic, Partition.RandomPartiton); return this.getOrCreateStore0(topic, partition); } private Store getOrCreateStore0(final String topic, final Partition partition) { final String name = this.generateKey(topic, partition); FutureTask<Store> task = this.topicStoreMap.get(name); if (task != null) { return this.getStore(name, task); } else { task = new FutureTask<Store>(new Callable<Store>() { @Override public Store call() throws Exception { final File file = new File(LocalMessageStorageManager.this.META_LOCALMESSAGE_PATH + File.separator + name); if (!file.exists()) { file.mkdir(); } return this.newStore(name); } private Store newStore(final String name) throws IOException { return LocalMessageStorageManager.this.newStore(name); } }); FutureTask<Store> existsTask = this.topicStoreMap.putIfAbsent(name, task); if (existsTask == null) { task.run(); existsTask = task; } return this.getStore(name, existsTask); } } private Store getStore(final String topic, final FutureTask<Store> task) { try { return task.get(); } catch (final Throwable t) { log.error("��ȡtopic=" + topic + "��Ӧ��storeʧ��", t); throw new GetRecoverStorageErrorException("��ȡtopic=" + topic + "��Ӧ��storeʧ��", t); } } private void makeDataDir() { final File file = new File(this.META_LOCALMESSAGE_PATH); if (!file.exists()) { file.mkdir(); } } @Override public void shutdown() { for (final Map.Entry<String, FutureTask<Store>> entry : this.topicStoreMap.entrySet()) { final String name = entry.getKey(); final FutureTask<Store> task = entry.getValue(); final Store store = this.getStore(name, task); try { store.close(); } catch (final IOException e) { // ignore } } } @Override public void append(final Message message, final Partition partition) throws IOException { final Store store = this.getOrCreateStore(message.getTopic(), partition); final ByteBuffer buf = ByteBuffer.allocate(16); buf.putLong(this.idWorker.nextId()); store.add(buf.array(), this.serializer.encodeObject(message)); } @Override public int getMessageCount(final String topic, final Partition partition) { final String name = this.generateKey(topic, partition); final FutureTask<Store> task = this.topicStoreMap.get(name); if (task != null) { return this.getStore(name, task).size(); } else { return 0; } } protected MessageRecoverer messageRecoverer; protected String generateKey(final String topic, final Partition partition) { return topic + SPLIT + partition; } @Override synchronized public void setMessageRecoverer(final MessageRecoverer recoverer) { if (this.messageRecoverer == null) { this.messageRecoverer = recoverer; } } protected Store newStore(final String name) throws IOException { return new JournalStore(this.META_LOCALMESSAGE_PATH + File.separator + name, name); } }