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

org.cloudgraph.hbase.mapreduce.GraphInputFormat Maven / Gradle / Ivy

/**
 *        CloudGraph Community Edition (CE) License
 * 
 * This is a community release of CloudGraph, a dual-license suite of
 * Service Data Object (SDO) 2.1 services designed for relational and 
 * big-table style "cloud" databases, such as HBase and others. 
 * This particular copy of the software is released under the 
 * version 2 of the GNU General Public License. CloudGraph was developed by 
 * TerraMeta Software, Inc.
 * 
 * Copyright (c) 2013, TerraMeta Software, Inc. All rights reserved.
 * 
 * General License information can be found below.
 * 
 * This distribution may include materials developed by third
 * parties. For license and attribution notices for these
 * materials, please refer to the documentation that accompanies
 * this distribution (see the "Licenses for Third-Party Components"
 * appendix) or view the online documentation at 
 * . 
 */
package org.cloudgraph.hbase.mapreduce;

import java.io.Closeable;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.naming.NamingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configurable;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.RegionLocator;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.MultiTableInputFormatBase;
import org.apache.hadoop.hbase.mapreduce.TableRecordReader;
import org.apache.hadoop.hbase.mapreduce.TableSplit;
import org.apache.hadoop.hbase.util.Addressing;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.RegionSizeCalculator;
import org.apache.hadoop.hbase.util.Strings;
import org.apache.hadoop.mapreduce.InputFormat;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.net.DNS;
import org.apache.hadoop.util.StringUtils;
import org.cloudgraph.hbase.util.FilterUtil;
import org.cloudgraph.mapreduce.GraphWritable;

/**
 * A graph based input-specification for MapReduce jobs which splits an
 * underlying root table by region and by the scans resulting from a given
 * query, then
 * provides graph record {@link GraphRecordReader readers} for each split which assemble and serve data graphs to client {@link GraphMapper
 * mapper} extensions.
 * 

* Data graphs are assembled within a record {@link GraphRecordReader reader} * based on the detailed selection criteria within a given * query, and * may be passed to a {@link GraphRecordRecognizer recognizer} and potentially * screened from client {@link GraphMapper mappers} potentially illuminating * business logic dedicated to identifying specific records. *

*

* A graph recognizer is used only when query expressions are present which * reference properties not found in the row key model for a target * graph. *

* * @see org.apache.hadoop.hbase.mapreduce.TableSplit * @see GraphRecordReader * @see GraphRecordRecognizer * * @author Scott Cinnamond * @since 0.5.8 */ public class GraphInputFormat extends InputFormatimplements Configurable { static final Log log = LogFactory.getLog(GraphInputFormat.class); /** * Serialized query which encapsulates meta data which drives the creation * is one or more scans against any number of tables, starting with the root. */ public static final String QUERY = "cloudgraph.hbase.mapreduce.query"; /** * Serialized store mappings or configuration which links physical tables with any number of * data graphs/mappings. This data is typically packaged into a jar in an e.g. cloudgraph-config.xml * file, but where these mappings are dynamic and loaded at runtime, the job (spark,mapreduce) child * VM's must have access to and load this mapping dynamically. * The mapping must be serialized as XML and be marshalled and be unmarshallable using the * root {@link CloudGraphConfiguration} which may have any number of table mapping elements. */ public static final String TABLE_MAPPINGS = "cloudgraph.hbase.mapreduce.tablemappings"; /** * Boolean property indicating whether a graph recognizer is necessary for * the current query */ public static final String RECOGNIZER = "cloudgraph.hbase.mapreduce.recognizer"; /** Internal Job parameter that specifies the scan list. */ protected static final String SCANS = "cloudgraph.hbase.mapreduce.scans"; /** * Internal Job parameter that specifies root the input table as derived * from a deserialized query */ protected static final String ROOT_TABLE = "cloudgraph.hbase.mapreduce.roottable"; /** The timestamp used to filter columns with a specific timestamp. */ protected static final String SCAN_TIMESTAMP = "cloudgraph.hbase.mapreduce.scan.timestamp"; /** * The starting timestamp used to filter columns with a specific range of * versions. */ protected static final String SCAN_TIMERANGE_START = "cloudgraph.hbase.mapreduce.scan.timerange.start"; /** * The ending timestamp used to filter columns with a specific range of * versions. */ protected static final String SCAN_TIMERANGE_END = "cloudgraph.hbase.mapreduce.scan.timerange.end"; /** The maximum number of version to return. */ protected static final String SCAN_MAXVERSIONS = "cloudgraph.hbase.mapreduce.scan.maxversions"; /** Set to false to disable server-side caching of blocks for this scan. */ public static final String SCAN_CACHEBLOCKS = "cloudgraph.hbase.mapreduce.scan.cacheblocks"; /** The number of rows for caching that will be passed to scanners. */ public static final String SCAN_CACHEDROWS = "cloudgraph.hbase.mapreduce.scan.cachedrows"; /** The configuration. */ private Configuration conf = null; /** The {@link Admin}. */ private Admin admin; /** The root {@link Table} to scan. */ private Table table; /** The {@link RegionLocator} of the table. */ private RegionLocator regionLocator; /** The reader scanning the table, can be a custom one. */ private TableRecordReader tableRecordReader = null; /** The underlying {@link Connection} of the table. */ private Connection connection; /** Holds the set of scans used to define the input. */ private List scans; /** The reader scanning the table, can be a custom one. */ private GraphRecordReader graphRecordReader = null; private HashMap reverseDNSCache = new HashMap(); @Override public Configuration getConf() { return this.conf; } @Override public void setConf(Configuration configuration) { this.conf = configuration; // String tableName = conf.get(ROOT_TABLE); TableName tableName = TableName.valueOf(conf.get(ROOT_TABLE)); try { // table = new HTable(new Configuration(conf), tableName); Connection con = ConnectionFactory.createConnection(new Configuration(conf)); this.table = con.getTable(tableName); this.regionLocator = con.getRegionLocator(tableName); this.admin = con.getAdmin(); this.connection = con; } catch (Exception e) { log.error(StringUtils.stringifyException(e)); } String[] rawScans = conf.getStrings(SCANS); if (rawScans.length <= 0) { throw new IllegalArgumentException("There must be at least 1 scan configuration set to : " + SCANS); } List scans = new ArrayList(); for (int i = 0; i < rawScans.length; i++) { try { Scan scan = GraphMapReduceSetup.convertStringToScan(rawScans[i]); setConf(scan, configuration); scans.add(scan); } catch (IOException e) { throw new RuntimeException("Failed to convert Scan : " + rawScans[i] + " to string", e); } } this.setScans(scans); } private void setConf(Scan scan, Configuration configuration) throws NumberFormatException, IOException { if (conf.get(SCAN_TIMESTAMP) != null) { scan.setTimeStamp(Long.parseLong(conf.get(SCAN_TIMESTAMP))); } if (conf.get(SCAN_TIMERANGE_START) != null && conf.get(SCAN_TIMERANGE_END) != null) { scan.setTimeRange(Long.parseLong(conf.get(SCAN_TIMERANGE_START)), Long.parseLong(conf.get(SCAN_TIMERANGE_END))); } if (conf.get(SCAN_MAXVERSIONS) != null) { scan.setMaxVersions(Integer.parseInt(conf.get(SCAN_MAXVERSIONS))); } if (conf.get(SCAN_CACHEDROWS) != null) { scan.setCaching(Integer.parseInt(conf.get(SCAN_CACHEDROWS))); } // false by default, full table scans generate too much GC churn scan.setCacheBlocks((conf.getBoolean(SCAN_CACHEBLOCKS, false))); } @Override public List getSplits(JobContext context) throws IOException, InterruptedException { if (scans.isEmpty()) { throw new IOException("No scans were provided."); } try { RegionSizeCalculator sizeCalculator = new RegionSizeCalculator(regionLocator, admin); Pair keys = getStartEndKeys(); // if potentially a single split (or table is empty?) if (keys == null || keys.getFirst() == null || keys.getFirst().length == 0) { HRegionLocation regLoc = regionLocator.getRegionLocation(HConstants.EMPTY_BYTE_ARRAY, false); if (null == regLoc) { throw new IOException("Expecting at least one region."); } List splits = new ArrayList(1); long regionSize = sizeCalculator.getRegionSize(regLoc.getRegionInfo().getRegionName()); Scan scan = scans.get(0); if (scans.size() > 1) log.warn("single split with multiple scans - ignoring other than first scan"); TableSplit split = new TableSplit(table.getName(), scan, HConstants.EMPTY_BYTE_ARRAY, HConstants.EMPTY_BYTE_ARRAY, regLoc.getHostnamePort().split(Addressing.HOSTNAME_PORT_SEPARATOR)[0], regionSize); splits.add(split); return splits; } List splits = new ArrayList(keys.getFirst().length); for (Scan scan : scans) { for (int i = 0; i < keys.getFirst().length; i++) { if (!includeRegionInSplit(keys.getFirst()[i], keys.getSecond()[i])) { continue; } HRegionLocation location = regionLocator.getRegionLocation(keys.getFirst()[i], false); // The below InetSocketAddress creation does a name // resolution. InetSocketAddress isa = new InetSocketAddress(location.getHostname(), location.getPort()); if (isa.isUnresolved()) { log.warn("Failed resolve " + isa); } InetAddress regionAddress = isa.getAddress(); String regionLocation; try { regionLocation = reverseDNS(regionAddress); } catch (NamingException e) { log.warn("Cannot resolve the host name for " + regionAddress + " because of " + e); regionLocation = location.getHostname(); } byte[] startRow = scan.getStartRow(); byte[] stopRow = scan.getStopRow(); // determine if the given start an stop key fall into the // region. if ((startRow.length == 0 || keys.getSecond()[i].length == 0 || Bytes.compareTo(startRow, keys.getSecond()[i]) < 0) && (stopRow.length == 0 || Bytes.compareTo(stopRow, keys.getFirst()[i]) > 0)) { byte[] splitStart = startRow.length == 0 || Bytes.compareTo(keys.getFirst()[i], startRow) >= 0 ? keys.getFirst()[i] : startRow; byte[] splitStop = (stopRow.length == 0 || Bytes.compareTo(keys.getSecond()[i], stopRow) <= 0) && keys.getSecond()[i].length > 0 ? keys.getSecond()[i] : stopRow; byte[] regionName = location.getRegionInfo().getRegionName(); long regionSize = sizeCalculator.getRegionSize(regionName); TableSplit split = new TableSplit(table.getName(), scan, // must include the scan as it may have various filters splitStart, splitStop, regionLocation, regionSize); splits.add(split); if (log.isDebugEnabled()) { log.debug("getSplits: split -> " + i + " -> " + split); } } } } return splits; } finally { closeAll(); } } /** * Uses {@link InetAddress} in case of {@link DNS} lookup failure. * @param ipAddr the address * @return the reverse address * @throws NamingException * @throws UnknownHostException */ public String reverseDNS(InetAddress ipAddr) throws NamingException, UnknownHostException { String hostName = this.reverseDNSCache.get(ipAddr); if (hostName == null) { String ipAddressString = null; try { ipAddressString = DNS.reverseDns(ipAddr, null); } catch (Exception e) { ipAddressString = InetAddress.getByName(ipAddr.getHostAddress()).getHostName(); } if (ipAddressString == null) throw new UnknownHostException("No host found for " + ipAddr); hostName = Strings.domainNamePointerToHostName(ipAddressString); this.reverseDNSCache.put(ipAddr, hostName); } return hostName; } private void closeAll() throws IOException { close(admin, table, regionLocator, connection); admin = null; table = null; regionLocator = null; connection = null; } private void close(Closeable... closables) throws IOException { for (Closeable c : closables) { if (c != null) { c.close(); } } } protected Pair getStartEndKeys() throws IOException { return this.regionLocator.getStartEndKeys(); } @Override public RecordReader createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException { if (table == null) { throw new IOException("Cannot create a record reader because of a" + " previous error. Please look at the previous logs lines from" + " the task's full log for more details."); } TableSplit tSplit = (TableSplit) split; GraphRecordReader reader = this.graphRecordReader; // if no record reader was provided use default if (reader == null) { reader = new GraphRecordReader(); } Scan sc = tSplit.getScan(); log.debug("SCAN: " + sc.toString()); if (sc.getFilter() != null) { log.info("SPLIT FILTER: " + FilterUtil.printFilterTree(sc.getFilter())); } else { log.info("split scan has no filter"); } sc.setStartRow(tSplit.getStartRow()); sc.setStopRow(tSplit.getEndRow()); reader.setScan(sc); reader.setTable(table); try { reader.initialize(tSplit, context); } catch (InterruptedException e) { throw new InterruptedIOException(e.getMessage()); } return reader; } /** * Test if the given region is to be included in the InputSplit while * splitting the regions of a table. *

* This optimization is effective when there is a specific reasoning to * exclude an entire region from the M-R job, (and hence, not contributing * to the InputSplit), given the start and end keys of the same.
* Useful when we need to remember the last-processed top record and revisit * the [last, current) interval for M-R processing, continuously. In * addition to reducing InputSplits, reduces the load on the region server * as well, due to the ordering of the keys.
*
* Note: It is possible that endKey.length() == 0 , for the * last (recent) region.
* Override this method, if you want to bulk exclude regions altogether from * M-R. By default, no region is excluded( i.e. all regions are included). * * @param startKey * Start key of the region * @param endKey * End key of the region * @return true, if this region needs to be included as part of the input * (default). */ protected boolean includeRegionInSplit(final byte[] startKey, final byte[] endKey) { return true; } /** * Allows subclasses to get the list of {@link Scan} objects. */ protected List getScans() { return this.scans; } /** * Allows subclasses to set the list of {@link Scan} objects. * * @param scans * The list of {@link Scan} used to define the input */ protected void setScans(List scans) { this.scans = scans; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy