com.alibaba.wasp.meta.FMetaScanner.java Source code

Java tutorial

Introduction

Here is the source code for com.alibaba.wasp.meta.FMetaScanner.java

Source

/**
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.wasp.meta;

import com.alibaba.wasp.EnityGroupOfflineException;
import com.alibaba.wasp.EntityGroupInfo;
import com.alibaba.wasp.FConstants;
import com.alibaba.wasp.MetaException;
import com.alibaba.wasp.ServerName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.PrefixFilter;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.PairOfSameType;

import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

/**
 * Scanner class that contains the <code>.FMETA.</code> table scanning logic and
 * uses a Retryable scanner. Provided visitors will be called for each row.
 * 
 * Although public visibility, this is not a public-facing API and may evolve in
 * minor releases.
 * 
 * <p>
 * Note that during concurrent EG splits, the scanner might not see META changes
 * across rows (for parent and daughter entries) consistently. see {@link BlockingMetaScannerVisitor} for details.
 * </p>
 */
public class FMetaScanner extends AbstractMetaService {

    private static final Log LOG = LogFactory.getLog(FMetaScanner.class);

    /**
     * Scans the meta table and calls a visitor on each RowResult and uses a empty
     * start row value as table name.
     *
     * @param configuration
     *          conf
     * @param visitor
     *          A custom visitor
     * @throws java.io.IOException
     *           e
     */
    public static void metaScan(Configuration configuration, MetaScannerVisitor visitor) throws IOException {
        metaScan(configuration, visitor, null);
    }

    /**
     * Need to add comments.
     *
     * @param conf
     * @param entityGroupName
     * @return
     * @throws java.io.IOException
     */
    public static Pair<EntityGroupInfo, ServerName> getEntityGroup(Configuration conf, byte[] entityGroupName)
            throws IOException {
        return getService(conf).getEntityGroupAndLocation(entityGroupName);
    }

    /**
     * Scans the meta table and calls a visitor on each RowResult. Uses a table
     * name to locate meta entitGroups.
     *
     * @param configuration
     *          config
     * @param visitor
     *          visitor object
     * @param userTableName
     *          User table name in meta table to start scan at. Pass null if not
     *          interested in a particular table.
     * @throws java.io.IOException
     *           e
     */
    public static void metaScan(Configuration configuration, MetaScannerVisitor visitor, byte[] userTableName)
            throws IOException {
        metaScan(configuration, visitor, userTableName, null, Integer.MAX_VALUE);
    }

    public static void metaScan(Configuration configuration, MetaScannerVisitor visitor, byte[] tableName,
            byte[] row, int rowLimit) throws IOException {
        int rowUpperLimit = rowLimit > 0 ? rowLimit : Integer.MAX_VALUE;

        // if row is not null, we want to use the startKey of the row's entityGroup
        // as
        // the startRow for the meta scan.
        byte[] startRow = tableName;
        if (tableName == null || tableName.length == 0) {
            // Full META scan
            startRow = FConstants.EMPTY_START_ROW;
        }
        String metaTableString = configuration.get(FConstants.METASTORE_TABLE, FConstants.DEFAULT_METASTORE_TABLE);
        HTable fmetaTable = new HTable(configuration, metaTableString);
        // Scan over each meta entityGroup
        int rows = Math.min(rowLimit, configuration.getInt(HConstants.HBASE_META_SCANNER_CACHING,
                HConstants.DEFAULT_HBASE_META_SCANNER_CACHING));

        final Scan scan = new Scan(startRow);
        if (LOG.isDebugEnabled()) {
            LOG.debug("Scanning " + metaTableString + " starting at row=" + Bytes.toStringBinary(startRow)
                    + " for max=" + rowUpperLimit + " rows ");
        }
        scan.addFamily(FConstants.CATALOG_FAMILY);
        scan.setCaching(rows);
        if (tableName == null || tableName.length == 0) {
            FilterList allFilters = new FilterList();
            allFilters.addFilter(new PrefixFilter(tableName));
            scan.setFilter(allFilters);
        }
        int processedRows = 0;
        try {
            ResultScanner scanner = fmetaTable.getScanner(scan);
            for (Result r = scanner.next(); r != null; r = scanner.next()) {
                if (processedRows >= rowUpperLimit) {
                    break;
                }
                if (Bytes.startsWith(r.getRow(), FConstants.TABLEROW_PREFIX)) {
                    continue;
                }
                if (!visitor.processRow(r))
                    break; // exit completely
                processedRows++;
            }
            // here, we didn't break anywhere. Check if we have more rows
        } finally {
            // Close scanner
            fmetaTable.close();
        }
    }

    /**
     * Returns EntityGroupInfo object from the column
     * FConstants.CATALOG_FAMILY:FConstants.EGINFO of the catalog table Result.
     *
     * @param data a Result object from the catalog table scan
     * @return EntityGroupInfo or null
     */
    public static EntityGroupInfo getEntityGroupInfo(Result data) {
        byte[] bytes = data.getValue(FConstants.CATALOG_FAMILY, FConstants.EGINFO);
        if (bytes == null)
            return null;
        EntityGroupInfo info = EntityGroupInfo.parseFromOrNull(bytes);
        if (LOG.isDebugEnabled()) {
            LOG.debug("Current INFO from scan results = " + info);
        }
        return info;
    }

    /**
     * Lists all of the EntityGroups currently in META.
     *
     * @param conf
     * @return List of all user-space EntityGroups.
     * @throws java.io.IOException
     */
    public static List<EntityGroupInfo> listAllEntityGroups(Configuration conf) throws IOException {
        return listAllEntityGroups(conf, true);
    }

    /**
     * Lists all of the EntityGroups currently in META.
     *
     * @param conf
     * @param offlined
     *          True if we are to include offlined EntityGroups, false and we'll
     *          leave out offlined EntityGroups from returned list.
     * @return List of all user-space EntityGroups.
     * @throws java.io.IOException
     */
    public static List<EntityGroupInfo> listAllEntityGroups(Configuration conf, final boolean offlined)
            throws IOException {
        final List<EntityGroupInfo> entityGroups = new ArrayList<EntityGroupInfo>();
        MetaScannerVisitor visitor = new BlockingMetaScannerVisitor(conf) {
            @Override
            public boolean processRowInternal(Result result) throws IOException {
                if (result == null || result.isEmpty()) {
                    return true;
                }

                EntityGroupInfo entityGroupInfo = getEntityGroupInfo(result);
                if (entityGroupInfo == null) {
                    LOG.warn("Null ENTITYGROUPINFO_QUALIFIER: " + result);
                    return true;
                }

                // If entityGroup offline AND we are not to include offlined entityGroups,
                // return.
                if (entityGroupInfo.isOffline() && !offlined)
                    return true;
                entityGroups.add(entityGroupInfo);
                return true;
            }
        };
        metaScan(conf, visitor);
        return entityGroups;
    }

    /**
     * Get root table name
     *
     * @param FTable
     * @return
     */
    public static String getRootTable(FTable table) {
        if (table.isRootTable()) {
            return table.getTableName();
        } else {
            return table.getParentName();
        }
    }

    /**
     * Lists all of the table EntityGroups currently in META.
     *
     * @param conf
     * @param offlined
     *          True if we are to include offlined EntityGroups, false and we'll
     *          leave out offlined EntityGroups from returned list.
     * @return Map of all user-space EntityGroups to servers
     * @throws java.io.IOException
     */
    public static NavigableMap<EntityGroupInfo, ServerName> allTableEntityGroups(Configuration conf,
            final byte[] tablename, final boolean offlined) throws IOException {
        FTable table = getService(conf).getTable(Bytes.toString(tablename));
        final byte[] parentTableName = Bytes.toBytes(getRootTable(table));
        final NavigableMap<EntityGroupInfo, ServerName> entityGroups = new TreeMap<EntityGroupInfo, ServerName>();
        MetaScannerVisitor visitor = new TableMetaScannerVisitor(conf, parentTableName) {
            @Override
            public boolean processRowInternal(Result rowResult) throws IOException {
                EntityGroupInfo entityGroupInfo = getEntityGroupInfo(rowResult);
                ServerName sn = ServerName.getServerName(rowResult);

                if (!(entityGroupInfo.isOffline() || entityGroupInfo.isSplit())) {
                    entityGroups.put(entityGroupInfo, sn);
                }
                return true;
            }
        };
        metaScan(conf, visitor, parentTableName);
        return entityGroups;
    }

    /**
     * Visitor class called to process each row of the .META. table
     */
    public interface MetaScannerVisitor extends Closeable {
        /**
         * Visitor method that accepts a RowResult and the meta entityGroup location.
         * Implementations can return false to stop the entityGroup's loop if it
         * becomes unnecessary for some reason.
         *
         * @param rowResult
         *          result
         * @return A boolean to know if it should continue to loop in the
         *         entityGroup
         * @throws java.io.IOException
         *           e
         */
        public boolean processRow(Result rowResult) throws IOException;
    }

    public static abstract class MetaScannerVisitorBase implements MetaScannerVisitor {
        @Override
        public void close() throws IOException {
        }
    }

    /**
     * A MetaScannerVisitor that provides a consistent view of the table's META
     * entries during concurrent splits (see HBASE-5986 for details). This class
     * does not guarantee ordered traversal of meta entries, and can block until
     * the META entries for daughters are available during splits.
     */
    public static abstract class BlockingMetaScannerVisitor extends MetaScannerVisitorBase {

        private static final int DEFAULT_BLOCKING_TIMEOUT = 10000;
        private Configuration conf;
        private TreeSet<byte[]> daughterEntityGroups = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
        private int blockingTimeout;
        private HTable fmetaTable;

        public BlockingMetaScannerVisitor(Configuration conf) {
            this.conf = conf;
            this.blockingTimeout = conf.getInt(HConstants.HBASE_CLIENT_OPERATION_TIMEOUT, DEFAULT_BLOCKING_TIMEOUT);
        }

        public abstract boolean processRowInternal(Result rowResult) throws IOException;

        @Override
        public void close() throws IOException {
            super.close();
            if (fmetaTable != null) {
                fmetaTable.close();
                fmetaTable = null;
            }
        }

        public HTable getFMetaTable() throws IOException {
            if (fmetaTable == null) {
                String metaTableString = conf.get(FConstants.METASTORE_TABLE, FConstants.DEFAULT_METASTORE_TABLE);
                fmetaTable = new HTable(conf, metaTableString);
            }
            return fmetaTable;
        }

        @Override
        public boolean processRow(Result rowResult) throws IOException {
            EntityGroupInfo info = getEntityGroupInfo(rowResult);
            if (info == null) {
                return true;
            }

            if (daughterEntityGroups.remove(info.getEntityGroupName())) {
                return true; // we have already processed this row
            }

            if (info.isSplitParent()) {
                /*
                 * we have found a parent entityGroup which was split. We have to ensure
                 * that it's daughters are seen by this scanner as well, so we block
                 * until they are added to the META table. Even though we are waiting
                 * for META entries, ACID semantics in HBase indicates that this scanner
                 * might not see the new rows. So we manually query the daughter rows
                 */
                PairOfSameType<EntityGroupInfo> daughters = EntityGroupInfo.getDaughterEntityGroups(rowResult);
                EntityGroupInfo splitA = daughters.getFirst();
                EntityGroupInfo splitB = daughters.getSecond();

                HTable fmetaTable = getFMetaTable();
                long start = System.currentTimeMillis();
                Result resultA = getEntityGroupResultBlocking(fmetaTable, blockingTimeout,
                        splitA.getEntityGroupName());
                if (resultA != null) {
                    processRow(resultA);
                    daughterEntityGroups.add(splitA.getEntityGroupName());
                } else {
                    throw new EnityGroupOfflineException("Split daughter entityGroup "
                            + splitA.getEntityGroupNameAsString() + " cannot be found in META.");
                }
                long rem = blockingTimeout - (System.currentTimeMillis() - start);

                Result resultB = getEntityGroupResultBlocking(fmetaTable, rem, splitB.getEntityGroupName());
                if (resultB != null) {
                    processRow(resultB);
                    daughterEntityGroups.add(splitB.getEntityGroupName());
                } else {
                    throw new EnityGroupOfflineException("Split daughter entityGroup "
                            + splitB.getEntityGroupNameAsString() + " cannot be found in META.");
                }
            }

            return processRowInternal(rowResult);
        }

        private Result getEntityGroupResultBlocking(HTable fmetaTable, long timeout, byte[] entityGroupName)
                throws IOException {
            if (LOG.isDebugEnabled()) {
                LOG.debug("blocking until entityGroup is in META: " + Bytes.toStringBinary(entityGroupName));
            }
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start < timeout) {
                Get get = new Get(entityGroupName);
                Result result = fmetaTable.get(get);
                EntityGroupInfo entityGroupInfo = getEntityGroupInfo(result);
                if (entityGroupInfo != null) {
                    return result;
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
            return null;
        }
    }

    /**
     * A MetaScannerVisitor for a table. Provides a consistent view of the table's
     * META entries during concurrent splits (see HBASE-5986 for details). This
     * class does not guarantee ordered traversal of meta entries, and can block
     * until the META entries for daughters are available during splits.
     */
    public static abstract class TableMetaScannerVisitor extends BlockingMetaScannerVisitor {
        private byte[] tableName;

        public TableMetaScannerVisitor(Configuration conf, byte[] tableName) {
            super(conf);
            this.tableName = tableName;
        }

        @Override
        public final boolean processRow(Result rowResult) throws IOException {
            EntityGroupInfo entityGroupInfo = getEntityGroupInfo(rowResult);
            if (entityGroupInfo == null) {
                return true;
            }
            if (!(Bytes.equals(entityGroupInfo.getTableName(), tableName))) {
                return false;
            }
            return super.processRow(rowResult);
        }

    }

    /**
     *
     * @param conf
     * @param disabledOrDisablingOrEnabling
     * @param excludeOfflinedSplitParents
     * @return
     * @throws com.alibaba.wasp.MetaException
     */
    public static Map<EntityGroupInfo, ServerName> fullScan(Configuration conf,
            Set<String> disabledOrDisablingOrEnabling, boolean excludeOfflinedSplitParents) throws MetaException {
        return getService(conf).fullScan(disabledOrDisablingOrEnabling, excludeOfflinedSplitParents);
    }

    /**
     *
     * @param conf
     * @return
     * @throws com.alibaba.wasp.MetaException
     */
    public static List<Result> fullScan(Configuration conf) throws MetaException {
        return getService(conf).fullScan();
    }

    /**
     *
     * @param conf
     * @param visitor
     * @param startrow
     * @param endrow
     * @throws com.alibaba.wasp.MetaException
     */
    public static void fullScan(final Configuration conf, final FMetaVisitor visitor, final byte[] startrow,
            final byte[] endrow) throws MetaException {
        getService(conf).fullScan(visitor, startrow, endrow);
    }
}