All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alibaba.rocketmq.store.index.IndexService Maven / Gradle / Ivy

/**
 * Copyright (C) 2010-2013 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.
 */
package com.alibaba.rocketmq.store.index;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.rocketmq.common.ServiceThread;
import com.alibaba.rocketmq.common.UtilAll;
import com.alibaba.rocketmq.common.constant.LoggerName;
import com.alibaba.rocketmq.common.message.MessageConst;
import com.alibaba.rocketmq.common.sysflag.MessageSysFlag;
import com.alibaba.rocketmq.store.DefaultMessageStore;
import com.alibaba.rocketmq.store.DispatchRequest;


/**
 * 消息索引服务
 * 
 * @author shijia.wxr
 * @since 2013-7-21
 */
public class IndexService extends ServiceThread {
    private static final Logger log = LoggerFactory.getLogger(LoggerName.StoreLoggerName);
    private final DefaultMessageStore defaultMessageStore;
    // 索引配置
    private final int hashSlotNum;
    private final int indexNum;
    private final String storePath;
    // 索引文件集合
    private final ArrayList indexFileList = new ArrayList();
    // 读写锁(针对indexFileList)
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private LinkedBlockingQueue requestQueue = new LinkedBlockingQueue(300000);


    public IndexService(final DefaultMessageStore store) {
        this.defaultMessageStore = store;
        this.hashSlotNum = store.getMessageStoreConfig().getMaxHashSlotNum();
        this.indexNum = store.getMessageStoreConfig().getMaxIndexNum();
        this.storePath = store.getMessageStoreConfig().getStorePathIndex();
    }


    public boolean load(final boolean lastExitOK) {
        File dir = new File(this.storePath);
        File[] files = dir.listFiles();
        if (files != null) {
            // ascending order
            Arrays.sort(files);
            for (File file : files) {
                try {
                    IndexFile f = new IndexFile(file.getPath(), this.hashSlotNum, this.indexNum, 0, 0);
                    f.load();

                    if (!lastExitOK) {
                        if (f.getEndTimestamp() > this.defaultMessageStore.getStoreCheckpoint()
                            .getIndexMsgTimestamp()) {
                            f.destroy(0);
                            continue;
                        }
                    }

                    log.info("load index file OK, " + f.getFileName());
                    this.indexFileList.add(f);
                }
                catch (IOException e) {
                    log.error("load file " + file + " error", e);
                    return false;
                }
            }
        }

        return true;
    }


    /**
     * 删除索引文件
     */
    public void deleteExpiredFile(long offset) {
        Object[] files = null;
        try {
            this.readWriteLock.readLock().lock();
            if (this.indexFileList.isEmpty()) {
                return;
            }

            long endPhyOffset = this.indexFileList.get(0).getEndPhyOffset();
            if (endPhyOffset < offset) {
                files = this.indexFileList.toArray();
            }
        }
        catch (Exception e) {
            log.error("destroy exception", e);
        }
        finally {
            this.readWriteLock.readLock().unlock();
        }

        if (files != null) {
            List fileList = new ArrayList();
            for (int i = 0; i < (files.length - 1); i++) {
                IndexFile f = (IndexFile) files[i];
                if (f.getEndPhyOffset() < offset) {
                    fileList.add(f);
                }
                else {
                    break;
                }
            }

            this.deleteExpiredFile(fileList);
        }
    }


    /**
     * 删除文件只能从头开始删
     */
    private void deleteExpiredFile(List files) {
        if (!files.isEmpty()) {
            try {
                this.readWriteLock.writeLock().lock();
                for (IndexFile file : files) {
                    boolean destroyed = file.destroy(3000);
                    destroyed = destroyed && this.indexFileList.remove(file);
                    if (!destroyed) {
                        log.error("deleteExpiredFile remove failed.");
                        break;
                    }
                }
            }
            catch (Exception e) {
                log.error("deleteExpiredFile has exception.", e);
            }
            finally {
                this.readWriteLock.writeLock().unlock();
            }
        }
    }


    public void destroy() {
        try {
            this.readWriteLock.readLock().lock();
            for (IndexFile f : this.indexFileList) {
                f.destroy(1000 * 3);
            }
            this.indexFileList.clear();
        }
        catch (Exception e) {
            log.error("destroy exception", e);
        }
        finally {
            this.readWriteLock.readLock().unlock();
        }
    }


    public QueryOffsetResult queryOffset(String topic, String key, int maxNum, long begin, long end) {
        List phyOffsets = new ArrayList(maxNum);
        // TODO 可能需要返回给最终用户
        long indexLastUpdateTimestamp = 0;
        long indexLastUpdatePhyoffset = 0;
        maxNum = Math.min(maxNum, this.defaultMessageStore.getMessageStoreConfig().getMaxMsgsNumBatch());
        try {
            this.readWriteLock.readLock().lock();
            if (!this.indexFileList.isEmpty()) {
                for (int i = this.indexFileList.size(); i > 0; i--) {
                    IndexFile f = this.indexFileList.get(i - 1);
                    boolean lastFile = i == this.indexFileList.size();
                    if (lastFile) {
                        indexLastUpdateTimestamp = f.getEndTimestamp();
                        indexLastUpdatePhyoffset = f.getEndPhyOffset();
                    }

                    if (f.isTimeMatched(begin, end)) {
                        // 最后一个文件需要加锁
                        f.selectPhyOffset(phyOffsets, this.buildKey(topic, key), maxNum, begin, end, lastFile);
                    }

                    // 再往前遍历时间更不符合
                    if (f.getBeginTimestamp() > end) {
                        break;
                    }

                    if (phyOffsets.size() >= maxNum) {
                        break;
                    }
                }
            }
        }
        catch (Exception e) {
            log.error("queryMsg exception", e);
        }
        finally {
            this.readWriteLock.readLock().unlock();
        }

        return new QueryOffsetResult(phyOffsets, indexLastUpdateTimestamp, indexLastUpdatePhyoffset);
    }


    private String buildKey(final String topic, final String key) {
        return topic + "#" + key;
    }


    /**
     * 向队列中添加请求,队列满情况下,丢弃请求
     */
    public void putRequest(final Object[] reqs) {
        boolean offer = this.requestQueue.offer(reqs);
        if (!offer) {
            if (log.isDebugEnabled()) {
                log.debug("putRequest index failed, {}", reqs);
            }
        }
    }


    @Override
    public void run() {
        log.info(this.getServiceName() + " service started");

        while (!this.isStoped()) {
            try {
                Object[] req = this.requestQueue.poll(3000, TimeUnit.MILLISECONDS);

                if (req != null) {
                    this.buildIndex(req);
                }
            }
            catch (Exception e) {
                log.warn(this.getServiceName() + " service has exception. ", e);
            }
        }

        log.info(this.getServiceName() + " service end");
    }


    public void buildIndex(Object[] req) {
        boolean breakdown = false;
        IndexFile indexFile = retryGetAndCreateIndexFile();
        if (indexFile != null) {
            long endPhyOffset = indexFile.getEndPhyOffset();
            MSG_WHILE: for (Object o : req) {
                DispatchRequest msg = (DispatchRequest) o;
                String topic = msg.getTopic();
                String keys = msg.getKeys();
                if (msg.getCommitLogOffset() < endPhyOffset) {
                    continue;
                }

                final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag());
                switch (tranType) {
                case MessageSysFlag.TransactionNotType:
                case MessageSysFlag.TransactionPreparedType:
                    break;
                case MessageSysFlag.TransactionCommitType:
                case MessageSysFlag.TransactionRollbackType:
                    continue;
                }

                if (keys != null && keys.length() > 0) {
                    String[] keyset = keys.split(MessageConst.KEY_SEPARATOR);
                    for (String key : keyset) {
                        // TODO 是否需要TRIM
                        if (key.length() > 0) {
                            for (boolean ok =
                                    indexFile.putKey(buildKey(topic, key), msg.getCommitLogOffset(),
                                        msg.getStoreTimestamp()); !ok;) {
                                log.warn("index file full, so create another one, " + indexFile.getFileName());
                                indexFile = retryGetAndCreateIndexFile();
                                if (null == indexFile) {
                                    breakdown = true;
                                    break MSG_WHILE;
                                }

                                ok =
                                        indexFile.putKey(buildKey(topic, key), msg.getCommitLogOffset(),
                                            msg.getStoreTimestamp());
                            }
                        }
                    }
                }
            }
        }
        // IO发生故障,build索引过程中断,需要人工参与处理
        else {
            breakdown = true;
        }

        if (breakdown) {
            log.error("build index error, stop building index");
        }
    }


    public IndexFile retryGetAndCreateIndexFile() {
        IndexFile indexFile = null;

        // 如果创建失败,尝试重建3次
        for (int times = 0; null == indexFile && times < 3; times++) {
            indexFile = this.getAndCreateLastIndexFile();
            if (null != indexFile)
                break;

            try {
                log.error("try to create index file, " + times + " times");
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 重试多次,仍然无法创建索引文件
        if (null == indexFile) {
            this.defaultMessageStore.getAccessRights().makeIndexFileError();
            log.error("mark index file can not build flag");
        }

        return indexFile;
    }


    /**
     * 获取最后一个索引文件,如果集合为空或者最后一个文件写满了,则新建一个文件
* 只有一个线程调用,所以不存在写竟争问题 */ public IndexFile getAndCreateLastIndexFile() { IndexFile indexFile = null; IndexFile prevIndexFile = null; long lastUpdateEndPhyOffset = 0; long lastUpdateIndexTimestamp = 0; // 先尝试使用读锁 { this.readWriteLock.readLock().lock(); if (!this.indexFileList.isEmpty()) { IndexFile tmp = this.indexFileList.get(this.indexFileList.size() - 1); if (!tmp.isWriteFull()) { indexFile = tmp; } else { lastUpdateEndPhyOffset = tmp.getEndPhyOffset(); lastUpdateIndexTimestamp = tmp.getEndTimestamp(); prevIndexFile = tmp; } } this.readWriteLock.readLock().unlock(); } // 如果没找到,使用写锁创建文件 if (indexFile == null) { try { String fileName = this.storePath + File.separator + UtilAll.timeMillisToHumanString(System.currentTimeMillis()); indexFile = new IndexFile(fileName, this.hashSlotNum, this.indexNum, lastUpdateEndPhyOffset, lastUpdateIndexTimestamp); this.readWriteLock.writeLock().lock(); this.indexFileList.add(indexFile); } catch (Exception e) { log.error("getLastIndexFile exception ", e); } finally { this.readWriteLock.writeLock().unlock(); } // 每创建一个新文件,之前文件要刷盘 if (indexFile != null) { final IndexFile flushThisFile = prevIndexFile; Thread flushThread = new Thread(new Runnable() { @Override public void run() { IndexService.this.flush(flushThisFile); } }, "FlushIndexFileThread"); flushThread.setDaemon(true); flushThread.start(); } } return indexFile; } public void flush(final IndexFile f) { if (null == f) return; long indexMsgTimestamp = 0; if (f.isWriteFull()) { indexMsgTimestamp = f.getEndTimestamp(); } f.flush(); if (indexMsgTimestamp > 0) { this.defaultMessageStore.getStoreCheckpoint().setIndexMsgTimestamp(indexMsgTimestamp); this.defaultMessageStore.getStoreCheckpoint().flush(); } } @Override public String getServiceName() { return IndexService.class.getSimpleName(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy