学习使用Apache Kafka、Kafka Connect、Debezium和ksqlDB的实时流ETL

79 阅读4分钟

正如你们大多数人已经知道的那样,ETL是Extract-Transform-Load的缩写,是将数据从一个源系统转移到另一个源系统的过程。首先,我们将阐明为什么我们需要将数据从一个点转移到另一个点;其次,我们将看看传统的方法;最后,我们将描述如何使用Apache Kafka、Kafka Connect、Debezium和ksqlDB建立一个实时流式ETL过程。

当我们建立我们的商业应用时,我们设计数据模型时考虑到我们应用的功能需求。我们不考虑任何形式的操作或分析报告要求。用于报告要求的数据模型应该是非规范化的,而用于应用程序操作的数据模型应该是大部分是规范化的。因此,为了报告或任何类型的分析目的,我们需要将我们的数据模型转换成非规范化的形式。

为了重新塑造我们的数据,我们需要将其转移到另一个数据库。有人可能会说,我们可以在同一个数据库中使用数据库视图或物化视图来重塑我们的数据,但报告数据库的配置可能与操作数据库不同,大多数操作数据库被配置为OLTP(事务性),而报告数据库被配置为OLAP(分析性)。此外,在操作性数据库上执行报告程序会减慢业务交易的速度,而且会导致业务流程的减慢,所以你的业务人员会对此感到不高兴。TLDR;如果你需要准备一份报告或者想在你的操作性数据库上做分析研究,你应该把你的数据转移到另一个数据库。

在行业中,人们大多分批从源系统中提取数据,在合理的时期内,大多是每天,但也可以是每小时或两三天一次。保持较短的周期可能会导致源系统的资源占用率较高,目标系统频繁中断;但是,保持较长的周期可能会导致目标系统出现最新的问题。因此,我们需要的是对源系统的性能造成最小的影响,并在较短的时间内或也许是实时地更新目标系统。

现在让我们来看看提议的架构。你可以在我的GitHub资源库中找到这个演示项目的完整源代码:https://github.com/dursunkoc/ksqlwithconnect。 我们将使用Debezium源连接器,从源系统中提取数据变化。

Debezium不是使用SQL来提取数据的。它使用数据库日志文件来跟踪数据库的变化,所以它对源系统的影响最小。关于Debezium的更多信息,请访问他们的网站

数据被提取出来后,我们需要Kafka Connect将其串联到Apache Kafka中,以便按照我们的要求玩弄它,重塑它。我们将使用ksqlDB,以便按照我们在目标系统中需要的方式来重塑原始数据。让我们考虑一个简单的订购系统数据库,其中我们有一个客户表,一个产品表和一个订单表,如下图所示。 image.png

现在,让我们考虑一下,我们需要提交一份关于订单的报告,在同一行中看到购买者的电子邮件和产品的名称。所以我们需要一个表,如下图所示。

image.png

客户列将包含客户的电子邮件,它存在于客户表的电子邮件字段中,而产品列将包含产品的名称,它存在于产品表的名称字段中。

首先,我们需要创建一个源连接器,从源数据库中提取数据。 在我们的例子中,源数据库是一个MySQL数据库,所以我们将使用Debezium MySQL源连接器,如下所示。

CREATE SOURCE CONNECTOR `mysql-connector` WITH(

    "connector.class"= 'io.debezium.connector.mysql.MySqlConnector',

    "tasks.max"= '1',

    "database.hostname"= 'mysql',

    "database.port"= '3306',

    "database.user"= 'root',

    "database.password"= 'debezium',

    "database.server.id"= '184054',

    "database.server.name"= 'dbserver1',

    "database.whitelist"= 'inventory',

    "table.whitelist"= 'inventory.customers,inventory.products,inventory.orders',

    "database.history.kafka.bootstrap.servers"= 'kafka:9092',

    "database.history.kafka.topic"= 'schema-changes.inventory',

    "transforms"= 'unwrap',

    "transforms.unwrap.type"= 'io.debezium.transforms.ExtractNewRecordState',

    "key.converter"= 'org.apache.kafka.connect.json.JsonConverter',

    "key.converter.schemas.enable"= 'false',

    "value.converter"= 'org.apache.kafka.connect.json.JsonConverter',

    "value.converter.schemas.enable"= 'false');

现在,我们将拥有来自源系统的表、客户、产品和订单的Kafka主题。

ksql> show topics;

 

 Kafka Topic                   | Partitions | Partition Replicas

-----------------------------------------------------------------

 dbserver1                     | 1          | 1

 dbserver1.inventory.customers | 1          | 1

 dbserver1.inventory.orders    | 1          | 1

 dbserver1.inventory.products  | 1          | 1

 default_ksql_processing_log   | 1          | 1

 my_connect_configs            | 1          | 1

 my_connect_offsets            | 25         | 1

 my_connect_statuses           | 5          | 1

 schema-changes.inventory      | 1          | 1

-----------------------------------------------------------------

现在,通过下面的脚本,我们将为订单创建一个ksqlDB流,将客户和产品数据与订单数据连接在一起。

CREATE STREAM S_CUSTOMER (ID INT,

                       FIRST_NAME string,

                       LAST_NAME string,

                       EMAIL string)

                 WITH (KAFKA_TOPIC='dbserver1.inventory.customers',

                       VALUE_FORMAT='json');

 

CREATE TABLE T_CUSTOMER

AS

    SELECT id,

           latest_by_offset(first_name) as fist_name,

           latest_by_offset(last_name) as last_name,

           latest_by_offset(email) as email

    FROM s_customer

    GROUP BY id

    EMIT CHANGES;

 

CREATE STREAM S_PRODUCT (ID INT,

                       NAME string,

                       description string,

                       weight DOUBLE)

                 WITH (KAFKA_TOPIC='dbserver1.inventory.products',

                       VALUE_FORMAT='json');

 

CREATE TABLE T_PRODUCT

AS

    SELECT id,

           latest_by_offset(name) as name,

           latest_by_offset(description) as description,

           latest_by_offset(weight) as weight

    FROM s_product

    GROUP BY id

    EMIT CHANGES;

 

CREATE STREAM s_order (

    order_number integer,

    order_date timestamp,

    purchaser integer,

    quantity integer,

    product_id integer) 

    WITH (KAFKA_TOPIC='dbserver1.inventory.orders',VALUE_FORMAT='json');

 

CREATE STREAM SA_ENRICHED_ORDER WITH (VALUE_FORMAT='AVRO') AS

   select o.order_number, o.quantity, p.name as product, c.email as customer, p.id as product_id, c.id as customer_id

     from s_order as o 

left join t_product as p on o.product_id = p.id

left join t_customer as c on o.purchaser = c.id

partition by o.order_number

emit changes;

最后,在JDBC水槽连接器的帮助下,我们将把我们丰富的订单表推送到PostgreSQL数据库中。

CREATE SINK CONNECTOR `postgres-sink` WITH(

    "connector.class"= 'io.confluent.connect.jdbc.JdbcSinkConnector',

    "tasks.max"= '1',

    "dialect.name"= 'PostgreSqlDatabaseDialect',

    "table.name.format"= 'ENRICHED_ORDER',

    "topics"= 'SA_ENRICHED_ORDER',

    "connection.url"= 'jdbc:postgresql://postgres:5432/inventory?user=postgresuser&password=postgrespw',

    "auto.create"= 'true',

    "insert.mode"= 'upsert',

    "pk.fields"= 'ORDER_NUMBER',

    "pk.mode"= 'record_key',

    "key.converter"= 'org.apache.kafka.connect.converters.IntegerConverter',

    "key.converter.schemas.enable" = 'false',

    "value.converter"= 'io.confluent.connect.avro.AvroConverter',

    "value.converter.schemas.enable" = 'true',

    "value.converter.schema.registry.url"= 'http://schema-registry:8081'

);

数据库 提取、转换、加载 HTTPS JSON 业务数据库 数据(计算) 数据模型(GIS) kafka 系统 数据类型