HBase2.1.0数据迁移方案
业务要求:将数据从旧的集群HBase1.2迁移到HBase2.1.0中去。每个表的数据量级10TB左右
值得注意:HBase升级到Hbase2.x之后,他的数据修复工具独立出来作为 HBCK2 成立了一个单独的项目 ,并且这个项目并不是所有的hbase都支持,其中就包含我们正在使用的HBase2.1.0
说明:步骤一和步骤二的代码打包的jar包名为readhbase-1.0-SNAPSHOT.jar
步骤三的代码打包的包名为:bulkload.jar
因为两套集群的hbase版本不统一,因此生成region信息的maven依赖和写入region信息的maven依赖需要更换
如果你的版本支持HBCK2命令,那么你自己你迁移了HFile之后还是使用HBCK2命令比较好,我的这个方法可以用但是麻烦。
步骤一、迁移HFile数据
温馨提示:迁移之前一定要disable 需要迁移的hbase表,不然可能会报错,最可气的是报错了你没发现,一套流程走下来发现hbase的数据不够,原因就是部分数据没有迁移过来
使用Hadoop自带的命令distcp命令进行hadoop层面的数据移动,具体命令如下:【我的参考】
hadoop distcp \
-Dmapreduce.job.name=jobname \
-bandwidth 20 \
-m 20 \
hdfs://192.168.1.1:8020/habse/data/default/cdr201906 \
hdfs://192.168.2.1:8020/copy_data/cdr201906
-m为指定的map数量,默认为20个
-bandwith为带宽限制,防止一次同步过多的文件影响其他任务运行
-Dmapreduce.job.name为任务的名称【yarn的网页显示出来】
步骤二、迁移表的region
如果有hbck2命令可以使用该命令进行修复,但是正如我们前面所提到的,HBCK2命令不支持HBase2.1.0。不信你去查看【点击进入】那么我们只能另想思路了。
我的思路为复制分区+bulkload方案。由于复制来的数据已经是HFile了,我们就可以直接拿着这批数据入bulk到hbase,但是这样存在一个弊端,那就是只会形成一个region,一旦是这样,那么这个10TB左右的region要面临无尽的分裂,这样会大量消耗hbase的性能,于是就加上一个预分区在前面。先复制分区到新的机器,然后再复制数据。
我复制分区的方案为手动复制,也就是写java代码拷贝。代码如下
package load;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
public class MyRegionInfo implements Serializable {
private static final long serialVersionUID = 1L;
private String tableName;
private List<String> columnFamilies;
private byte[][] regions;
public List<String> getColumnFamilies() {
return columnFamilies;
}
public void setColumnFamilies(List<String> columnFamilies) {
this.columnFamilies = columnFamilies;
}
public MyRegionInfo(String tableName, List<String> columnFamilys, byte[][] regions) {
this.tableName = tableName;
this.columnFamilies = columnFamilys;
this.regions = regions;
}
public MyRegionInfo() {
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public byte[][] getRegions() {
return regions;
}
public void setRegions(byte[][] regions) {
this.regions = regions;
}
@Override
public String toString() {
return "MyRegionInfo{" +
"tableName='" + tableName + '\'' +
", columnFamilies=" + columnFamilies.size()+columnFamilies.get(0) +
", regions=" + Arrays.toString(regions) +
'}';
}
}
package load;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.RegionLocator;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class GetRegionInfo {
public static void main(String[] args) throws Exception{
if(args.length < 2){
System.out.println("--------------请输入两个参数---------------");
System.out.println("|参数一:需要拷贝的元数据的表名 |");
System.out.println("|参数二:保存的元数据的文件名 |");
System.out.println("|------------------------------------------|");
}
Configuration configuration = HBaseConfiguration.create();
Connection connection = ConnectionFactory.createConnection(configuration);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File(args[1])));
RegionLocator regionLocator = connection.getRegionLocator(TableName.valueOf(args[0]));
List<HRegionLocation> regionLocations = regionLocator.getAllRegionLocations();
List<String> families = new ArrayList();
Collection<HColumnDescriptor> realFamilies = connection.getAdmin().getTableDescriptor(TableName.valueOf(args[0])).getFamilies();
for(HColumnDescriptor descriptor:realFamilies){
System.out.println("列族:"+descriptor.getNameAsString());
families.add(descriptor.getNameAsString());
}
byte[][] regionsInfo = new byte[regionLocations.size() -1][];
int i = 0;
for(HRegionLocation location:regionLocations){
HRegionInfo regionInfo = location.getRegionInfo();
byte[] startKey = regionInfo.getStartKey();
if(startKey != null && Bytes.compareTo(startKey, HConstants.EMPTY_BYTE_ARRAY) != 0 ){
regionsInfo[i] = startKey;
i++;
System.out.println(startKey);
}
}
MyRegionInfo myRegionInfo = new MyRegionInfo(args[0], families, regionsInfo);
outputStream.writeObject(myRegionInfo);
outputStream.flush();
outputStream.close();
connection.close();
}
}
这个jar包执行方法为:
[root@a01 data]# java -jar readhbase-1.0-SNAPSHOT.jar trp2017 region_trp2017
# 参数一:trp2017 表示表名
# 参数二:region_trp2017 表示保存的文件名,里面存储的为region信息
然后执行之后就得到了region_trp2017
这个文件,这个文件保存的就是region信息。拿到了这个文件之后去新的集群执行,就可以自动创建预分区之后的表。
创建表的代码如下:
package load;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
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.util.Bytes;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class CreateTable {
public static void main(String[] args) throws Exception{
if(args.length <= 0){
System.out.println("--------------请输入两个参数---------------");
System.out.println("|参数一:保存的元数据的文件名 |");
System.out.println("|------------------------------------------|");
}
Configuration configuration = HBaseConfiguration.create();
Connection connection = ConnectionFactory.createConnection(configuration);
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File(args[0])));
MyRegionInfo myRegionInfo = (MyRegionInfo) inputStream.readObject();
Admin admin = connection.getAdmin();
HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf(myRegionInfo.getTableName()));
List<String> columnFamilies = myRegionInfo.getColumnFamilies();
for(String families:columnFamilies){
hTableDescriptor.addFamily(new HColumnDescriptor(Bytes.toBytes(families)));
}
System.out.println(myRegionInfo);
admin.createTable(hTableDescriptor,myRegionInfo.getRegions());
connection.close();
}
}
执行方法如下:
[root@p01 ~]# java -jar readhbase-1.0-SNAPSHOT.jar region_trp2017
[root@p01 ~]#
# |该步骤在新的机器上执行,他会自动创建表,其中参数 region_trp2017 为上一步骤生成的文件名。里面存储的为region信息
步骤三、bulkload数据
分区信息拷贝之后再导入,此时导入就不用再次进行分裂了,我的导入代码为:【这里为scala代码因为我要借助spark执行】
package load
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.hadoop.hbase.client.{ConnectionFactory, HTable, Table}
import org.apache.hadoop.hbase.tool.LoadIncrementalHFiles
import org.apache.hadoop.hbase.{HBaseConfiguration, TableName}
import scala.collection.JavaConverters._
object StartBulkLoad {
def main(args: Array[String]): Unit = {
if (args.length < 2) {
System.out.println("--------------请输入两个参数---------------")
System.out.println("|参数一:hfile存在的位置 |")
System.out.println("|参数二:导入的表名 |")
System.out.println("|-------------------------------------------|")
}
val config = HBaseConfiguration.create()
val conn = ConnectionFactory.createConnection(config)
val fs = FileSystem.get(new Configuration)
val hfiles = fs.listStatus(new Path(args(0)))
val load = new LoadIncrementalHFiles(config)
for (hfile <- hfiles){
if(hfile.getPath.getName.length == 32){
load.doBulkLoad(hfile.getPath, conn.getAdmin, conn.getTable(TableName.valueOf(args(1))),
conn.getRegionLocator(TableName.valueOf(args(1))))
}
}
}
}
我的执行命令为【请用client模式】:
[root@p01 ~]# spark-submit --class load.StartBulkLoad --deploy-mode client --master yarn bulkload.jar /copy_data/trp2017 trp2017
看好日志,如果发现有错误,重新bulkload数据
此时数据已经导入完成。查看一下,果然好多数据
旧集群统计一下数据的多少
[root@p01 ~] hbase org.apache.hadoop.hbase.mapreduce.RowCounter 'tri2018'
# 得到结果ROWS=395502806
新集群统计一下数据的多少
[root@p01 ~] hbase org.apache.hadoop.hbase.mapreduce.RowCounter 'tri2018'
# 得到结果ROWS=395502806