深入分析Kafka Connect的Worker实现
根据Confluent官方文档Kafka Connect Concept的描述。Kafka Connect的Connectors和Tasks都是运行在进程中的逻辑工作单元。而这些工作进程称为Worker,Kafka Connect运行逻辑如下图所示:
从上图来看,一个Kafka Connect进程就是一个Woker。同一组Kafka Connect组成同一组Worker,分担属于该组的Connectors和Taskss运行工作。根据Distributed mode下Kafka Connect启动流程详解分析的启动过程来看,Kafka Connect源码中抽象了一个Worker类,在启动的时候会创建一个实例,并且运行。这篇文章就是深入分析Worker类的实现和如何启动运行分配给它的Connectors和Tasks。
Worker中的线程池
Woker中有一个线程池,用于运行Connectors和Tasks的工作的。Worker的公共构造函数如下:
从上面的构造函数可知,Worker的线程池是一个默认的缓存线程池。每次要运行一个Connector或Task任务,都会提交一个Runnable。线程池会随着运行的Connectors和Tasks数量增加。
Worker的启动
在Kafka Connect启动流程中,创建了Worker实例后会调用start
方法,启动Worker。Worker的start
方法实现如下:
Worker的start
方法比较简单,主要是启动offsetBackingStorage
。offsetBackingStorage
可用于Source Connector的任务,保存已经产生的offset信息。这样给需要保存已经产生的消息的信息的Source Connector提供了一个统一的机制,无需Source Connector另外设计一套。offsetBackingStorage
信息的持久化都是依赖Kafka的topic,与Consumer的offset保存原理一样。
Worker运行Connector
一般当我们希望向Kafka Connect提交新的Connector时候都会调用如下REST接口例子:
curl -i -X POST -H "Accept:application/json" -H "Content-Type:application/json" 192.168.39.213:8083/connectors -d '{
"name": "project-connector",
"config": {
"tasks.max": "1",
"connector.class": "io.debezium.connector.mysql.MySqlConnector",
"database.hostname": "127.0.0.1",
"database.port": "3306",
"database.user": "root",
"database.password": "123456",
"database.server.id": "100",
"database.server.name": "myServerName",
"database.include.list": "myDatabase",
"table.whitlelist" : "project,member",
"database.history.kafka.bootstrap.servers": "127.0.0.1:9092",
"database.history.kafka.topic": "debezium-mysql",
"include.schema.changes": "true"
}
}'
当Kafka Connect组的Leader收到这个Connector配置后,会保存到Config的topic中。这组Kafka Connect就开始新的Rebalance过程,当Rebalance结束后,这个Connector就会被分配给其中一个Kafka Connect运行。
Kafka Connect运行Connector时都会调用Worker.startConnector
方法:
public void startConnector(
String connName,
Map<String, String> connProps,
CloseableConnectorContext ctx,
ConnectorStatus.Listener statusListener,
TargetState initialState,
Callback<TargetState> onConnectorStateChange
) {
try (LoggingContext loggingContext = LoggingContext.forConnector(connName)) {
. . . . . . .
final WorkerConnector workerConnector;
ClassLoader savedLoader = plugins.currentThreadLoader();
try {
// 根据配置中Connector class类,从插件classLoader加载这个类
final String connClass = connProps.get(ConnectorConfig.CONNECTOR_CLASS_CONFIG);
ClassLoader connectorLoader = plugins.delegatingLoader().connectorLoader(connClass);
savedLoader = Plugins.compareAndSwapLoaders(connectorLoader);
log.info("Creating connector {} of type {}", connName, connClass);
// 创建该Connector类实例
final Connector connector = plugins.newConnector(connClass);
// 根据Connector类型不同创建不同的ConnectorConfig配置类
final ConnectorConfig connConfig = ConnectUtils.isSinkConnector(connector)
? new SinkConnectorConfig(plugins, connProps)
: new SourceConnectorConfig(plugins, connProps, config.topicCreationEnable())