背景:项目中,领导要求定时将生产集群的数据导入到测试集群中,以便于数据开发人员进行开发,其次也能起到一个备份的作用;
我们调研了一下,觉得distcp应该是集群同步最快的方式了,因而决定用distcp 命令来进行集群间的文件拷贝,但文件导过去后,因为都是orc表,虽然文件导过去了,但是数据依然不可使用;所以决定一不做二不休,直接将元数据也一把干过去;
集群环境:TDH6.0.2
我们准备分两步进行:
1、全量同步 集群文件 和 元数据
2、其后每日增量同步集群文件和元数据
首先第一步,挑一个黄道吉日,将文件按库逐步全量同步到测试环境;
hadoop distcp -pb hdfs://node4:8020/inceptor1/user/hive/warehouse/ods.db/hbxyoper hdfs://tdh1:8020/inceptor1/user/hive/warehouse/ods.db/
这一步非常耗时,而且一旦开始集群间拷贝,会非常占带宽,虽然是万兆带宽,但是依然会长时间占满,有可能影响业务系统,因而建议在周末同步,或者加 -bandwidth 来限制带宽。
第二步,我们则打算通过java程序来实现数据和元数据的自动同步
首先来说,其实数据文件其实很好同步,只要通过程序调度一下hadoop命令即可,但是文件的拷贝过程,增量同步的最细粒度只能做到按分区同步;
所以我们决定对集群中的非分区表每日全量同步,分区表则按日期增量抽取(正常来说,大表一般都会进行分区);
有些难度的是元数据的增量同步,经过梳理和测试,我们发现要想完全从生产集群”拷贝”一张表到测试环境,至少需要同步13张核心表;
- TBLS(该表中存储Hive表、视图、索引表的基本信息)
TBL_ID | 表ID |
CREATE_TIME | 创建时间 |
DB_ID | 数据库ID (对应DBS的DB_ID) |
LAST_ACCESS_TIME | 上次访问时间 |
OWNER | 所有者 |
RETENTION | 保留字段 |
SD_ID | 序列化配置信息(对应SDS表中的SD_ID) |
TBL_NAME | 表名 |
TBL_TYPE | 表类型 |
VIEW_EXPANDED_TEXT | 视图详细的HQL语句 |
VIEW_ORIGINAL_TEXT | 视图原始的HQL语句 |
2、TABLE_PARAMS(表/视图的属性信息)
字段 | 描述 |
TBL_ID | 表ID(对应TBLS中的TBL_ID) |
PARAM_KEY | 属性名 |
PARAM_VALUES | 属性值 |
3、TBL_PRIVS(存储表/视图的权限信息)
字段 | 描述 |
TBL_GRANT_ID | 表ID(对应TBLS中的TBL_ID) |
CREATE_TIME | |
GRANT_OPTION | |
GRANTOR |
|
GRANTOR_TYPE |
|
PRINCIPAL_TYPE | 用户类型 |
TBL_PRIV | 权限 |
TBL_ID | 表ID(对应TBLS中的TBL_ID) |
4、SDS(文件存储的基本信息 ,如:存储格式,路径,分桶,是否压缩等信息)
SD_ID |
|
CD_ID | 字段信息ID |
INPUT_FORMAT | 文件输入格式 |
IS_COMPRESSED | 是否压缩 |
IS_STOREDASSUBDIRECTORIES | 是否以子目录存储 |
LOCATION | HDFS路径 |
NUM_BUCKETS | 分桶数量 |
OUTPUT_FORMAT | 文件输出格式 |
SERDE_ID | 序列化类ID |
5、SERDE_PARAMS(存储序列化的一些属性、格式信息,比如:行、列分隔符)
字段 | 描述 |
SERDE_ID | 序列化类配置ID(对应SERDES的SERDE_ID ) |
PARAM_KEY | 属性名 |
PARAM_VALUE | 属性值 |
6、SERDES(存储Hive存储的属性信息 存储序列化使用的类信息)
字段 | 字段说明 |
SERDE_ID | 序列化类配置ID(对应SDS的SERDE_ID ) |
NAME | 序列化类别名 |
SLIB | 序列化类 |
7、columns_v2(表的字段信息)
字段 | 字段说明 |
CD_ID | 字段信息ID(对应表SDS的CD_ID) |
COMMENT | 字段注释 |
COLUMN_NAME | 字段名 |
TYPE_NAME | 字段类型 |
INTEGER_IDX | 字段顺序 |
8、PARTITIONS(分区的基本信息)
字段 | 字段说明 |
PART_ID | 分区ID |
CREATE_TIME | 分区创建时间 |
LAST_ACCESS_TIME | 最后一次访问时间 |
PART_NAME | 分区名称 |
SD_ID | 分区存储ID |
TBL_ID | 表ID,对应TBLS的TBL_ID |
9、partition_keys(分区的字段信息)
字段名称 | 字段说明 |
TBL_ID | 表ID,对应TBLS的TBL_ID |
PKEY_COMMENT | 分区字段说明 |
PKEY_NAME | 分区字段名称 |
PKEY_TYPE | 分区字段类型 |
INTEGER_IDX | 分区字段顺序 |
10、partition_key_vals(分区字段值)
字段 | 字段说明 |
PART_ID | 分区ID,对应PARTITIONS的PART_ID |
PART_KEY_VAL | 分区字段值 |
INTEGER_IDX | 分区字段值顺序 |
11、partition_params(分区的属性信息)
字段 | 字段说明 |
PART_ID | 分区ID,对应PARTITIONS的PART_ID |
PARAM_KEY | 分区属性名 |
PARAM_VALUE | 分区属性值 |
12、BUCKETING_COLS()
字段 | 字段说明 |
SD_ID | |
BUCKET_COL_NAME | 分桶字段名 |
INTEGER_IDX |
13、CDS ()
字段 | 字段说明 |
CD_ID | 对应SDS的CD_ID |
需要注意的是:TBLS的SD_ID和SD_ID并不完全对应,需要获取TBLS的SD_ID查出SDS的CD_ID,再根据拿到的CD_ID反查出SDS中对应的SD_ID和SERDE_ID; 这样才能获取完整的数据;
在这一步,要注意的是这13张表之中存在着外键关系,因而无论是删除还是插入,都要注意其先后顺序,否则会导致无法删除或插入;
但是后面我们从mysqldump中受到了启发,又发现了一种新的方式可以不受外键之间的关系,任意的删除和插入数据:
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
在session中,添加这条命令,即可忽略外键;
到这一步,基本元数据和数据文件都已能成功落到测试环境上了;
不过我们在这里遇到了一个巨坑,虽然元数据看上去已经成功落入到测试的元数据库中,但是在hive上我们依然不能访问到数据;
我们简单比对了一下生产和测试的元数据,发现二者几乎一致;
到这一步,我们一度以为可能同步元数据过程中,不止需要同步12张,可能还是缺少了某一张“关键”的表,我们花费了大量的时间去比对了一下,基本确定了,除了这12张表,其他的表只要初始化过来,之后基本不用同步;
至此,场面一度陷入僵局,不得已之下,我们直接将两个元数据库的数据直接dump出来,比较两个dump文件,最终发现了metastore_inceptor1.TBLS 表中的stargate_table_code 字段不一致
生产截图:
测试截图:
至此我们才发现这个巨坑的问题:
那就是mysql会把tinyint(1) 类型当成boolean返回,所以 -1 无法被转换,插入到测试库中会变为null值;
其实到这步解决方式比较多了,可以在程序里多加一个类型转换,不过我们没采用这种方式。
我们的解决方案是:在jdbc的连接中加上?tinyInt1isBit=false&transformedBitIsBoolean=false; 例:jdbc:mysql://node1:3316/metastore_inceptor1?tinyInt1isBit=false&transformedBitIsBoolean=false
这样同样可以解决tinyint会被转换成boolean类型的问题;
此时重新同步元数据,就不会有问题了,数据也可以正常访问了;