在使用Atlas采集Hive元数据信息时,可以通过Hive Hook的方式实时采集元数据变动,也可以使用 HiveMetaStoreBridge将历史元数据初始化到Atlas中。
本文分析后者即使用HiveMetaStoreBridge的方式来导入Hive的历史元数据。
通过执行import-hive.sh脚本来导入历史元数据,脚本中可看到主类为HiveMetaStoreBridge。
...
"${JAVA_BIN}" ${JAVA_PROPERTIES} -cp "${CP}" org.apache.atlas.hive.bridge.HiveMetaStoreBridge $IMPORT_ARGS
...
分析HiveMetaStoreBridge的源码,大致的执行流程如下。
下面是各个方法的带注释的源码程序。
#importDatabases方法
/**
* 导入数据库到Atlas。根据给定的条件导入数据库和表。
*
* @param failOnError 是否在出现错误时终止操作
* @param databaseToImport 要导入的数据库名称
* @param tableToImport 要导入的表名称
* @throws Exception 可能的异常
*/
private void importDatabases(boolean failOnError, String databaseToImport, String tableToImport) throws Exception {
// 用于存储数据库名称的列表
List<String> databaseNames = null;
// 当数据库和表都为空时,导入所有数据库
if (StringUtils.isEmpty(databaseToImport) && StringUtils.isEmpty(tableToImport)) {
databaseNames = hiveClient.getAllDatabases();
}
// 当数据库为空且表不为空时,检查表中是否包含数据库名称,如果是,则导入该数据库和表
else if (StringUtils.isEmpty(databaseToImport) && StringUtils.isNotEmpty(tableToImport)) {
// 检查表名中是否包含数据库名
if (isTableWithDatabaseName(tableToImport)) {
// 以"."分割表名,获取数据库名和表名
String val[] = tableToImport.split("\\.");
if (val.length > 1) {
databaseToImport = val[0];
tableToImport = val[1];
}
// 根据数据库名的模式获取数据库列表
databaseNames = hiveClient.getDatabasesByPattern(databaseToImport);
} else {
// 如果表名中不包含数据库名,则导入所有数据库
databaseNames = hiveClient.getAllDatabases();
}
}
// 当数据库不为空时,导入指定数据库及其所有表
else {
databaseNames = hiveClient.getDatabasesByPattern(databaseToImport);
}
// 如果找到了数据库,则进行导入操作
if (!CollectionUtils.isEmpty(databaseNames)) {
LOG.info("Found {} databases", databaseNames.size());
// 遍历每个数据库
for (String databaseName : databaseNames) {
// 注册数据库实体到Atlas
AtlasEntityWithExtInfo dbEntity = registerDatabase(databaseName);
// 如果数据库实体成功注册,则导入数据库中的表
if (dbEntity != null) {
importTables(dbEntity.getEntity(), databaseName, tableToImport, failOnError);
}
}
}
// 如果没有找到数据库,则打印错误日志并退出程序
else {
LOG.error("No database found");
System.exit(EXIT_CODE_FAILED);
}
}
#importTables方法
/**
* 导入表到Atlas。根据给定的条件导入数据库中的表。
*
* @param dbEntity 数据库实体对象
* @param databaseName 数据库名称
* @param tblName 要导入的表名称
* @param failOnError 是否在出现错误时终止操作
* @return 成功导入的表的数量
* @throws Exception 可能的异常
*/
private int importTables(AtlasEntity dbEntity, String databaseName, String tblName, final boolean failOnError) throws Exception {
// 记录成功导入的表的数量
int tablesImported = 0;
// 存储表名的列表
final List<String> tableNames;
// 如果表名为空,则导入数据库中的所有表
if (StringUtils.isEmpty(tblName)) {
tableNames = hiveClient.getAllTables(databaseName);
}
// 如果表名非空,则根据模式匹配获取表名列表
else {
tableNames = hiveClient.getTablesByPattern(databaseName, tblName);
}
// 如果找到要导入的表,则进行导入操作
if (!CollectionUtils.isEmpty(tableNames)) {
LOG.info("Found {} tables to import in database {}", tableNames.size(), databaseName);
try {
// 遍历每个表
for (String tableName : tableNames) {
// 导入单个表,记录导入的数量
int imported = importTable(dbEntity, databaseName, tableName, failOnError);
tablesImported += imported;
}
} finally {
// 根据导入结果打印日志信息
if (tablesImported == tableNames.size()) {
LOG.info("Successfully imported {} tables from database {}", tablesImported, databaseName);
} else {
LOG.error("Imported {} of {} tables from database {}. Please check logs for errors during import", tablesImported, tableNames.size(), databaseName);
}
}
}
// 如果没有找到要导入的表,则打印错误日志
else {
LOG.error("No tables to import in database {}", databaseName);
}
// 返回成功导入的表的数量
return tablesImported;
}
#importTable方法
/**
* 导入单个表到Atlas。根据给定的数据库名称和表名称导入表的元数据信息。
*
* @param dbEntity 数据库实体对象
* @param databaseName 数据库名称
* @param tableName 表名称
* @param failOnError 是否在出现错误时终止操作
* @return 成功导入的表的数量(1表示成功,0表示失败)
* @throws Exception 可能的异常
*/
@VisibleForTesting
public int importTable(AtlasEntity dbEntity, String databaseName, String tableName, final boolean failOnError) throws Exception {
try {
// 获取表的元数据信息
Table table = hiveClient.getTable(databaseName, tableName);
// 注册表实体到Atlas
AtlasEntityWithExtInfo tableEntity = registerTable(dbEntity, table);
// 如果表类型为EXTERNAL_TABLE,则创建相应的Hive进程实体
if (table.getTableType() == TableType.EXTERNAL_TABLE) {
String processQualifiedName = getTableProcessQualifiedName(metadataNamespace, table);
// 检查进程实体是否已存在
AtlasEntityWithExtInfo processEntity = findProcessEntity(processQualifiedName);
if (processEntity == null) {
// 获取表的位置信息和创建表的查询语句
String tableLocationString = isConvertHdfsPathToLowerCase() ? lower(table.getDataLocation().toString()) : table.getDataLocation().toString();
Path location = table.getDataLocation();
String query = getCreateTableString(table, tableLocationString);
// 创建Hive进程实体
PathExtractorContext pathExtractorCtx = new PathExtractorContext(getMetadataNamespace(), isConvertHdfsPathToLowerCase(), awsS3AtlasModelVersion);
AtlasEntityWithExtInfo entityWithExtInfo = AtlasPathExtractorUtil.getPathEntity(location, pathExtractorCtx);
AtlasEntity pathInst = entityWithExtInfo.getEntity();
AtlasEntity tableInst = tableEntity.getEntity();
AtlasEntity processInst = new AtlasEntity(HiveDataTypes.HIVE_PROCESS.getName());
long now = System.currentTimeMillis();
processInst.setAttribute(ATTRIBUTE_QUALIFIED_NAME, processQualifiedName);
processInst.setAttribute(ATTRIBUTE_NAME, query);
processInst.setAttribute(ATTRIBUTE_CLUSTER_NAME, metadataNamespace);
processInst.setRelationshipAttribute(ATTRIBUTE_INPUTS, Collections.singletonList(AtlasTypeUtil.getAtlasRelatedObjectId(pathInst, RELATIONSHIP_DATASET_PROCESS_INPUTS)));
processInst.setRelationshipAttribute(ATTRIBUTE_OUTPUTS, Collections.singletonList(AtlasTypeUtil.getAtlasRelatedObjectId(tableInst, RELATIONSHIP_PROCESS_DATASET_OUTPUTS)));
String userName = table.getOwner();
if (StringUtils.isEmpty(userName)) {
userName = ApplicationProperties.get().getString(HIVE_USERNAME, "hive");
}
processInst.setAttribute(ATTRIBUTE_USER_NAME, userName);
processInst.setAttribute(ATTRIBUTE_START_TIME, now);
processInst.setAttribute(ATTRIBUTE_END_TIME, now);
processInst.setAttribute(ATTRIBUTE_OPERATION_TYPE, "CREATETABLE");
processInst.setAttribute(ATTRIBUTE_QUERY_TEXT, query);
processInst.setAttribute(ATTRIBUTE_QUERY_ID, query);
processInst.setAttribute(ATTRIBUTE_QUERY_PLAN, "{}");
processInst.setAttribute(ATTRIBUTE_RECENT_QUERIES, Collections.singletonList(query));
AtlasEntitiesWithExtInfo createTableProcess = new AtlasEntitiesWithExtInfo();
createTableProcess.addEntity(processInst);
if (pathExtractorCtx.getKnownEntities() != null) {
pathExtractorCtx.getKnownEntities().values().forEach(entity -> createTableProcess.addEntity(entity));
} else {
createTableProcess.addEntity(pathInst);
}
// 注册进程实体及相关实体到Atlas
registerInstances(createTableProcess);
} else {
// 如果进程实体已存在,则打印日志信息
LOG.info("Process {} is already registered", processQualifiedName);
}
}
// 返回成功导入的表的数量
return 1;
} catch (Exception e) {
// 打印导入失败的错误日志
LOG.error("Import failed for hive_table {}", tableName, e);
// 根据配置决定是否在导入失败时终止操作
if (failOnError) {
throw e;
}
// 返回导入失败的标志
return 0;
}
}
#registerInstance方法
/**
* 注册实体信息到Atlas。通过Atlas REST API将给定的实体信息注册到Atlas服务器。
*
* @param entities 包含要注册的实体信息的对象
* @return 包含已注册实体信息的对象
* @throws Exception 可能的异常
*/
private AtlasEntitiesWithExtInfo registerInstances(AtlasEntitiesWithExtInfo entities) throws Exception {
// 如果调试日志启用,打印要创建的实体信息数量和详细信息
if (LOG.isDebugEnabled()) {
LOG.debug("creating {} entities: {}", entities.getEntities().size(), entities);
}
// 用于存储已注册的实体信息
AtlasEntitiesWithExtInfo ret = null;
// 调用Atlas客户端的createEntities方法,将实体信息注册到Atlas
EntityMutationResponse response = atlasClientV2.createEntities(entities);
// 获取通过CREATE操作创建的实体信息
List<AtlasEntityHeader> createdEntities = response.getEntitiesByOperation(EntityMutations.EntityOperation.CREATE);
// 如果有成功创建的实体,则处理并返回结果
if (CollectionUtils.isNotEmpty(createdEntities)) {
ret = new AtlasEntitiesWithExtInfo();
// 遍历每个创建成功的实体信息
for (AtlasEntityHeader createdEntity : createdEntities) {
// 根据GUID获取已创建的实体信息
AtlasEntityWithExtInfo entity = atlasClientV2.getEntityByGuid(createdEntity.getGuid());
// 将实体信息添加到结果对象中
ret.addEntity(entity.getEntity());
// 如果存在关联实体信息,则也添加到结果对象中
if (MapUtils.isNotEmpty(entity.getReferredEntities())) {
for (Map.Entry<String, AtlasEntity> entry : entity.getReferredEntities().entrySet()) {
ret.addReferredEntity(entry.getKey(), entry.getValue());
}
}
// 打印日志,表示已成功创建实体信息
LOG.info("Created {} entity: name={}, guid={}", entity.getEntity().getTypeName(), entity.getEntity().getAttribute(ATTRIBUTE_QUALIFIED_NAME), entity.getEntity().getGuid());
}
}
// 清理结果对象中的关系属性
clearRelationshipAttributes(ret);
// 返回包含已注册实体信息的结果对象
return ret;
}