【DataHub】 现代数据栈的元数据平台--如何将自定义的元数据事件发送到DataHub

在某些情况下,业务上需要程序直接构造自定义的元数据事件【MetadataEvent】,然后将该元数据发送到DataHub。

DataHub分别针对python和java提供了emitter 库,如下:

  • Python emitter libraries
  • Java emitter libraries

本文基于上面的2个库,讲解如何构造元数据事件,然后将其发送到DataHub

Python emitter

基于Python的元数据摄取系统【acryl-datahub】提供了 REST 和 Kafka emitter API,很容易进行代码集成。
REST Emitter:基于requests 模块进行了一层薄包装,提供了一个通过HTTP发送元数据事件的阻塞接口,主要用于如下2种场景:

  • 当将元数据持久化到DataHub的元数据存储的简单性和确认比元数据发送的吞吐量更重要时,请使用此方法
  • 当存在write -after-write场景时使用此方法,例如写元数据然后立即将其读回来。

Kafka Emitter:基于confluent-kafka的SerializingProducer类之上的一个薄包装,提供了一个非阻塞接口,用于向DataHub发送元数据事件,主要用于如下场景:

  • 想要将元数据生产者与datahub元数据服务的正常运行时间解耦时,可以使用Kafka作为一个高可用的消息总线。例如DataHub元数据服务由于计划或计划外的中断而关闭,仍然可以通过将其发送到Kafka来继续从关键任务系统收集元数据
  • 当将元数据发送的吞吐量比确认元数据被持久化到DataHub的后端存储更重要时,请使用此方法。

注意:
Kafka Emitter使用Avro序列化metadata事件到Kafka

详细构建元数据事件的代码可以参考:acryl-datahub[mysql] 模块中的 datahub.ingestion.source.sql.sql_common 文件中的loop_tables方法

使用DatasetSnapshotClass构造元数据事件

添加元数据后,在DataHub上查看的效果如下:
点击"View In Mysql",即跳转到 externalUrl中定义的地址,即"https://blog.csdn.net/penriver/"
【DataHub】 现代数据栈的元数据平台--如何将自定义的元数据事件发送到DataHub_第1张图片

# -*- coding: utf-8 -*-
import datahub.emitter.mce_builder as builder
from datahub.emitter.rest_emitter import DatahubRestEmitter
from datahub.metadata.schema_classes import *

# 通过REST创建一个DataHub Emitter
emitter = DatahubRestEmitter(gms_server="http://172.25.21.188:8080", extra_headers={})

#测试连接
emitter.test_connection()

# 使用DatasetSnapshotClass构造元数据事件
def add_metadata_by_dataset_snapshot():
    dataset_snapshot = DatasetSnapshotClass(
        urn=builder.make_dataset_urn("mysql", "user_center.Person"),
        aspects=[StatusClass(removed=False)],
    )

    dataset_properties = DatasetPropertiesClass(
        description="用于存储人员记录表",
        customProperties={
            "governance": "ENABLED",
            "归属系统": "用户中心"
        },
        externalUrl="https://blog.csdn.net/penriver/"
    )
    dataset_snapshot.aspects.append(dataset_properties)

    schema_metadata = SchemaMetadataClass(
        schemaName="PersonSchema",
        platform=builder.make_data_platform_urn("mysql"),
        version=1,
        hash="",
        platformSchema=MySqlDDLClass(tableSchema=""),
        fields=[
            SchemaFieldClass(
                fieldPath="person_id",
                type=SchemaFieldDataTypeClass(type=NumberTypeClass()),
                nativeDataType="long",
                description="人员标识"
            ),
            SchemaFieldClass(
                fieldPath="person_name",
                type=SchemaFieldDataTypeClass(type=StringTypeClass()),
                nativeDataType="varchar(100)",
                description="人员姓名"
            ),
        ],
        primaryKeys=["person_id"]
    )

    dataset_snapshot.aspects.append(schema_metadata)

    mce = MetadataChangeEventClass(proposedSnapshot=dataset_snapshot)

    emitter.emit(mce)

add_metadata_by_dataset_snapshot()

使用MetadataChangeProposalWrapper构造元数据事件

添加元数据后,在DataHub上查看的效果如下:
注意:通过MetadataChangeProposalWrapper构造元数据事件,一次只能更新元数据的一个Aspect的信息,如datasetProperties、schemaMetadata
【DataHub】 现代数据栈的元数据平台--如何将自定义的元数据事件发送到DataHub_第2张图片

# -*- coding: utf-8 -*-
import datahub.emitter.mce_builder as builder
from datahub.emitter.mcp import MetadataChangeProposalWrapper
from datahub.emitter.rest_emitter import DatahubRestEmitter
from datahub.metadata.schema_classes import *

# 通过REST创建一个DataHub Emitter
emitter = DatahubRestEmitter(gms_server="http://172.25.21.22:8080", extra_headers={})

#测试连接
emitter.test_connection()

# 使用MetadataChangeProposalWrapper构造元数据事件
def add_metadata_by_me1():
    #构造一个数据集属性对象
    dataset_properties = DatasetPropertiesClass(description="用于存储人员记录表",
        customProperties={
             "governance": "ENABLED",
             "归属系统": "用户中心"
        })

    #构造一个MetadataChangeProposalWrapper对象
    metadata_event = MetadataChangeProposalWrapper(
        entityType="dataset",
        changeType=ChangeTypeClass.UPSERT,
        entityUrn=builder.make_dataset_urn("mysql", "user_center.Person2"),
        aspectName="datasetProperties",
        aspect=dataset_properties,
    )

    # emit 元数据,这是一个阻塞调用
    emitter.emit(metadata_event)

def add_metadata_by_me2():
    #构造一个数据集SchemaMetadataClass对象
    schema_metadata = SchemaMetadataClass(
        schemaName="Person2Schema",
        platform=builder.make_data_platform_urn("mysql"),
        version=1,
        hash="",
        platformSchema=MySqlDDLClass(tableSchema=""),
        fields=[
            SchemaFieldClass(
                fieldPath="person_id",
                type=SchemaFieldDataTypeClass(type=NumberTypeClass()),
                nativeDataType="long",
                description="人员标识"
            ),
            SchemaFieldClass(
                fieldPath="person_name",
                type=SchemaFieldDataTypeClass(type=StringTypeClass()),
                nativeDataType="varchar(100)",
                description="人员姓名"
            ),
        ],
        primaryKeys=["person_id"]
    )
    DatasetSnapshotClass
    #构造一个MetadataChangeProposalWrapper对象
    metadata_event = MetadataChangeProposalWrapper(
        entityType="dataset",
        changeType=ChangeTypeClass.UPSERT,
        entityUrn=builder.make_dataset_urn("mysql", "user_center.Person2"),
        aspectName="schemaMetadata",
        aspect=schema_metadata,
    )

    # emit 元数据,这是一个阻塞调用
    emitter.emit(metadata_event)

add_metadata_by_me1()
add_metadata_by_me2()

Java emitter

Java emitter 是基于Apache HttpClient库之上的一个轻度封装,它支持元数据的非阻塞emitter,并处理元数据 Aspect的JSON序列化细节。
构造REST Emitter 遵循基于lambda的流畅构建器模式【fluent builder pattern】。配置参数绝大部分与Python Emitter的配置一样。此外,您还可以通过将定制传递给HttpClient构建器来定制在底层构造的HttpClient。

它可以很容易地从基于JVM的系统发出元数据。例如,Spark的数据血缘集成使用Java emitter从Spark作业发出元数据事件。

Java包目前还不支持Kafka emitter,但官方已经计划在后续的版本中提供

Java emitter 中的创建一个maven项目,添加Datahub client依赖

        <dependency>
            <groupId>io.acrylgroupId>
            <artifactId>datahub-clientartifactId>
            <version>0.8.24version>
        dependency>
        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpasyncclientartifactId>
            <version>4.1.5version>
        dependency>
        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpcoreartifactId>
            <version>4.4.15version>
        dependency>

示例代码如下: 可以使用阻塞或非阻塞两种方式
注意:目前java emitter存在一些BUG【如属性是中文时,可能导致写入失败】,不如python emitter稳定可靠。

public class SimpleDemo {
    private static final Logger logger = LoggerFactory.getLogger(SimpleDemo.class);
    public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
        logger.info("创建RestEmitter");
        RestEmitter emitter = RestEmitter.create(b ->
                b.server("http://172.25.21.22:8080")
                        .timeoutSec(30)
                        .customizeHttpAsyncClient(c -> c.setConnectionTimeToLive(30, TimeUnit.SECONDS))
        );

        logger.info("创建mcpw");
        MetadataChangeProposalWrapper mcpw = MetadataChangeProposalWrapper.builder()
                .entityType("dataset")
                .entityUrn("urn:li:dataset:(urn:li:dataPlatform:mysql,user-center.Person3,PROD)")
                .upsert()
                .aspect(new DatasetProperties().setDescription("person table"))
                .build();

        // Blocking call using future
//        Future requestFuture = emitter.emit(mcpw, null);
//        MetadataWriteResponse mwr = requestFuture.get();
//        if (mwr.isSuccess()) {
//            logger.info(String.format("成功发送元数据事件: %s", mcpw.getEntityUrn()));
//        } else {
//            HttpResponse httpResponse = (HttpResponse) mwr.getUnderlyingResponse();
//            logger.info(String.format("失败发送元数据事件: %s, aspect: %s 状态码是: %d",
//                    mcpw.getEntityUrn(), mcpw.getAspectName(), httpResponse.getStatusLine().getStatusCode()));
//        }

        // Non-blocking using callback
        emitter.emit(mcpw, new Callback() {
            @Override
            public void onCompletion(MetadataWriteResponse response) {
                if (response.isSuccess()) {
                    logger.info(String.format("成功发送元数据事件: %s", mcpw.getEntityUrn()));
                } else {
                    // Get the underlying http response
                    HttpResponse httpResponse = (HttpResponse) response.getUnderlyingResponse();
                    logger.info(String.format("失败发送元数据事件: %s, aspect: %s 状态码是: %d",
                            mcpw.getEntityUrn(), mcpw.getAspectName(), httpResponse.getStatusLine().getStatusCode()));
                }
            }

            @Override
            public void onFailure(Throwable exception) {
                logger.info(
                        String.format("失败发送元数据事件: %s, aspect: %s 原因是: %s", mcpw.getEntityUrn(),
                                mcpw.getAspectName(), exception.getMessage()));
            }
        });
    }
}

参考

metadata-ingestion/as-a-library
metadata-ingestion/#using-as-a-library

你可能感兴趣的:(【BigData】,【数据治理】,java,kafka,开发语言,大数据,数据库)