关于kafka-connect的一些理解

关于kafka-connect的一些理解

大家好,我是一拳就能打爆A柱的一拳超人

这个礼拜我去了解了kafka-connect,相信各位应该了解过Confluent,也可能用过一些功能。我之前使用Confluent是因为这个平台可以做自定义数据拉取,通过配置可以对许多数据源的数据做增量查询。但是经过这段时间的了解,我发现其实并不需要confluent平台,只需要其中的一个组件,也就是kafka-connect。kafka-connect-jdbc是基于kafka-connect开源项目开发出来的可以适配所有JDBC数据库的一个组件。接下来我将从kafka-connect的核心组件、kafka-connect-jdbc源码阅读两个方面给大家介绍我的收获。

1、kafka-connect的几个核心组件

​ 在查找kafka-connect-jdbc相关的博客的过程中,我了解到kafka-connect-jdbc其实是在kafka-connect这个开源组件的基础上开发出来的。所以我决定先去了解kafka-connect的内容,在JDBC Connect (Source and Sink)中有一些功能性的介绍,但是并没有内部组件的介绍。最后我在博客confluent kafka-connect-jdbc中找到其核心组件的介绍,kafka-connect-jdbc核心组件有七个:

  • source:负责将外部数据写入kafka的topic中。
  • sink: 负责从kafka中读取数据到自己需要的地方去,比如读取到HDFS,hbase等;可以接收数据,也可以接收模式信息 。
  • connectors: 通过管理任务来协调数据流的高级抽象。
  • Tasks: 数据写入kafka和从kafka中读出数据的具体实现,source和sink使用时都需要Task。
  • Workers: 运行connectors和tasks的进程 。
  • Converters: kafka connect转换器提供了一种机制,用于将数据从kafka connect使用的内部数据类型转换为表示为Avro、Protobuf或JSON模式的数据类型 。
  • Transforms: 一种轻量级数据调整的工具 。

​ 在Confluent对Kafka Connect的介绍中提到Kafka Connect是Apache Kafka的免费开源组件,并且在文章《Kafka Connect介绍以及在分布式部署模式下启动流程分析》中有介绍:

Kafka背后的公司Confluent鼓励社区创建更多的开源的Connector,将Kafka生态圈扩大,促进Kafka Connect的应用。 另外在Kafka Connect框架的支持下,我们还可自定义开发Connector关于如何开发一个定制的Connector,可参考《Connector Developer Guide》。

​ 也就是说,开发者可以在Kafka Connect的基础做定制的Connector,从而将各种数据源的数据连接到Kafka。所以我接下来的目标是了解Kafka Connect的开发,从而理解kafka-connect-jdbc的设计原理。

​ 由于Confluent的官方开发指南不是很好看,所以我找到一篇翻译版的博客《Kafka Connector开发指南》,指南中以Kafka Connect自带的本地文件Connector为案例简单介绍了connector编程入口以及各部分的功能。首先,kafka connect已经提供了很强大的支持,开发者只需要实现SourceConnector、SinkConnector、SourceTask和SinkTask即可。SourceConnector与SourceTask是一对,Connector是编程的入口(无论是Souce还是Sink),SouceConnector负责完成properties的配置和Task的定义和配置。

在SourceConnector中需要实现如下几个方法:

  • public ConfigDef config() : this is how we expose what properties the connector cares about
  • public void start(Map<String, String> props):Connector的生命周期中第一个被调用的方法,也是开发者设置kafka connect属性的地方。
  • public Class<? extends Task> taskClass():返回值代表Task应当对应的Connector。
  • public List<Map<String, String>> taskConfigs(int maxTasks):定义Task的扩展方式以及每个Task应具备的配置。
  • public void stop():在程序关闭或者崩溃时被调用,即生命周期结束的收尾函数。

在SourceTask中需要实现如下几个方法:

  • public void start(Map<String, String> props):与Connector类似,Task在start方法(生命周期第一个函数)中做参数配置。
  • public List poll() throws InterruptedException:poll方法是实际工作的方法,kafka会尽可能快地循环调用该方法。在这个方法中,开发者需要链接外部系统拉取数据,整理成SourceRecords列表返回给Connect框架。
  • public void stop():与Connector相同,生命周期结束的收尾函数。

2、阅读kafka-connect-jdbc源码

2.1 SourceConnector

​ 根据kafka-connect-jdbc的 JdbcSourceConnector我画出了下面这张图:

在这里插入图片描述

​ 经观察发现Connector的主要工作都将交由这几部分来完成:

  • JdbcSourceConnectorConfig 负责Connector可能需要的所有参数的定义。
  • CachedConnectionProvider 负责保存并提供JDBC的Connection。
  • TableMonitorThread 负责对whitelist中的表做监视。
  • DatabaseDialect 负责数据库方言相关的工作。

​ 在Connector生命周期中的第一个函数start函数中,除了将properties(由其他组件自动解析成map送入connector的start方法)中的参数取出设置相关参数外,还进行了以上几个类的设置:

// 数据库方言
dialect = DatabaseDialects.findBestFor(
    dbUrl,
    config
);
// 建立数据库连接
cachedConnectionProvider = connectionProvider(maxConnectionAttempts, connectionRetryBackoff);
// 开启表监视线程
tableMonitorThread = new TableMonitorThread(
    dialect,
    cachedConnectionProvider,
    context,
    tablePollMs,
    whitelistSet,
    blacklistSet
);
tableMonitorThread.start();

​ 经过start函数的设置,连接已经打开并且已经有线程负责监视表的变化了。在SourceConnector中还有一个比较长的函数taskConfigs,在这个函数中对SouceConnectorConfig的子类SourceTaskConfig添加了一些配置。

2.2 SourceTask

​ 同样的对SourceTask我也画了相应的图:

在这里插入图片描述

​ 在Task的start方法中,除了必要的参数的设置以及重要对象的构建(如cachedConnectionProvider、各种Querier),还对每张表的不同协议下的偏移量做了计算。

2.2.1 start

​ start函数的职责同样是做一些准备工作。首先,需要计算表在不同协议下的partition:

// 定义table和partition的map,list中装的是不同协议对应partition的map
Map<String, List<Map<String, String>>> partitionsByTableFqn = new HashMap<>();
// 经过一系列的验证参数后,对所有的表做循环,将不同表下不同协议下对应的partition都记录下来
for (String table : tables) {
    // Find possible partition maps for different offset protocols
    // We need to search by all offset protocol partition keys to support compatibility
    List<Map<String, String>> tablePartitions = possibleTablePartitions(table);
    partitions.addAll(tablePartitions);
    partitionsByTableFqn.put(table, tablePartitions);
}

​ 然后,在得到不同表下不同协议下对应的partition后,循环遍历每张表,计算出每张表在不同协议下的偏移量:

for (String tableOrQuery : tablesOrQuery) {
    final List<Map<String, String>> tablePartitionsToCheck;//装的是当前表在不同协议对应的分区
    final Map<String, String> partition;
    switch (queryMode) {
        case TABLE: // 在预设好的表查询模式(incrementing等)的情况,做参数校验
            if (validateNonNulls) {
                validateNonNullable(
                    mode,
                    tableOrQuery,
                    incrementingColumn,
                    timestampColumns
                );
            }
            // 得到表对应的List<Map<协议,partition>>
            tablePartitionsToCheck = partitionsByTableFqn.get(tableOrQuery);
            break;
        case QUERY: // 对自定义表查询模式,做相应的校验
            ...
        default:
            throw new ConnectException("Unexpected query mode: " + queryMode);
    }

    Map<String, Object> offset = null;
    if (offsets != null) {
        // 取出Map<协议,partition>
        for (Map<String, String> toCheckPartition : tablePartitionsToCheck) {
            // 取出协议A,partitionA下的偏移量
            // 而offsets是在context中保存的
            offset = offsets.get(toCheckPartition);
            if (offset != null) { // 能取出偏移量则协议正确
                log.info("Found offset {} for partition {}", offsets, toCheckPartition);
                break;
            }
        }
    }
    offset = computeInitialOffset(tableOrQuery, offset, timeZone); // 再次校验偏移量

    ... 
	// 根据不同的查询模式添加查询器,并且其顺序跟table在List中顺序相同,故一一对应
    if (mode.equals(JdbcSourceTaskConfig.MODE_BULK)) { 
        tableQueue.add(
            new BulkTableQuerier(
                dialect, 
                queryMode, 
                tableOrQuery, 
                topicPrefix, 
                suffix
            )
        );
    } else if (mode.equals(JdbcSourceTaskConfig.MODE_INCREMENTING)) {
        ...
    } else if (mode.equals(JdbcSourceTaskConfig.MODE_TIMESTAMP)) {
        ...
    } else if (mode.endsWith(JdbcSourceTaskConfig.MODE_TIMESTAMP_INCREMENTING)) {
        ...
    }
}

​ 上面这段代码的最终效果是通过所有表在不同协议下的partition计算出对应的偏移量,并且根据方言、查询模式偏移量等构造出查询器,通过查询器可进行后面的查询工作。

2.2.2 poll

​ poll的职责是做实际的数据拉取工作,通过查询start中配置好的querier来做数据的拉取,将结果放在List< SourceRecord>中集中返回给上层。由于poll函数会被connect高速反复地调用且查询器队列保存了查询顺序,所以实现对多个表的轮询。以下是对poll关键步骤的注释:

@Override
public List<SourceRecord> poll() throws InterruptedException {
    // 目前还不清楚这个对象作用
    Map<TableQuerier, Integer> consecutiveEmptyResults = tableQueue.stream().collect(
        Collectors.toMap(Function.identity(), (q) -> 0));
    while (running.get()) {
        // 取出查询器
        final TableQuerier querier = tableQueue.peek();
        ....
        // 查询到的结果集
        final List<SourceRecord> results = new ArrayList<>();
        try {
            log.debug("Checking for next block of results from {}", querier.toString());
            querier.maybeStartQuery(cachedConnectionProvider.getConnection());

            int batchMaxRows = config.getInt(JdbcSourceTaskConfig.BATCH_MAX_ROWS_CONFIG);
            boolean hadNext = true;
            // 将结果逐条查询并添加入结果集
            while (results.size() < batchMaxRows && (hadNext = querier.next())) {
                results.add(querier.extractRecord());
            }
            // 无法查到下一条后,将该查询器加入队列尾部
            if (!hadNext) {
                resetAndRequeueHead(querier);
            }

            if (results.isEmpty()) {
                // 对结果集在空集合下做处理
                ...
            } else {
                consecutiveEmptyResults.put(querier, 0);
            }
            // 将结果返回给上层
            return results;
        } catch (SQLException sqle) {
            ...
        } catch (Throwable t) {
            ...
        }
    }

    // Only in case of shutdown
    final TableQuerier querier = tableQueue.peek();
    if (querier != null) {
        resetAndRequeueHead(querier);
    }
    closeResources();
    return null;
}

​ 经过上面Task的两个关键方法以及Connector的分析,我了解到kafka-connect-jdbc两个核心实现类的工作流程。

总结

经过对kafka-connect-jdbc的学习,对kafka-connect的概念以及各组件有了一些了解。最后通过阅读kafka-connect-jdbc源码的Connector和Task实现类了解到其内部的运行机制。
kafka-connect-jdbc是针对所有jdbc数据库开发的连接器,他在不同模块都做了相应的抽象,最重要的是查询器(querier)以及方言(dialect),程序运行之后通过Connector对参数进行配置、校验并且做表监视,而Task则通过计算分区、偏移量进行下一步的数据拉取。而数据拉取的工作是交给querier来完成的,其背后关系到方言的转换、SQL的创建。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
kafka-connect-transform-kryptonite 是 Kafka Connect 的一个转换器插件。Kafka Connect 是一个可扩展的分布式数据集成工具,可用于在 Apache Kafka 和外部系统之间进行可靠和高效的数据流传输。而 Kafka Connect 转换器是用于对数据进行转换、过滤或处理的插件。 Kafka Connect 是一个开源的分布式数据集成框架,用于连接和处理来自各种数据源的数据,例如数据库、消息队列和文件系统等。它提供了一个统一的、可扩展的架构,允许用户将数据从多个来源导入到 Kafka 中,并将数据导出到多个目标系统。这为数据集成提供了更加灵活和可靠的方式,且能够满足各种实时数据处理的需求。 Kafka Connect 的一个关键特性是插件化的架构,使得用户可以根据自己的需求,选择和配置合适的插件。其中,kafka-connect-transform-kryptonite 插件就是其中之一。Kryptonite 可以理解为一种“解除”或“削弱”转换器,它可能采用一些特定的规则或算法,对输入的数据进行加工、转换或过滤。 使用 kafka-connect-transform-kryptonite 插件,我们可以根据具体的业务需求,对 Kafka 中的消息进行处理。例如,我们可以通过 Kryptonite 转换器,将消息中的某些字段进行加密,以保护敏感数据的安全;或者根据一些规则,对消息进行过滤和筛选,只保留我们关心的数据。 总之,kafka-connect-transform-kryptonite 是 Kafka Connect 提供的一个转换器插件,可以用于对数据进行加工、转换和过滤。通过这个插件,我们可以根据业务需求对 Kafka 中的消息进行定制化处理,以满足不同场景下的数据集成和处理需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值