001/** 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019package org.apache.hadoop.hbase.mapreduce; 020 021import java.io.IOException; 022import java.util.HashMap; 023import java.util.Map; 024import java.util.Random; 025 026import org.apache.hadoop.conf.Configured; 027import org.apache.hadoop.fs.FileSystem; 028import org.apache.hadoop.fs.Path; 029import org.apache.hadoop.hbase.HBaseConfiguration; 030import org.apache.hadoop.hbase.HConstants; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.yetus.audience.InterfaceAudience; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035import org.apache.hadoop.hbase.client.Admin; 036import org.apache.hadoop.hbase.client.Connection; 037import org.apache.hadoop.hbase.client.ConnectionFactory; 038import org.apache.hadoop.hbase.client.Scan; 039import org.apache.hadoop.hbase.tool.LoadIncrementalHFiles; 040import org.apache.hadoop.hbase.util.Bytes; 041import org.apache.hadoop.mapreduce.Job; 042import org.apache.hadoop.util.Tool; 043import org.apache.hadoop.util.ToolRunner; 044 045/** 046 * Tool used to copy a table to another one which can be on a different setup. 047 * It is also configurable with a start and time as well as a specification 048 * of the region server implementation if different from the local cluster. 049 */ 050@InterfaceAudience.Public 051public class CopyTable extends Configured implements Tool { 052 private static final Logger LOG = LoggerFactory.getLogger(CopyTable.class); 053 054 final static String NAME = "copytable"; 055 long startTime = 0; 056 long endTime = HConstants.LATEST_TIMESTAMP; 057 int batch = Integer.MAX_VALUE; 058 int cacheRow = -1; 059 int versions = -1; 060 String tableName = null; 061 String startRow = null; 062 String stopRow = null; 063 String dstTableName = null; 064 String peerAddress = null; 065 String families = null; 066 boolean allCells = false; 067 static boolean shuffle = false; 068 069 boolean bulkload = false; 070 Path bulkloadDir = null; 071 072 private final static String JOB_NAME_CONF_KEY = "mapreduce.job.name"; 073 074 /** 075 * Sets up the actual job. 076 * 077 * @param args The command line parameters. 078 * @return The newly created job. 079 * @throws IOException When setting up the job fails. 080 */ 081 public Job createSubmittableJob(String[] args) 082 throws IOException { 083 if (!doCommandLine(args)) { 084 return null; 085 } 086 087 Job job = Job.getInstance(getConf(), getConf().get(JOB_NAME_CONF_KEY, NAME + "_" + tableName)); 088 job.setJarByClass(CopyTable.class); 089 Scan scan = new Scan(); 090 091 scan.setBatch(batch); 092 scan.setCacheBlocks(false); 093 094 if (cacheRow > 0) { 095 scan.setCaching(cacheRow); 096 } else { 097 scan.setCaching(getConf().getInt(HConstants.HBASE_CLIENT_SCANNER_CACHING, 100)); 098 } 099 100 scan.setTimeRange(startTime, endTime); 101 102 if (allCells) { 103 scan.setRaw(true); 104 } 105 if (shuffle) { 106 job.getConfiguration().set(TableInputFormat.SHUFFLE_MAPS, "true"); 107 } 108 if (versions >= 0) { 109 scan.setMaxVersions(versions); 110 } 111 112 if (startRow != null) { 113 scan.setStartRow(Bytes.toBytesBinary(startRow)); 114 } 115 116 if (stopRow != null) { 117 scan.setStopRow(Bytes.toBytesBinary(stopRow)); 118 } 119 120 if(families != null) { 121 String[] fams = families.split(","); 122 Map<String,String> cfRenameMap = new HashMap<>(); 123 for(String fam : fams) { 124 String sourceCf; 125 if(fam.contains(":")) { 126 // fam looks like "sourceCfName:destCfName" 127 String[] srcAndDest = fam.split(":", 2); 128 sourceCf = srcAndDest[0]; 129 String destCf = srcAndDest[1]; 130 cfRenameMap.put(sourceCf, destCf); 131 } else { 132 // fam is just "sourceCf" 133 sourceCf = fam; 134 } 135 scan.addFamily(Bytes.toBytes(sourceCf)); 136 } 137 Import.configureCfRenaming(job.getConfiguration(), cfRenameMap); 138 } 139 job.setNumReduceTasks(0); 140 141 if (bulkload) { 142 TableMapReduceUtil.initTableMapperJob(tableName, scan, Import.CellImporter.class, null, 143 null, job); 144 145 // We need to split the inputs by destination tables so that output of Map can be bulk-loaded. 146 TableInputFormat.configureSplitTable(job, TableName.valueOf(dstTableName)); 147 148 FileSystem fs = FileSystem.get(getConf()); 149 Random rand = new Random(); 150 Path root = new Path(fs.getWorkingDirectory(), "copytable"); 151 fs.mkdirs(root); 152 while (true) { 153 bulkloadDir = new Path(root, "" + rand.nextLong()); 154 if (!fs.exists(bulkloadDir)) { 155 break; 156 } 157 } 158 159 System.out.println("HFiles will be stored at " + this.bulkloadDir); 160 HFileOutputFormat2.setOutputPath(job, bulkloadDir); 161 try (Connection conn = ConnectionFactory.createConnection(getConf()); 162 Admin admin = conn.getAdmin()) { 163 HFileOutputFormat2.configureIncrementalLoadMap(job, 164 admin.getDescriptor((TableName.valueOf(dstTableName)))); 165 } 166 } else { 167 TableMapReduceUtil.initTableMapperJob(tableName, scan, 168 Import.Importer.class, null, null, job); 169 170 TableMapReduceUtil.initTableReducerJob(dstTableName, null, job, null, peerAddress, null, 171 null); 172 } 173 174 return job; 175 } 176 177 /* 178 * @param errorMsg Error message. Can be null. 179 */ 180 private static void printUsage(final String errorMsg) { 181 if (errorMsg != null && errorMsg.length() > 0) { 182 System.err.println("ERROR: " + errorMsg); 183 } 184 System.err.println("Usage: CopyTable [general options] [--starttime=X] [--endtime=Y] " + 185 "[--new.name=NEW] [--peer.adr=ADR] <tablename>"); 186 System.err.println(); 187 System.err.println("Options:"); 188 System.err.println(" rs.class hbase.regionserver.class of the peer cluster"); 189 System.err.println(" specify if different from current cluster"); 190 System.err.println(" rs.impl hbase.regionserver.impl of the peer cluster"); 191 System.err.println(" startrow the start row"); 192 System.err.println(" stoprow the stop row"); 193 System.err.println(" starttime beginning of the time range (unixtime in millis)"); 194 System.err.println(" without endtime means from starttime to forever"); 195 System.err.println(" endtime end of the time range. Ignored if no starttime specified."); 196 System.err.println(" versions number of cell versions to copy"); 197 System.err.println(" new.name new table's name"); 198 System.err.println(" peer.adr Address of the peer cluster given in the format"); 199 System.err.println(" hbase.zookeeper.quorum:hbase.zookeeper.client" 200 + ".port:zookeeper.znode.parent"); 201 System.err.println(" families comma-separated list of families to copy"); 202 System.err.println(" To copy from cf1 to cf2, give sourceCfName:destCfName. "); 203 System.err.println(" To keep the same name, just give \"cfName\""); 204 System.err.println(" all.cells also copy delete markers and deleted cells"); 205 System.err.println(" bulkload Write input into HFiles and bulk load to the destination " 206 + "table"); 207 System.err.println(); 208 System.err.println("Args:"); 209 System.err.println(" tablename Name of the table to copy"); 210 System.err.println(); 211 System.err.println("Examples:"); 212 System.err.println(" To copy 'TestTable' to a cluster that uses replication for a 1 hour window:"); 213 System.err.println(" $ hbase " + 214 "org.apache.hadoop.hbase.mapreduce.CopyTable --starttime=1265875194289 --endtime=1265878794289 " + 215 "--peer.adr=server1,server2,server3:2181:/hbase --families=myOldCf:myNewCf,cf2,cf3 TestTable "); 216 System.err.println("For performance consider the following general option:\n" 217 + " It is recommended that you set the following to >=100. A higher value uses more memory but\n" 218 + " decreases the round trip time to the server and may increase performance.\n" 219 + " -Dhbase.client.scanner.caching=100\n" 220 + " The following should always be set to false, to prevent writing data twice, which may produce \n" 221 + " inaccurate results.\n" 222 + " -Dmapreduce.map.speculative=false"); 223 } 224 225 private boolean doCommandLine(final String[] args) { 226 // Process command-line args. TODO: Better cmd-line processing 227 // (but hopefully something not as painful as cli options). 228 if (args.length < 1) { 229 printUsage(null); 230 return false; 231 } 232 try { 233 for (int i = 0; i < args.length; i++) { 234 String cmd = args[i]; 235 if (cmd.equals("-h") || cmd.startsWith("--h")) { 236 printUsage(null); 237 return false; 238 } 239 240 final String startRowArgKey = "--startrow="; 241 if (cmd.startsWith(startRowArgKey)) { 242 startRow = cmd.substring(startRowArgKey.length()); 243 continue; 244 } 245 246 final String stopRowArgKey = "--stoprow="; 247 if (cmd.startsWith(stopRowArgKey)) { 248 stopRow = cmd.substring(stopRowArgKey.length()); 249 continue; 250 } 251 252 final String startTimeArgKey = "--starttime="; 253 if (cmd.startsWith(startTimeArgKey)) { 254 startTime = Long.parseLong(cmd.substring(startTimeArgKey.length())); 255 continue; 256 } 257 258 final String endTimeArgKey = "--endtime="; 259 if (cmd.startsWith(endTimeArgKey)) { 260 endTime = Long.parseLong(cmd.substring(endTimeArgKey.length())); 261 continue; 262 } 263 264 final String batchArgKey = "--batch="; 265 if (cmd.startsWith(batchArgKey)) { 266 batch = Integer.parseInt(cmd.substring(batchArgKey.length())); 267 continue; 268 } 269 270 final String cacheRowArgKey = "--cacheRow="; 271 if (cmd.startsWith(cacheRowArgKey)) { 272 cacheRow = Integer.parseInt(cmd.substring(cacheRowArgKey.length())); 273 continue; 274 } 275 276 final String versionsArgKey = "--versions="; 277 if (cmd.startsWith(versionsArgKey)) { 278 versions = Integer.parseInt(cmd.substring(versionsArgKey.length())); 279 continue; 280 } 281 282 final String newNameArgKey = "--new.name="; 283 if (cmd.startsWith(newNameArgKey)) { 284 dstTableName = cmd.substring(newNameArgKey.length()); 285 continue; 286 } 287 288 final String peerAdrArgKey = "--peer.adr="; 289 if (cmd.startsWith(peerAdrArgKey)) { 290 peerAddress = cmd.substring(peerAdrArgKey.length()); 291 continue; 292 } 293 294 final String familiesArgKey = "--families="; 295 if (cmd.startsWith(familiesArgKey)) { 296 families = cmd.substring(familiesArgKey.length()); 297 continue; 298 } 299 300 if (cmd.startsWith("--all.cells")) { 301 allCells = true; 302 continue; 303 } 304 305 if (cmd.startsWith("--bulkload")) { 306 bulkload = true; 307 continue; 308 } 309 310 if (cmd.startsWith("--shuffle")) { 311 shuffle = true; 312 continue; 313 } 314 315 if (i == args.length-1) { 316 tableName = cmd; 317 } else { 318 printUsage("Invalid argument '" + cmd + "'"); 319 return false; 320 } 321 } 322 if (dstTableName == null && peerAddress == null) { 323 printUsage("At least a new table name or a " + 324 "peer address must be specified"); 325 return false; 326 } 327 if ((endTime != 0) && (startTime > endTime)) { 328 printUsage("Invalid time range filter: starttime=" + startTime + " > endtime=" + endTime); 329 return false; 330 } 331 332 if (bulkload && peerAddress != null) { 333 printUsage("Remote bulkload is not supported!"); 334 return false; 335 } 336 337 // set dstTableName if necessary 338 if (dstTableName == null) { 339 dstTableName = tableName; 340 } 341 } catch (Exception e) { 342 e.printStackTrace(); 343 printUsage("Can't start because " + e.getMessage()); 344 return false; 345 } 346 return true; 347 } 348 349 /** 350 * Main entry point. 351 * 352 * @param args The command line parameters. 353 * @throws Exception When running the job fails. 354 */ 355 public static void main(String[] args) throws Exception { 356 int ret = ToolRunner.run(HBaseConfiguration.create(), new CopyTable(), args); 357 System.exit(ret); 358 } 359 360 @Override 361 public int run(String[] args) throws Exception { 362 Job job = createSubmittableJob(args); 363 if (job == null) return 1; 364 if (!job.waitForCompletion(true)) { 365 LOG.info("Map-reduce job failed!"); 366 if (bulkload) { 367 LOG.info("Files are not bulkloaded!"); 368 } 369 return 1; 370 } 371 int code = 0; 372 if (bulkload) { 373 code = new LoadIncrementalHFiles(this.getConf()).run(new String[]{this.bulkloadDir.toString(), 374 this.dstTableName}); 375 if (code == 0) { 376 // bulkloadDir is deleted only LoadIncrementalHFiles was successful so that one can rerun 377 // LoadIncrementalHFiles. 378 FileSystem fs = FileSystem.get(this.getConf()); 379 if (!fs.delete(this.bulkloadDir, true)) { 380 LOG.error("Deleting folder " + bulkloadDir + " failed!"); 381 code = 1; 382 } 383 } 384 } 385 return code; 386 } 387}