Kafka Connector 编程入门

image.png

如果你之前使用过 Apache Kafka ®和 Confluent 生态系统,那么你很可能已经使用过 Kafka Connect将数据传输到Kafka 或 从Kafka中获取数据。尽管可用的连接器列表不断增加——无论是 Confluent 还是社区支持的⏤您可能任然会发现自己需要与别的技术集成,而这些技术却不存在现成可用的kafka连接器。但是不要气馁!你可以使用 Kafka Connect API 创建一个连接器,它提供了一种简单的方法来创建容错的 Kafka 生产者或消费者,以便将数据流传入Kafka或从Kafka传出。

本文将介绍 Kafka Connect 框架的基本概念和架构。然后,我们将分四个步骤来帮助你顺利开发一个Kafka 连接器。我们的讨论将主要集中在source连接器上,但涵盖的许多概念也将适用于sink连接器。我们还将讨论后续步骤,以了解有关 Kafka Connect 开发最佳实践的更多信息,以及利用 Confluent 的帮助来验证你的连接器并在Confluent Hub上发布。

什么是 Kafka Connect?

Kafka Connect 专门用于将数据复制进和复制出 Kafka。概括地说,连接器是一项管理任务及其配置的作业。在内部,Kafka Connect 创建了容错的 Kafka 生产者和消费者,跟踪他们写入或读取的 Kafka 记录的偏移量。

除此之外,Kafka 连接器还提供了许多强大的功能。它们可以轻松配置为将无法处理或无效的消息路由到死信队列,在消息由source连接器写入 Kafka 之前或在sink连接器从 Kafka 消费之前应用单个消息转换,与 Confluent Schema Registry 集成以实现自动模式注册和管理,并将数据转换为 Avro 或 JSON 等类型。通过利用现有连接器⏤例如,Confluent Hub上列出的连接器⏤开发人员可以快速创建容错数据管道,将数据从外部源可靠地传输到 Kafka 主题中或从 Kafka 主题到外部接收器,所有这些都只需配置并不需要写代码!

每个连接器实例都可以将其工作分解为多个task,从而使复制数据的task并行化并提供可扩展性。当连接器实例启动任务时,它会传递每个task所需的配置属性。该task将此配置以及它已生成或使用的记录的状态和最新偏移量存储在外部的 Kafka 主题中。由于task不存储任何状态,因此可以随时停止、启动或重新启动任务。新开始的task将简单地从 Kafka 获取最新的偏移量并继续它们的工作。

image.png

Kafka 连接器可以在独立模式或分布式模式下运行。在独立模式下,Kafka Connect 在单个worker上运行 ⏤ 即执行连接器及其任务的正在运行的 JVM 进程。在分布式模式下,连接器及其任务在多个worker之间进行平衡。一般建议以分布式模式运行 Kafka Connect,因为独立模式不提供容错。

要以分布式模式启动连接器,需要向 Kafka Connect REST API 发送 POST请求,参考文档中所述。此请求触发 Kafka Connect 以自动安排跨多个worker执行连接器和任务。在一个worker宕机或被添加到组中的情况下,worker将自动协调以重新平衡它们之间的连接器和任务。

Kafka 连接器入门

Kafka Connect 是 Apache Kafka 的一部分,但它本身不包括连接器。您可以单独下载连接器,也可以从Confluent Platform下载,其中包括 Apache Kafka 和许多连接器,例如 JDBC、Elasticsearch、HDFS、S3 和 JMS。启动这些连接器比较简单,只需要向Kafka Connect REST API 发送一个 POST请求并提交连接器所需的配置属性就可以了。为了与其他源或接收器集成,您可能会在Confluent Hub上找到适合您需求的连接器。

如果你想要集成的技术没有现成可用的 Kafka 连接器,本文将指导您完成开发具有此功能的 Kafka 连接器的第一步。正如我们将看到的,创建一个连接器只需要实现几个 Kafka Connect 接口。Kafka Connect 框架负责其余的工作,因此您可以专注于实现特定于您的集成的逻辑,而不会被样板代码和操作复杂性所困扰。

Kafka Connect API 允许您通过实现其提供的多个接口和抽象类来加入 Kafka Connect 框架的强大功能。例如,一个基本的源连接器将需要提供以下三个类的扩展:SourceConnectorSourceTaskAbstractConfig. 这些共同定义了自定义 Kafka 连接器的配置和运行时行为。在下边的部分中,我们将介绍让你启动并运行一个新的 Kafka 连接器的基本组件。

第 1 步:定义配置属性

当连接器启动时,它们会使用一些配置属性,这些配置属性定义了连接器及其任务如何与外部sink 或 source通信,设置并行任务的最大数量,指定将数据传入或传出的 Kafka 主题,并提供任何其他自定义连接器完成其工作可能需要的信息。

配置项的值基本上都是以String形式提供给连接器。例如,参见方法签名Connector#start


public abstract class Connector implements Versioned {
[...]
	public abstract void start(Map<String, String> props);
[...]
}

配置属性 在启动时传递给连接器,然后将它们转化为更合适的AbstractConfig子类。所以开发连接器的第一步是创建一个AbstractConfig的子类,它允许自定义类型以及每个属性的默认值、验证、建议值和文档。

例如,假设你正在编写一个源连接器来从云存储提供商流式传输数据。在启动此类连接器所需的配置属性中,您可能希望包含 Kafka 主题名称以生成记录,例如,要导入的对象的键前缀白名单(prefix.whitelist)。这是示例配置类:


public class CloudStorageSourceConnectorConfig extends AbstractConfig {

    public CloudStorageSourceConnectorConfig(Map originals) {
        super(configDef(), originals);
    }

    protected static ConfigDef configDef() {
        return new ConfigDef()
                .define("bucket",
                        ConfigDef.Type.STRING,
                        ConfigDef.Importance.HIGH,
                        "Name of the bucket to import objects from")
                .define("prefix.whitelist",
                        ConfigDef.Type.LIST,
                        ConfigDef.Importance.HIGH,
                        "Whitelist of object key prefixes")
                .define("topic",
                        ConfigDef.Type.STRING,
                        ConfigDef.Importance.HIGH,
                        "Name of Kafka topic to produce to");
    }
}


可以参考JdbcSourceConnectorConfig

请注意,在示例中,我们将prefix.whitelist属性定义为List类型。当我们将原始值的映射传递给父AbstractConfig类时,配置属性将根据配置定义解析为相应的类型。因此,我们稍后可以从连接器的配置实例中获取prefix.whitelist值作为 List,即使该值最初是作为逗号分隔的 String提供给连接器的,例如“path/to/file/1,path/to/file/2,path/to/file/3”.

至少,每个配置定义都需要一个配置键、配置值类型、重要性级别、记录配置属性的简要描述,在大多数情况下,还需要一个默认值。但是,您还应该利用更高级的功能,例如定义配置组的能力,传入将在启动时调用的验证器,提供向用户建议配置值的推荐器,以及指定配置的顺序或对其他配置的依赖。事实上,最好的做法是尽可能包含验证器、推荐器、组和默认值,以确保您的用户在配置错误时立即获得反馈,并且可以轻松理解可用的配置选项及其逻辑分组。

在创建了我们的配置类之后,我们现在可以将注意力转移到启动连接器上。start这是我们CloudStorageSourceConnector类中的示例实现:

public class CloudStorageSourceConnector extends SourceConnector {

    private CloudStorageSourceConnectorConfig connectorConfig;

    @Override
    public void start(Map<String, String> props) {
        this.connectorConfig = new CloudStorageConnectorConfig(props);
        this.configProps = Collections.unmodifiableMap(props);
    }

   [...]
}

当连接器启动时,我们的自定义配置类的一个实例被创建,它为 Kafka Connect 框架提供了一个配置定义。如果缺少任何必需的配置或提供的类型不正确,验证器将自动导致启动失败并显示适当的错误消息。

第 2 步:将配置属性传递给任务

下一步是实现该Connector#taskConfigs方法,该方法返回一个映射列表,其中包含每个任务将用于将数据流入或流出 Kafka 的配置属性:


public abstract class Connector implements Versioned {
[...]
	public abstract List<Map<String, String>> taskConfigs(int maxTasks);
[...]
}


该方法接受int并行运行的最大任务数的值,并从tasks.max启动时提供的配置属性中提取。

List返回的每个映射taskConfigs对应于任务使用的配置属性。根据您的连接器正在执行的工作类型,所有任务接收相同的配置属性可能是有意义的,或者您可能希望不同的任务实例获得不同的属性。例如,假设您希望将对象键前缀的数量除以在运行的任务实例的数量上均匀地流式传输数据。如果给定一个包含三个键前缀的白名单,则只为三个要为其导入对象的任务实例中的每一个提供一个键前缀。然后,每个任务都可以专注于其键具有特定前缀的对象的流数据,从而将工作拆分为并行任务。

在实现taskConfig方法时要牢记几个注意事项。首先,tasks.max提供配置属性以允许用户限制并行运行的任务数量。它提供了从taskConfig返回的列表大小的上限。其次,返回列表的大小将决定启动多少任务。例如,对于数据库连接器,您可能希望每个任务从单个表中提取数据。如果您的数据库相对简单并且只有两个表,那么您可以taskConfigs返回一个大小为 2 的列表,即使maxTasks传递给方法的值大于 2。另一方面,如果您有六个表但maxTasks值为 2,那么您将需要从三个表中提取每个任务。

为了帮助执行此分组,Kafka Connect API 提供了实用方法ConnectorUtils#groupPartitions,它将元素的目标列表拆分为所需数量的组。同样,在我们的云存储示例中,我们可以实现taskConfig获取 对象键前缀的白名单(prefix.whitelist),maxTasks根据 前缀白名单(prefix.whitelist)的值或大小划分该列表,并返回一个配置列表,每个配置包含不同的对象键要为其流式传输对象的任务的前缀。下面是一个示例实现:


    @Override
    public List<Map<String, String>> taskConfigs(int maxTasks) {
        List prefixes = connectorConfig.getList(PREFIX_WHITELIST_CONFIG);
        int numGroups = Math.min(prefixes.size(), maxTasks);
        List<List> groupedPrefixes = ConnectorUtils.groupPartitions(prefixes, numGroups);
        List<Map<String, String>> taskConfigs = new ArrayList<>(groupedPrefixes.size());
        
        for (List taskPrefixes : groupedPrefixes) {
            Map<String, String> taskProps = new HashMap<>(configProps);
            taskProps.put(TASK_PREFIXES, String.join(",", taskPrefixes));
            taskConfigs.add(taskProps);
        }

        return taskConfigs;
    }


在启动时,Kafka Connect 框架会将包含在由taskConfigs返回的列表中的每个配置映射传递给每个任务。

连接器还需要实现其他方法,但这些方法的实现相对简单。Connector#stop让您有机会在连接器停止之前关闭任何可能打开的资源。尽管它需要完成的工作很简单,但重要的是Connector#stop不要长时间阻止关闭过程。Connector#taskClass返回自定义任务的类名。Connector#config应该返回自定义配置类中定义的 ConfigDef。最后,Connector#version必须返回连接器的版本。

第3步:实现Task任务轮询

Connector类一样,Task 类包含startstopversion 三个抽象方法。然而,将数据流传输到 Kafka 的大部分业务逻辑都将发生在poll方法中,Kafka Connect 为每个任务不断调用该方法:

    public abstract List<SourceRecord> poll() throws InterruptedException;


可以参考JdbcSourceTask.java

poll 方法返回一个List<SourceRecord>。SourceRecord 主要用于存储 Connect记录的头信息、键和值,也存储元数据,例如源的分区和源的偏移量。

源的分区和源的偏移量只是一个Map,用于跟踪已复制到 Kafka 的源数据。在大多数情况下,源分区反映了允许任务专注于导入特定数据组的任务配置。

例如,我们的云存储源连接器基于对象键前缀白名单(prefix.whitelist)导入对象。在 Task#poll 的实现中,导入的对象包装在SourceRecord包含源的分区,该Map中包含有关记录来自哪个分区的信息。源分区可以存储任务用于导入对象的对象键前缀。SourceRecord实例还包含一个源偏移量,用于标识从源导入的对象。源偏移量可以包含云存储桶中对象的标识信息⏤完整的对象键名、版本 ID、最后修改的时间戳和其他此类字段。任务稍后可以使用源分区和偏移量来跟踪已经导入的对象。

Kafka Connect 框架自动将偏移量提交到offset.storage.topic属性配置的主题。当 Connect 工作者或任务重新启动时,它可以使用任务SourceTaskContext来获取OffsetStorageReader,它具有offset方法可以获取给定源分区记录的最新偏移量。然后,该任务可以使用偏移量和分区信息来继续从源导入数据,而无需复制或跳过记录。

第4步:创建监控线程

Kafka Connect REST API 包含一个用于修改连接器配置的端点。按照文档中的说明提交PUT请求,您的连接器和任务将在可用工作人员之间重新平衡,以确保配置更改不会导致节点之间的工作负载不均。

但是,您可能希望将连接器设计为能够获取源中的更改、获取新配置并在可用工作人员之间重新平衡工作负载,而无需手动向 Kafka Connect API 提交请求。监视外部源中可能需要重新配置并自动重新配置以适应这些变化的更改的连接器称为动态连接器。

要使您的连接器动态化,您需要创建一个单独的线程来监控更改,并在连接器启动时创建一个新的监控线程实例:

public class MySourceConnector extends SourceConnector {

    private MonitoringThread monitoringThread;

    @Override
    public void start(Map<String, String> props) {
        [...]
        monitoringThread = new MonitoringThread(context);
    }
    [...]
}

您的源连接器还需要将其传递ConnectorContext给监控线程。如果监视器检测到外部源发生变化,需要重新配置,它将调用ConnectorContext#requestTaskReconfiguration触发 Kafka Connect 框架以更新其任务配置。

由于更新的配置通常意味着对输入分区的更改,因此 Kafka Connect 框架还重新平衡了可用工作人员之间的工作负载。在启动时,源连接器可以将轮询间隔属性传递给可以在CountDownLatch. 这是一个示例实现,它在再次查询外部源以进行更改之前等待一定的毫秒数:


public class MonitoringThread extends Thread {

    [...]
    private final Long pollInterval;

    public MonitoringThread(ConnectorContext context, Long pollInterval) {
        [...]
        this.pollInterval = pollInterval;
    }

    @Override
    public void run() {
        while (shutdownLatch.getCount() > 0) {
            if (sourceHasChanged()) {
                context.requestTaskReconfiguration();
            }

            try {
                shutdownLatch.await(pollInterval, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                log.warn("MonitoringThread interrupted: ", e);
            }
        }
    }
    [...]
}


实现了一个在外部源发生变化时触发任务重新配置的监控线程,您现在拥有一个动态 Kafka 连接器!

下一步

尽管需要进一步完善才能拥有完全可以运行的连接器,但我们已经介绍了开始创建动态源连接器所需的主要组件。要了解有关 Kafka Connect 开发的更多信息,请参阅文档。还请务必查看 Robin Moffatt 的精彩演讲From Zero to Hero with Kafka Connect,其中介绍了如何使用 Kafka 连接器创建管道,将数据从数据库流式传输到 Kafka,然后再到 Elasticsearch,包括对可能出现的常见问题的讨论出现以及如何解决它们。

如果您有兴趣开发或提交连接器以在 Confluent Hub 上发布,那么Confluent 验证集成计划是获取有关开发和验证连接器的指导的绝佳资源。在那里,您会找到一个验证指南和清单,其中包含连接器开发最佳实践以及实现 Confluent 验证金牌状态所需的连接器功能和行为。验证指南是了解更多关于 Kafka Connect 开发的另一个重要资源。

原文地址

JDBC connect 源码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值