深入理解 Kafka Connect:轉換器和序列化

AI 前線導讀:Kafka Connect 是一個簡單但功能強大的工具,可用於 Kafka 和其他系統之間的集成。人們對 Kafka Connect 最常見的誤解之一是它的轉換器。這篇文章將告訴我們如何正確地使用消息的序列化格式,以及如何在 Kafka Connect 連接器中對其進行標準化。

Kafka Connect 是 Apache Kafka 的一部分,爲其他數據存儲和 Kafka 提供流式集成。對於數據工程師來說,他們只需要配置一下 JSON 文件就可以了。Kafka 提供了一些可用於常見數據存儲的連接器,如 JDBC、Elasticsearch、IBM MQ、S3 和 BigQuery,等等。

對於開發人員來說,Kafka Connect 提供了豐富的 API,如果有必要還可以開發其他連接器。除此之外,它還提供了用於配置和管理連接器的 REST API。

Kafka Connect 是一種模塊化組件,提供了一種非常強大的集成方法。一些關鍵組件包括:

  • 連接器——定義如何與數據存儲集成的 JAR 文件;
  • 轉換器——處理數據的序列化和反序列化;
  • 變換——可選的運行時消息操作。


人們對 Kafka Connect 最常見的誤解與數據的序列化有關。Kafka Connect 使用轉換器處理數據序列化。接下來讓我們看看它們是如何工作的,並說明如何解決一些常見問題。

Kafka 消息都是字節

Kafka 消息被保存在主題中,每條消息就是一個鍵值對。當它們存儲在 Kafka 中時,鍵和值都只是字節。Kafka 因此可以適用於各種場景,但這也意味着開發人員需要決定如何序列化數據。

在配置 Kafka Connect 時,序列化格式是最關鍵的配置選項之一。你需要確保從主題讀取數據時使用的序列化格式與寫入主題的序列化格式相同,否則就會出現混亂和錯誤!

image

序列化格式有很多種,常見的包括:

  • JSON;
  • Avro;
  • Protobuf;
  • 字符串分隔(如 CSV)。

選擇序列化格式

選擇序列化格式的一些指導原則:

  • schema。很多時候,你的數據都有對應的 schema。你可能不喜歡,但作爲開發人員,你有責任保留和傳播 schema。schema 爲服務之間提供了一種契約。某些消息格式(例如 Avro 和 Protobuf)具有強大的 schema 支持,而其他消息格式支持較少(JSON)或根本沒有(CVS)。
  • 生態系統兼容性。Avro 是 Confluent 平臺的一等公民,擁有來自 Confluent Schema Registry、Kafka Connect、KSQL 的原生支持。另一方面,Protobuf 依賴社區爲部分功能提供支持。
  • 消息大小。JSON 是純文本的,並且依賴了 Kafka 本身的壓縮機制,Avro 和 Protobuf 都是二進制格式,序列化的消息體積更小。
  • 語言支持。Avro 在 Java 領域得到了強大的支持,但如果你的公司不是基於 Java 的,那麼可能會覺得它不太好用。

如果目標系統使用 JSON,Kafka 主題也必須使用 JSON 嗎?

完全不需要這樣。從數據源讀取數據或將數據寫入外部數據存儲的格式不需要與 Kafka 消息的序列化格式一樣。

Kafka Connect 中的連接器負責從源數據存儲(例如數據庫)獲取數據,並以數據內部表示將數據傳給轉換器。然後,Kafka Connect 的轉換器將這些源數據對象序列化到主題上。

在使用 Kafka Connect 作爲接收器時剛好相反——轉換器將來自主題的數據反序列化爲內部表示,傳給連接器,以便能夠使用特定於目標的適當方法將數據寫入目標數據存儲。

也就是說,主題數據可以是 Avro 格式,當你將數據寫入 HDFS 時,指定接收器的連接器使用 HDFS 支持的格式即可。

配置轉換器

Kafka Connect 默認使用了 worker 級別的轉換器配置,連接器可以對其進行覆蓋。由於在整個管道中使用相同的序列化格式通常會更好,所以一般只需要在 worker 級別設置轉換器,而不需要在連接器中指定。但你可能需要從別人的主題拉取數據,而他們使了用不同的序列化格式——對於這種情況,你需要在連接器配置中設置轉換器。即使你在連接器的配置中進行了覆蓋,它仍然是執行實際任務的轉換器。

好的連接器一般不會序列化或反序列化存儲在 Kafka 中的消息,它會讓轉換器完成這項工作。

 

image

請記住,Kafka 消息是鍵值對字節,你需要使用 key.converter 和 value.converter 爲鍵和值指定轉換器。在某些情況下,你可以爲鍵和值使用不同的轉換器。

 

image

這是使用 String 轉換器的一個示例。

"key.converter": "org.apache.kafka.connect.storage.StringConverter",

有些轉換器有一些額外的配置。對於 Avro,你需要指定 Schema Registry。對於 JSON,你需要指定是否希望 Kafka Connect 將 schema 嵌入到 JSON 消息中。在指定特定於轉換器的配置時,請始終使用 key.converter. 或 value.converter. 前綴。例如,要將 Avro 用於消息載荷,你需要指定以下內容:
 

"value.converter": "io.confluent.connect.avro.AvroConverter",

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

常見的轉換器包括:

  • Avro——來自 Confluent 的開源項目
io.confluent.connect.avro.AvroConverter
  • String——Apache Kafka 的一部分
org.apache.kafka.connect.storage.StringConverter
  • JSON——Apache Kafka 的一部分
org.apache.kafka.connect.json.JsonConverter
  • ByteArray——Apache Kafka 的一部分
org.apache.kafka.connect.converters.ByteArrayConverter
  • Protobuf——來自社區的開源項目
com.blueapron.connect.protobuf.ProtobufConverter

JSON 和 schema

雖然 JSON 默認不支持嵌入 schema,但 Kafka Connect 提供了一種可以將 schema 嵌入到消息中的特定 JSON 格式。由於 schema 被包含在消息中,因此生成的消息大小可能會變大。

如果你正在設置 Kafka Connect 源,並希望 Kafka Connect 在寫入 Kafka 消息包含 schema,可以這樣:

value.converter=org.apache.kafka.connect.json.JsonConverter

 	
value.converter.schemas.enable=true

生成的 Kafka 消息看起來像下面這樣,其中包含 schema 和 payload 節點元素:

{	
  "schema": {
    "type": "struct",
    "fields": [
      {
        "type": "int64",
        "optional": false,
        "field": "registertime"
      },

      {
        "type": "string",
        "optional": false,
        "field": "userid"
      },

      {
        "type": "string",
        "optional": false,
        "field": "regionid"
      },

      {
        "type": "string",
        "optional": false,
        "field": "gender"
      }
    ],

    "optional": false,
    "name": "ksql.users"
  },

  "payload": {
    "registertime": 1493819497170,
    "userid": "User_1",
    "regionid": "Region_5",
    "gender": "MALE"
  }
}

請注意消息的大小,消息由 playload 和 schema 組成。每條消息中都會重複這些數據,這也就是爲什麼說 Avro 這樣的格式會更好,因爲它的 schema 是單獨存儲的,消息中只包含載荷(並進行了壓縮)。

如果你正在使用 Kafka Connect 消費 Kafka 主題中的 JSON 數據,那麼就需要知道數據是否包含了 schema。如果包含了,並且它的格式與上述的格式相同,那麼你可以這樣設置:
 

value.converter=org.apache.kafka.connect.json.JsonConverter

value.converter.schemas.enable=true

不過,如果你正在消費的 JSON 數據如果沒有 schema 加 payload 這樣的結構,例如:

{ 
  "registertime": 1489869013625,
  "userid": "User_1",
  "regionid": "Region_2",
  "gender": "OTHER"
}

那麼你必須通過設置 schemas.enable = false 告訴 Kafka Connect 不要查找 schema:

value.converter=org.apache.kafka.connect.json.JsonConverter

value.converter.schemas.enable=false

和之前一樣,轉換器配置選項(這裏是 schemas.enable)需要使用前綴 key.converter 或 value.converter。

常見錯誤

如果你錯誤地配置了轉換器,將會遇到以下的一些常見錯誤。這些消息將顯示在你爲 Kafka Connect 配置的接收器中,因爲你試圖在接收器中反序列化 Kafka 消息。這些錯誤會導致連接器失敗,主要錯誤消息如下所示:
 

ERROR WorkerSinkTask{id=sink-file-users-json-noschema-01-0} Task threw an uncaught and unrecoverable exception (org.apache.kafka.connect.runtime.WorkerTask)

org.apache.kafka.connect.errors.ConnectException: Tolerance exceeded in error handler

at org.apache.kafka.connect.runtime.errors.RetryWithToleranceOperator. execAndHandleError(RetryWithToleranceOperator.java:178)

at org.apache.kafka.connect.runtime.errors.RetryWithToleranceOperator.execute (RetryWithToleranceOperator.java:104)

在錯誤消息的後面,你將看到堆棧信息,描述了發生錯誤的原因。請注意,對於連接器中的任何致命錯誤,都會拋出上述異常,因此你可能會看到與序列化無關的錯誤。要快速查看錯誤配置可能會導致的錯誤,請參考下表:

image

問題:使用 JsonConverter 讀取非 JSON 數據

如果你的源主題上有非 JSON 數據,但嘗試使用 JsonConverter 讀取它,你將看到:

org.apache.kafka.connect.errors.DataException: Converting byte[] to Kafka Connect data failed due to serialization error:

……………

org.apache.kafka.common.errors.SerializationException: java.io.CharConversionException: Invalid UTF-32 character 0x1cfa7e2 (above 0x0010ffff) at char #1, byte #7)

這有可能是因爲源主題使用了 Avro 或其他格式。

解決方案:如果數據是 Avro 格式的,那麼將 Kafka Connect 接收器的配置改爲:

"value.converter": "io.confluent.connect.avro.AvroConverter",
"value.converter.schema.registry.url": "http://schema-registry:8081",

或者,如果主題數據是通過 Kafka Connect 填充的,那麼你也可以這麼做,讓上游源也發送 JSON 數據:

"value.converter": "org.apache.kafka.connect.json.JsonConverter",
"value.converter.schemas.enable": "false",

問題:使用 AvroConverter 讀取非 Avro 數據

這可能是我在 Confluent Community 郵件組和 Slack 組等地方經常看到的錯誤。當你嘗試使用 Avro 轉換器從非 Avro 主題讀取數據時,就會發生這種情況。這包括使用 Avro 序列化器而不是 Confluent Schema Registry 的 Avro 序列化器(它有自己的格式)寫入的數據。
 

org.apache.kafka.connect.errors.DataException: my-topic-name	
at io.confluent.connect.avro.AvroConverter.toConnectData(AvroConverter.java:97)

…

org.apache.kafka.common.errors.SerializationException: Error deserializing Avro message for id -1	
org.apache.kafka.common.errors.SerializationException: Unknown magic byte!

解決方案:檢查源主題的序列化格式,修改 Kafka Connect 接收器連接器,讓它使用正確的轉換器,或將上游格式切換爲 Avro。如果上游主題是通過 Kafka Connect 填充的,則可以按如下方式配置源連接器的轉換器:

"value.converter": "io.confluent.connect.avro.AvroConverter",
"value.converter.schema.registry.url": "http://schema-registry:8081",

問題:沒有使用預期的 schema/payload 結構讀取 JSON 消息

如前所述,Kafka Connect 支持包含載荷和 schema 的 JSON 消息。如果你嘗試讀取不包含這種結構的 JSON 數據,你將收到這個錯誤:

org.apache.kafka.connect.errors.DataException: JsonConverter with schemas.enable requires "schema" and "payload" fields and may not contain additional fields. If you are trying to deserialize plain JSON data, set schemas.enable=false in your converter configuration.

需要說明的是,當 schemas.enable=true 時,唯一有效的 JSON 結構需要包含 schema 和 payload 這兩個頂級元素(如上所示)。

如果你只有簡單的 JSON 數據,則應將連接器的配置改爲:

"value.converter": "org.apache.kafka.connect.json.JsonConverter",

"value.converter.schemas.enable": "false",

如果要在數據中包含 schema,可以使用 Avro(推薦),也可以修改上游的 Kafka Connect 配置,讓它在消息中包含 schema:

"value.converter": "org.apache.kafka.connect.json.JsonConverter",

"value.converter.schemas.enable": "true",

故障排除技巧
查看 Kafka Connect 日誌

要在 Kafka Connect 中查找錯誤日誌,你需要找到 Kafka Connect 工作程序的輸出。這個位置取決於你是如何啓動 Kafka Connect 的。有幾種方法可用於安裝 Kafka Connect,包括 Docker、Confluent CLI、systemd 和手動下載壓縮包。你可以這樣查找日誌的位置:

  • Docker:docker logs container_name;
  • Confluent CLI:confluent log connect;
  • systemd:日誌文件在 /var/log/confluent/kafka-connect;
  • 其他:默認情況下,Kafka Connect 將其輸出發送到 stdout,因此你可以在啓動 Kafka Connect 的終端中找到它們。

查看 Kafka Connect 配置文件

  • Docker——設置環境變量,例如在 Docker Compose 中:
CONNECT_KEY_CONVERTER: io.confluent.connect.avro.AvroConverter

CONNECT_KEY_CONVERTER_SCHEMA_REGISTRY_URL: 'http://schema-registry:8081'

CONNECT_VALUE_CONVERTER: io.confluent.connect.avro.AvroConverter

CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL: 'http://schema-registry:8081'
  • Confluent CLI——使用配置文件 etc/schema-registry/connect-avro-distributed.properties;
  • systemd(deb/rpm)——使用配置文件 /etc/kafka/connect-distributed.properties;
  • 其他——在啓動 Kafka Connect 時指定工作程序的屬性文件,例如:
$ cd confluent-5.0.0

$ ./bin/connect-distributed ./etc/kafka/connect-distributed.properties

檢查 Kafka 主題

假設我們遇到了上述當中的一個錯誤,並且想要解決 Kafka Connect 接收器無法從主題讀取數據的問題。

我們需要檢查正在被讀取的數據,並確保它使用了正確的序列化格式。另外,所有消息都必須使用這種格式,所以不要假設你現在正在以正確的格式向主題發送消息就不會出問題。Kafka Connect 和其他消費者也會從主題上讀取已有的消息。

下面,我將使用命令行進行故障排除,當然也可以使用其他的一些工具:

  • Confluent Control Center 提供了可視化檢查主題內容的功能;
  • KSQL 的 PRINT 命令將主題的內容打印到控制檯;
  • Confluent CLI 工具提供了 consume 命令,可用於讀取字符串和 Avro 數據。

如果你的數據是字符串或 JSON 格式

你可以使用控制檯工具,包括 kafkacat 和 kafka-console-consumer。我個人的偏好是使用 kafkacat:

$ kafkacat -b localhost:9092 -t users-json-noschema -C -c1
{"registertime":1493356576434,"userid":"User_8","regionid":"Region_2","gender":"MALE"}

你也可以使用 jq 驗證和格式化 JSON:

$ kafkacat -b localhost:9092 -t users-json-noschema -C -c1|jq '.'
{
  "registertime": 1493356576434,
  "userid": "User_8",
  "regionid": "Region_2",
  "gender": "MALE"
}

如果你得到一些“奇怪的”字符,你查看的很可能是二進制數據,這些數據是通過 Avro 或 Protobuf 寫入的:

$ kafkacat -b localhost:9092 -t users-avro -C -c1

ڝ���VUser_9Region_MALE

如果你的數據是 Avro 格式

你應該使用專爲讀取和反序列化 Avro 數據而設計的控制檯工具。我使用的是 kafka-avro-console-consumer。確保指定了正確的 Schema Registry URL:

$ kafka-avro-console-consumer --bootstrap-server localhost:9092 \

 	
    --property schema.registry.url=http://localhost:8081 \

 	
    --topic users-avro \

 	
    --from-beginning --max-messages 1

 	
{"registertime":1505213905022,"userid":"User_5","regionid":"Region_4","gender":"FEMALE"}

 和之前一樣,如果要格式化,可以使用 jq:

$ kafka-avro-console-consumer --bootstrap-server localhost:9092 \
     --property schema.registry.url=http://localhost:8081 \
     --topic users-avro \
     --from-beginning --max-messages 1 | \
     jq '.'
 	
{
  "registertime": 1505213905022,
  "userid": "User_5",
  "regionid": "Region_4",
  "gender": "FEMALE"

}

內部轉換器

在分佈式模式下運行時,Kafka Connect 使用 Kafka 來存儲有關其操作的元數據,包括連接器配置、偏移量等。

可以通過 internal.key.converter/internal.value.converter 讓這些 Kafka 使用不同的轉換器。不過這些設置只在內部使用,實際上從 Apache Kafka 2.0 開始就已被棄用。你不應該更改這些配置,從 Apache Kafka 2.0 版開始,如果你這麼做了將會收到警告。

將 schema 應用於沒有 schema 的消息

很多時候,Kafka Connect 會從已經存在 schema 的地方引入數據,並使用合適的序列化格式(例如 Avro)來保留這些 schema。然後,這些數據的所有下游用戶都可以使用這些 schema。但如果沒有提供顯式的 schema 該怎麼辦?

或許你正在使用 FileSourceConnector 從普通文件中讀取數據(不建議用於生產環境中,但可用於 PoC),或者正在使用 REST 連接器從 REST 端點提取數據。由於它們都沒有提供 schema,因此你需要聲明它。

有時候你只想傳遞你從源讀取的字節,並將它們保存在一個主題上。但大多數情況下,你需要 schema 來使用這些數據。在攝取時應用一次 schema,而不是將問題推到每個消費者,這纔是一種更好的處理方式。

你可以編寫自己的 Kafka Streams 應用程序,將 schema 應用於 Kafka 主題中的數據上,當然你也可以使用 KSQL。下面讓我們來看一下將 schema 應用於某些 CSV 數據的簡單示例。

假設我們有一個 Kafka 主題 testdata-csv,保存着一些 CSV 數據,看起來像這樣:

$ kafkacat -b localhost:9092 -t testdata-csv -C

1,Rick Astley,Never Gonna Give You Up
	
2,Johnny Cash,Ring of Fire

我們可以猜測它有三個字段,可能是:

  • ID
  • Artist
  • Song

如果我們將數據保留在這樣的主題中,那麼任何想要使用這些數據的應用程序——無論是 Kafka Connect 接收器還是自定義的 Kafka 應用程序——每次都需要都猜測它們的 schema 是什麼。或者,每個消費應用程序的開發人員都需要向提供數據的團隊確認 schema 是否發生變更。正如 Kafka 可以解耦系統一樣,這種 schema 依賴讓團隊之間也有了硬性耦合,這並不是一件好事。

因此,我們要做的是使用 KSQL 將 schema 應用於數據上,並使用一個新的派生主題來保存 schema。這樣你就可以通過 KSQL 檢查主題數據:

ksql> PRINT 'testdata-csv' FROM BEGINNING;

Format:STRING

11/6/18 2:41:23 PM UTC , NULL , 1,Rick Astley,Never Gonna Give You Up

11/6/18 2:41:23 PM UTC , NULL , 2,Johnny Cash,Ring of Fire

前兩個字段(11/6/18 2:41:23 PM UTC 和 NULL)分別是 Kafka 消息的時間戳和鍵。其餘字段來自 CSV 文件。現在讓我們用 KSQL 註冊這個主題並聲明 schema:
 

	
ksql> CREATE STREAM TESTDATA_CSV (ID INT, ARTIST VARCHAR, SONG VARCHAR) \

WITH (KAFKA_TOPIC='testdata-csv', VALUE_FORMAT='DELIMITED');

Message

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

Stream created

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

可以看到,KSQL 現在有一個數據流 schema:

	
ksql> DESCRIBE TESTDATA_CSV;
Name                 : TESTDATA_CSV
 Field   | Type

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

 ROWTIME | BIGINT (system)
 ROWKEY  | VARCHAR(STRING) (system)
 ID      | INTEGER
 ARTIST  | VARCHAR(STRING)
 SONG    | VARCHAR(STRING)

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

For runtime statistics and query details run: DESCRIBE EXTENDED <Stream,Table>;

可以通過查詢 KSQL 流來檢查數據是否符合預期。請注意,這個時候我們只是作爲現有 Kafka 主題的消費者——並沒有更改或複製任何數據。

ksql> SET 'auto.offset.reset' = 'earliest';

Successfully changed local property 'auto.offset.reset' from 'null' to 'earliest'

ksql> SELECT ID, ARTIST, SONG FROM TESTDATA_CSV;

1 | Rick Astley | Never Gonna Give You Up

2 | Johnny Cash | Ring of Fire

最後,創建一個新的 Kafka 主題,使用帶有 schema 的數據進行填充。KSQL 查詢是持續的,因此除了將現有的數據從源主題發送到目標主題之外,KSQL 還將向目標主題發送未來將生成的數據。

ksql> CREATE STREAM TESTDATA WITH (VALUE_FORMAT='AVRO') AS SELECT * FROM TESTDATA_CSV;

Message

----------------------------
Stream created and running
----------------------------

使用 Avro 控制檯消費者驗證數據:

$ kafka-avro-console-consumer --bootstrap-server localhost:9092 \
       --property schema.registry.url=http://localhost:8081 \
       --topic TESTDATA \
       --from-beginning | \
       jq '.'
{
  "ID": {
    "int": 1
},
  "ARTIST": {
    "string": "Rick Astley"
},
  "SONG": {
    "string": "Never Gonna Give You Up"
  }
}
[…]

你甚至可以在 Schema Registry 中查看已註冊的 schema:

$ curl -s http://localhost:8081/subjects/TESTDATA-value/versions/latest|jq '.schema|fromjson'
{
  "type": "record",
  "name": "KsqlDataSourceSchema",
  "namespace": "io.confluent.ksql.avro_schemas",
  "fields": [
    {
      "name": "ID",
      "type": [
        "null",
        "int"
      ],
      "default": null
    },
    {
      "name": "ARTIST",
      "type": [
        "null",
        "string"
      ],
      "default": null
    },
    {
      "name": "SONG",
      "type": [
        "null",
        "string"
      ],
      "default": null
    }
  ]
}

寫入原始主題(testdata-csv)的任何新消息都由 KSQL 自動處理,並以 Avro 格式寫入新的 TESTDATA 主題。現在,任何想要使用這些數據的應用程序或團隊都可以使用 TESTDATA 主題。你還可以更改主題的分區數、分區鍵和複製係數。


本文轉自:https://blog.csdn.net/D55dffdh/article/details/84929263

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章