本文介紹如何使用訊息佇列Kafka連接器。
背景資訊
Apache Kafka是一款開源的分布式訊息佇列系統,廣泛用於高效能資料處理、流式分析、Data Integration等巨量資料領域。Kafka連接器基於開源Apache Kafka用戶端,為阿里雲Realtime ComputeFlink提供高效能的資料吞吐、多種資料格式的讀寫和精確一次語義的支援。
|
類別 |
詳情 |
|
支援類型 |
源表和結果表,資料攝入目標端 |
|
運行模式 |
流模式 |
|
資料格式 |
|
|
特有監控指標 |
|
|
API種類 |
SQL,Datastream和資料攝入YAML |
|
是否支援更新或刪除結果表資料 |
不支援更新和刪除結果表資料,只支援插入資料。 說明
更新和刪除資料相關功能請參見Upsert Kafka。 |
前提條件
請根據需求選擇以下任意一種方式串連叢集:
-
串連阿里雲雲訊息佇列Kafka版叢集
-
Kafka叢集版本在0.11及以上。
-
ApsaraMQ for Kafka叢集已建立。詳情請參見建立資源。
-
Flink工作空間與Kafka叢集處於同一VPC內,且ApsaraMQ for Kafka已對Flink開放白名單,具體操作請參見配置白名單。
重要寫入阿里雲Kafka的限制:
-
阿里雲Kafka不支援zstd壓縮格式寫入。
-
阿里雲Kafka不支援等冪和事務寫入,無法使用Kafka結果表提供的精確一次語義exactly-once semantic功能。自Realtime Compute引擎VVR 8.0.0版本起,Kafka Connector 使用的開源Kafka Client版本升級到3.x版本,該Client的
properties.enable.idempotence屬性預設值從false變為true表示顯式開啟等冪寫入, 因此在使用Realtime Compute引擎VVR 8.0.0及以上版本寫入阿里雲Kafka時,需要在結果表中添加顯式配置properties.enable.idempotence=false以關閉等冪寫入功能,避免無法寫入阿里雲Kafka的問題。阿里雲Kafka的儲存引擎對比與功能限制參見儲存引擎對比。
-
-
串連自建Apache Kafka叢集
注意事項
目前不推薦使用事務寫入,這是 Flink 社區和 Kafka 社區的設計缺陷所致。當設定sink.delivery-guarantee = exactly-once,Kafka Connector 會啟用事務寫入,存在三個已知問題:
-
每個 Checkpoint 會產生一個 Transaction ID。如果 Checkpoint 間隔太短,Transaction ID會過多。Kafka 叢集的 Coordinator 可能因此記憶體不足,從而破壞 Kafka 叢集的穩定性。
-
每個事務會建立一個 Producer 執行個體。如果同時提交的事務太多,TaskManager 的記憶體可能耗盡,從而破壞 Flink 作業的穩定性。
-
多個 Flink 作業若使用相同的
sink.transactional-id-prefix,它們產生的事務 ID 可能衝突。一個作業寫入失敗時,會阻塞 Kafka 分區的 LSO(Log Start Offset)前進,這會影響所有消費者讀取該分區的資料。
如果你需要 Exactly-Once 語義,改用 Upsert Kafka 寫入主鍵表,並用主鍵保證等冪性。如果需要使用事務寫入,請參見EXACTLY_ONCE語義注意事項。
網路連接排查
Flink作業啟動時若報錯Timed out waiting for a node assignment,通常是因為 Flink 與 Kafka 之間網路不通。
Kafka 用戶端串連服務端如下所示:
-
用戶端用
bootstrap.servers中的地址串連 Kafka。 -
Kafka 返回叢集中各 broker 的中繼資料,包括它們的串連地址。
-
用戶端再用這些返回的地址串連各 broker,進行讀寫。
即使bootstrap.servers地址能通,若 Kafka 返回的 broker 地址錯誤,用戶端仍無法讀寫。這類問題常出現在使用代理、連接埠轉寄或專線等網路架構中。
排查步驟
訊息佇列Kafka
自建Kafka(ECS)
-
使用Flink開發控制台進行網路探測。
排除
bootstrap.servers地址連通性問題,確認內外網存取點正確性。 -
檢查安全性群組與白名單
-
ECS 安全性群組必須允許存取 Kafka 存取點連接埠(通常為 9092 或 9093)。
-
ECS 執行個體需將 Flink 所在VPC加入白名單,詳情請參見查看VPC網段。
-
-
配置排查
-
登入 Kafka 所用的 ZooKeeper 叢集,使用 zkCli.sh 或 zookeeper-shell.sh工具。
-
執行命令擷取 broker 中繼資料。例如:
get /brokers/ids/0。在返回結果的endpoints欄位中,找到 Kafka 向用戶端通告的地址。
-
Flink開發控制台進行網路探測,測試該地址是否可達。
說明-
若不可達,請 Kafka 營運人員檢查並修正
listeners和advertised.listeners配置,確保返回的地址對 Flink 可訪問。 -
更多關於Kafka用戶端與服務端的串連資訊,請參見Troubleshoot Connectivity。
-
-
-
檢查 SASL 配置(如啟用)
若使用 SASL_SSL存取點,必須在 Flink 作業中正確配置 JAAS、SSL 與 SASL 機制。缺少認證會導致串連在握手階段失敗,也可能表現為逾時,詳情請參見安全與認證。
SQL
Kafka連接器可以在SQL作業中使用,作為源表或者結果表。
文法結構
CREATE TABLE KafkaTable (
`user_id` BIGINT,
`item_id` BIGINT,
`behavior` STRING,
`ts` TIMESTAMP_LTZ(3) METADATA FROM 'timestamp' VIRTUAL
) WITH (
'connector' = 'kafka',
'topic' = 'user_behavior',
'properties.bootstrap.servers' = 'localhost:9092',
'properties.group.id' = 'testGroup',
'scan.startup.mode' = 'earliest-offset',
'format' = 'csv'
)
元資訊列
可以在源表和結果表中定義元資訊列,以擷取或寫入Kafka訊息的元資訊。例如,當WITH參數中定義了多個topic時,如果在Kafka源表中定義了元資訊列,那麼Flink讀取到的資料就會被標識是從哪個topic中讀取的資料。元資訊列的使用樣本如下。
CREATE TABLE kafka_source (
--讀取訊息所屬的topic作為`record_topic`欄位
`record_topic` STRING NOT NULL METADATA FROM 'topic' VIRTUAL,
--讀取ConsumerRecord中的時間戳記作為`ts`欄位
`ts` TIMESTAMP_LTZ(3) METADATA FROM 'timestamp' VIRTUAL,
--讀取訊息的offset作為`record_offset`欄位
`record_offset` BIGINT NOT NULL METADATA FROM 'offset' VIRTUAL,
...
) WITH (
'connector' = 'kafka',
...
);
CREATE TABLE kafka_sink (
--將`ts`欄位中的時間戳記作為ProducerRecord的時間戳記寫入Kafka
`ts` TIMESTAMP_LTZ(3) METADATA FROM 'timestamp' VIRTUAL,
...
) WITH (
'connector' = 'kafka',
...
);
下表列出了Kafka源表和結果表所支援的元資訊列。
|
Key |
資料類型 |
說明 |
源表或結果表 |
|
topic |
STRING NOT NULL METADATA VIRTUAL |
Kafka訊息所在的Topic名稱。 |
源表 |
|
partition |
INT NOT NULL METADATA VIRTUAL |
Kafka訊息所在的Partition ID。 |
源表 |
|
headers |
MAP<STRING, BYTES> NOT NULL METADATA VIRTUAL |
Kafka訊息的訊息頭(header)。 |
源表和結果表 |
|
leader-epoch |
INT NOT NULL METADATA VIRTUAL |
Kafka訊息的Leader epoch。 |
源表 |
|
offset |
BIGINT NOT NULL METADATA VIRTUAL |
Kafka訊息的位移量(offset)。 |
源表 |
|
timestamp |
TIMESTAMP(3) WITH LOCAL TIME ZONE NOT NULL METADATA VIRTUAL |
Kafka訊息的時間戳記。 |
源表和結果表 |
|
timestamp-type |
STRING NOT NULL METADATA VIRTUAL |
Kafka訊息的時間戳記類型:
|
源表 |
|
__raw_key__ |
STRING NOT NULL METADATA VIRTUAL |
Kafka原始訊息的Key欄位。 |
源表和結果表 說明
僅VVR 11.4及以上版本支援該參數。 |
|
__raw_value__ |
STRING NOT NULL METADATA VIRTUAL |
Kafka原始訊息的Value欄位。 |
源表和結果表 說明
僅VVR 11.4及以上版本支援該參數。 |
WITH參數
-
通用
參數
說明
資料類型
是否必填
預設值
備忘
connector
表類型。
String
是
無
固定值為kafka。
properties.bootstrap.servers
Kafka broker地址。
String
是
無
格式為host:port,host:port,host:port,以英文逗號(,)分割。
properties.*
對Kafka用戶端的直接配置。
String
否
無
Flink會將properties.首碼移除,並將剩餘的配置傳遞給Kafka用戶端。例如可以通過
'properties.allow.auto.create.topics'='false'來禁用自動建立topic。不能通過該方式修改以下配置,因為它們會被Kafka連接器覆蓋:
-
key.deserializer
-
value.deserializer
format
讀取或寫入Kafka訊息value部分時使用的格式。
String
否
無
支援的格式
-
csv
-
json
-
avro
-
debezium-json
-
canal-json
-
maxwell-json
-
avro-confluent
-
raw
說明更多format參數設定請參見Format參數。
key.format
讀取或寫入Kafka訊息key部分時使用的格式。
String
否
無
支援的格式
-
csv
-
json
-
avro
-
debezium-json
-
canal-json
-
maxwell-json
-
avro-confluent
-
raw
說明使用該配置時,key.options配置是必填的。
key.fields
Kafka訊息key部分對應的源表或結果表欄位。
String
否
無
多個欄位名以分號(;)分隔。例如
field1;field2key.fields-prefix
為所有Kafka訊息key部分指定自訂首碼,以避免與訊息value部分格式欄位重名。
String
否
無
該配置項僅用於源表和結果表的列名區分,解析和產生Kafka訊息key部分時,該首碼會被移除。
說明使用該配置時,
value.fields-include必須配置為EXCEPT_KEY。value.format
讀取或寫入Kafka訊息value部分時使用的格式。
String
否
無
該配置等同於
format,只能設定format或value.format中的一個。如果同時配置,value.format會覆蓋format。value.fields-include
在解析或產生Kafka訊息value部分時,是否要包含訊息key部分對應的欄位。
String
否
ALL
參數取值如下:
-
ALL(預設值):所有列都會作為Kafka訊息value部分處理 -
EXCEPT_KEY:除去key.fields定義的欄位,剩餘欄位作為Kafka訊息value部分處理
-
-
源表
參數
說明
資料類型
是否必填
預設值
備忘
topic
讀取的topic名稱。
String
否
無
以英文分號 (;) 分隔多個topic名稱,例如topic-1和topic-2
說明topic和topic-pattern兩個選項只能指定其中一個。
topic-pattern
匹配讀取topic名稱的Regex。所有匹配該Regex的topic在作業運行時均會被讀取。
String
否
無
樣本:
-
user_event_.*:匹配所有以user_event_開頭的 topic -
prod\.logs\..*:匹配prod.logs.首碼的 topic(.需轉義)
說明topic和topic-pattern兩個選項只能指定其中一個。
properties.group.id
消費組ID。
String
否
KafkaSource-{源表表名}
如果指定的group id為首次使用,則必須將properties.auto.offset.reset設定為earliest或latest以指定初次開機位點。
scan.startup.mode
Kafka讀取資料的啟動位點。
String
否
group-offsets
取值如下:
-
earliest-offset:從Kafka最早的分區開始讀取。 -
latest-offset:從Kafka最新位點開始讀取。 -
group-offsets(預設值):從指定的properties.group.id已提交的位點開始讀取。 -
timestamp:從scan.startup.timestamp-millis指定的時間戳記開始讀取。 -
specific-offsets:從scan.startup.specific-offsets指定的位移量開始讀取。
說明該參數在作業無狀態啟動時生效。作業在從checkpoint重啟或狀態恢複時,會優先使用狀態中儲存的進度恢複讀取。
scan.startup.specific-offsets
specific-offsets啟動模式下,指定每個分區的啟動位移量。
String
否
無
例如
partition:0,offset:42;partition:1,offset:300scan.startup.timestamp-millis
timestamp啟動模式下,指定啟動位點時間戳記。
Long
否
無
單位為毫秒
scan.topic-partition-discovery.interval
動態檢測Kafka topic和partition的時間間隔。
Duration
否
5分鐘
分區檢查間隔預設為5分鐘。需要顯式地設定分區檢查間隔為非正數才能關閉此功能。開啟動態分區發現後,Kafka Source 可以自動地發現新增的分區並自動讀取對應分區上的資料。在topic-pattern模式下,不僅讀取已有topic的新增分區資料,也會讀取符合正則匹配的新增topic的所有分區資料。
說明在Realtime Compute引擎VVR 6.0.x版本中,動態分區檢測預設為關閉。自8.0版本起該功能預設開啟,活動訊號間隔時間預設設定為5分鐘。
scan.header-filter
根據Kafka資料是否包含指定的訊息頭(Header)對資料進行條件過濾。
String
否
無
Header key和value使用冒號(:)分隔,多個header條件之間使用邏輯運算子(&、|)串連,支援取反邏輯運算子(!)。例如
depart:toy|depart:book&!env:test表示保留header中包含depart=toy或depart=book,且不包含env=test的Kafka資料。說明-
僅Realtime Compute引擎VVR 8.0.6及以上版本支援配置該參數。
-
暫不支援括弧運算。
-
邏輯運算順序為從左至右。
-
Header value會以UTF-8格式轉換為字串,與參數指定的header value進行比較。
scan.check.duplicated.group.id
是否檢查通過
properties.group.id指定的消費者組有重複。Boolean
否
false
參數取值如下:
-
true:在啟動作業前,系統會檢查消費者組是否存在重複。若發現重複,作業將報錯並停止運行,從而避免與現有消費者組產生衝突。
-
false:直接啟動作業,不檢查消費者組衝突。
說明僅VVR 6.0.4及以上版本支援該參數。
-
-
結果表
參數
說明
資料類型
是否必填
預設值
備忘
topic
寫入的topic名稱。
String
是
無
無
sink.partitioner
從Flink並發到Kafka分區的映射模式。
String
否
default
取值如下:
-
default(預設值):使用Kafka預設的分區模式
-
fixed:每個Flink並發對應一個固定的Kafka分區。
-
round-robin:Flink並發中的資料將被輪流分配至Kafka的各個分區。
-
自訂分區映射模式:如果fixed和round-robin不滿足需求,可以建立一個FlinkKafkaPartitioner的子類來自訂分區映射模式。例如org.mycompany.MyPartitioner
sink.delivery-guarantee
Kafka結果表的語義模式。
String
否
at-least-once
取值如下:
-
none:不保證任何語義,資料可能會丟失或重複。
-
at-least-once(預設值):保證資料不丟失,但可能會重複。
-
exactly-once:使用Kafka事務保證資料不會丟失和重複。
說明在使用exactly-once語義時,sink.transactional-id-prefix是必填的。
sink.transactional-id-prefix
在exactly-once語義下使用的Kafka事務ID首碼。
String
否
無
只有sink.delivery-guarantee配置為exactly-once時該配置才會生效。
sink.parallelism
Kafka結果表運算元的並發數。
Integer
否
無
上遊運算元的並發,由架構決定。
-
安全與認證
如果Kafka叢集要求安全連線或認證,請將相關的安全與認證配置添加properties.首碼後設定在WITH參數中。配置Kafka表以使用PLAIN作為SASL機制,並提供JAAS配置的樣本如下。
CREATE TABLE KafkaTable (
`user_id` BIGINT,
`item_id` BIGINT,
`behavior` STRING,
`ts` TIMESTAMP_LTZ(3) METADATA FROM 'timestamp'
) WITH (
'connector' = 'kafka',
...
'properties.security.protocol' = 'SASL_PLAINTEXT',
'properties.sasl.mechanism' = 'PLAIN',
'properties.sasl.jaas.config' = 'org.apache.flink.kafka.shaded.org.apache.kafka.common.security.plain.PlainLoginModule required username="username" password="password";'
)
使用SASL_SSL作為安全性通訊協定,並使用SCRAM-SHA-256作為SASL機制的樣本如下。
CREATE TABLE KafkaTable (
`user_id` BIGINT,
`item_id` BIGINT,
`behavior` STRING,
`ts` TIMESTAMP_LTZ(3) METADATA FROM 'timestamp'
) WITH (
'connector' = 'kafka',
...
'properties.security.protocol' = 'SASL_SSL',
/*SSL配置*/
/*佈建服務端提供的truststore (CA 憑證) 的路徑*/
/*檔案管理上傳的檔案會存放在/flink/usrlib/路徑下*/
'properties.ssl.truststore.location' = '/flink/usrlib/kafka.client.truststore.jks',
'properties.ssl.truststore.password' = 'test1234',
/*如果要求用戶端認證,則需要配置keystore (私密金鑰) 的路徑*/
'properties.ssl.keystore.location' = '/flink/usrlib/kafka.client.keystore.jks',
'properties.ssl.keystore.password' = 'test1234',
/*用戶端驗證伺服器位址的演算法,空值表示禁用伺服器位址驗證*/
'properties.ssl.endpoint.identification.algorithm' = '',
/*SASL配置*/
/*將SASL機制配置為as SCRAM-SHA-256*/
'properties.sasl.mechanism' = 'SCRAM-SHA-256',
/*配置JAAS*/
'properties.sasl.jaas.config' = 'org.apache.flink.kafka.shaded.org.apache.kafka.common.security.scram.ScramLoginModule required username="username" password="password";'
)
樣本中提到的CA認證和私密金鑰可使用Realtime Compute控制台的檔案管理功能上傳至平台。上傳後檔案存放在/flink/usrlib目錄下,需要使用的CA認證檔案名稱為my-truststore.jks,則上傳後在WITH參數中有兩種方式可以設定'properties.ssl.truststore.location'來使用該認證:
-
配置
'properties.ssl.truststore.location' = '/flink/usrlib/my-truststore.jks',採用這種方式Flink運行期間不需要動態下載OSS檔案,但是不支援偵錯模式。 -
Realtime Compute引擎版本為 VVR 11.5 及以上,可以配置
properties.ssl.truststore.location和properties.ssl.keystore.location為OSS絕對路徑地址,檔案路徑格式為oss://flink-fullymanaged-<工作空間ID>/artifacts/namespaces/<專案空間名稱>/<檔案名稱>。採用這種方式會在Flink運行期間動態下載OSS檔案,支援偵錯模式。
-
配置確認:上文中的樣本僅適用於大多數配置情況。在配置Kafka連接器前,請與Kafka服務端營運人員聯絡,以擷取正確的安全與認證配置資訊。
-
轉義說明:與開源Flink不同,Realtime ComputeFlink版的SQL編輯器預設已經對雙引號(")進行轉義處理,因此在配置
properties.sasl.jaas.config時無需對使用者名稱和密碼中的雙引號(")添加額外的轉義符(\)。 -
憑據託管:配置
properties.sasl.jaas.config時,其中的使用者名稱和密碼可填寫secret://kms.<secret-name>.<key>引用KMS託管的憑據,避免明文暴露。僅Realtime Compute引擎VVR 11.9及以上版本支援,配置步驟請參見KMS 憑據管理整合。
源表起始位點
啟動模式
Kafka源表可通過配置scan.startup.mode來指定初始讀取位點:
-
最早位點(earliest-offset):從當前分區的最早位點開始讀取。
-
最末尾位點(latest-offset):從當前分區的最末尾位點開始讀取。
-
已提交位點(group-offsets):從指定group id的已提交位點開始讀取,group id通過properties.group.id指定。
-
指定時間戳記(timestamp):從時間戳記大於等於指定時間的第一條訊息開始讀取,時間戳記通過scan.startup.timestamp-millis指定。
-
特錨點(specific-offsets):從指定的分區位點開始消費,位點通過scan.startup.specific-offsets指定。
-
如果不指定啟動位點,則預設會從已提交位點(group-offsets)啟動消費。
-
scan.startup.mode只針對無狀態啟動的作業生效,有狀態作業啟動時會從狀態中儲存的位點開始消費。
程式碼範例如下:
CREATE TEMPORARY TABLE kafka_source (
...
) WITH (
'connector' = 'kafka',
...
--從最早位點開始消費
'scan.startup.mode' = 'earliest-offset',
--從最末尾位點開始消費
'scan.startup.mode' = 'latest-offset',
--從消費者組"my-group"的已提交位點開始消費
'properties.group.id' = 'my-group',
'scan.startup.mode' = 'group-offsets',
'properties.auto.offset.reset' = 'earliest', -- 如果 "my-group" 為首次使用,則從最早位點開始消費
'properties.auto.offset.reset' = 'latest', -- 如果 "my-group" 為首次使用,則從最末尾位點開始消費
--從指定的毫秒時間戳記1655395200000開始消費
'scan.startup.mode' = 'timestamp',
'scan.startup.timestamp-millis' = '1655395200000',
--從指錨點開始消費
'scan.startup.mode' = 'specific-offsets',
'scan.startup.specific-offsets' = 'partition:0,offset:42;partition:1,offset:300'
);
起始位點優先順序
源表起始位點的優先順序為:
|
優先順序從高到低 |
Checkpoint或Savepoint中儲存的位點 |
|
Realtime Compute控制台作業啟動選擇的啟動時間 |
|
|
WITH參數中通過scan.startup.mode指定的啟動位點 |
|
|
未指定scan.startup.mode的情況下使用group-offsets,使用對應消費組的位點 |
在以上任何一個步驟中,如果位點到期或Kafka叢集發生問題等原因導致位點無效,則會使用properties.auto.offset.reset指定的策略進行位點重設,如果未設定該配置項,則會產生異常要求使用者介入。
一種常見情況是使用全新的group id開始消費。首先源表會向Kafka叢集查詢該group的已提交位點,由於該group id是第一次使用,不會查詢到有效位點,所以會通過properties.auto.offset.reset參數配置的策略進行重設。因此在使用全新group id進行消費時,必須配置properties.auto.offset.reset來指錨點重設策略。
源表位點提交
Kafka源表只在checkpoint成功後將當前消費位點提交至Kafka叢集。如果checkpoint間隔設定較長,在Kafka叢集側觀察到的消費位點會有延遲。在進行checkpoint時,Kafka源表會將當前讀取進度儲存在狀態中,並不依賴於提交到叢集上的位點進行故障恢複,提交位點僅僅是為了在Kafka側能夠監控到讀取進度,位點提交失敗不會對資料正確性產生任何影響。
結果表自訂分區器
如果內建的Kafka Producer分區模式無法滿足需求,可以實現自訂分區模式將資料寫入對應的分區。自訂分區器需要繼承FlinkKafkaPartitioner,開發完成後編譯JAR包,使用檔案管理功能上傳至Realtime Compute控制台。上傳並引用完成後,請在WITH參數中設定sink.partitioner參數,參數值為分區器完整的類路徑,如org.mycompany.MyPartitioner。
Kafka、Upsert Kafka或Kafka JSON catalog的選擇
Kafka是一種只能添加資料的訊息佇列系統,無法進行資料的更新和刪除操作,因此在流式SQL計算中無法處理上遊的CDC變更資料和彙總、聯合等運算元的回撤邏輯。如果需要將含有變更或回撤類型的資料寫入Kafka,請使用對變更資料進行特殊處理的Upsert Kafka結果表。
為了方便將上遊資料庫中一個或多個資料表中的變更資料批量同步到Kafka中,可以使用Kafka JSON catalog。如果Kafka中儲存的資料格式為JSON,使用Kafka JSON catalog可以省去定義schema和WITH參數的步驟。詳情可參見管理Kafka JSON Catalog。
使用樣本
樣本一:從Kafka中讀取資料後寫入Kafka
從名稱為源表的Topic中讀取Kafka資料,再寫入名稱為結果表的Topic,資料使用CSV格式。
CREATE TEMPORARY TABLE kafka_source (
id INT,
name STRING,
age INT
) WITH (
'connector' = 'kafka',
'topic' = 'source',
'properties.bootstrap.servers' = '<yourKafkaBrokers>',
'properties.group.id' = '<yourKafkaConsumerGroupId>',
'format' = 'csv'
);
CREATE TEMPORARY TABLE kafka_sink (
id INT,
name STRING,
age INT
) WITH (
'connector' = 'kafka',
'topic' = 'sink',
'properties.bootstrap.servers' = '<yourKafkaBrokers>',
'properties.group.id' = '<yourKafkaConsumerGroupId>',
'format' = 'csv'
);
INSERT INTO kafka_sink SELECT id, name, age FROM kafka_source;
樣本二:同步表結構以及資料
將Kafka Topic中的訊息即時同步到Hologres中。在該情況下,可以將Kafka訊息的offset和partition id作為主鍵,從而保證在Failover時,Hologres中不會有重複訊息。
CREATE TEMPORARY TABLE kafkaTable (
`offset` INT NOT NULL METADATA,
`part` BIGINT NOT NULL METADATA FROM 'partition',
PRIMARY KEY (`part`, `offset`) NOT ENFORCED
) WITH (
'connector' = 'kafka',
'properties.bootstrap.servers' = '<yourKafkaBrokers>',
'topic' = 'kafka_evolution_demo',
'scan.startup.mode' = 'earliest-offset',
'format' = 'json',
'json.infer-schema.flatten-nested-columns.enable' = 'true'
--可選,將嵌套列全部展開。
);
CREATE TABLE IF NOT EXISTS hologres.kafka.`sync_kafka`
WITH (
'connector' = 'hologres'
) AS TABLE vvp.`default`.kafkaTable;
樣本三:同步表結構以及Kafka訊息的key和value資料
Kafka訊息中的key部分已經儲存了相關資訊,可以同時同步Kafka中的key和value。
CREATE TEMPORARY TABLE kafkaTable (
`key_id` INT NOT NULL,
`val_name` VARCHAR(200)
) WITH (
'connector' = 'kafka',
'properties.bootstrap.servers' = '<yourKafkaBrokers>',
'topic' = 'kafka_evolution_demo',
'scan.startup.mode' = 'earliest-offset',
'key.format' = 'json',
'value.format' = 'json',
'key.fields' = 'key_id',
'key.fields-prefix' = 'key_',
'value.fields-prefix' = 'val_',
'value.fields-include' = 'EXCEPT_KEY'
);
CREATE TABLE IF NOT EXISTS hologres.kafka.`sync_kafka`(
WITH (
'connector' = 'hologres'
) AS TABLE vvp.`default`.kafkaTable;
Kafka訊息中的key部分不支援表結構變更和類型解析,需要手動聲明。
樣本四:同步表結構和資料並進行計算
在同步Kafka資料到Hologres時,往往需要一些輕量級的計算。
CREATE TEMPORARY TABLE kafkaTable (
`distinct_id` INT NOT NULL,
`properties` STRING,
`timestamp` TIMESTAMP_LTZ METADATA,
`date` AS CAST(`timestamp` AS DATE)
) WITH (
'connector' = 'kafka',
'properties.bootstrap.servers' = '<yourKafkaBrokers>',
'topic' = 'kafka_evolution_demo',
'scan.startup.mode' = 'earliest-offset',
'key.format' = 'json',
'value.format' = 'json',
'key.fields' = 'key_id',
'key.fields-prefix' = 'key_'
);
CREATE TABLE IF NOT EXISTS hologres.kafka.`sync_kafka` WITH (
'connector' = 'hologres'
) AS TABLE vvp.`default`.kafkaTable
ADD COLUMN
`order_id` AS COALESCE(JSON_VALUE(`properties`, '$.order_id'), 'default');
--使用COALESCE處理空值情況。
樣本五:嵌套JSON解析
JSON訊息樣本
{
"id": 101,
"name": "VVP",
"properties": {
"owner": "阿里雲",
"engine": "Flink"
}
}
為避免後續使用 JSON_VALUE(payload, '$.properties.owner') 等函數解析欄位,可直接在 Source DDL 中定義結構:
CREATE TEMPORARY TABLE kafka_source (
id VARCHAR,
`name` VARCHAR,
properties ROW<`owner` STRING, engine STRING>
) WITH (
'connector' = 'kafka',
'topic' = 'xxx',
'properties.bootstrap.servers' = 'xxx',
'scan.startup.mode' = 'earliest-offset',
'format' = 'json'
);
這樣,Flink會在讀取階段一次性將 JSON 解析為結構化欄位,後續 SQL 查詢直接使用 properties.owner,無需額外函數調用,整體效能更優。
EXACTLY_ONCE語義注意事項
-
配置消費者隔離等級
所有消費 Kafka 資料的應用必須設定 isolation.level:
-
read_committed:唯讀取已提交的資料。 -
read_uncommitted(預設):可讀取未提交的資料。
EXACTLY_ONCE 依賴
read_committed。否則消費者可能看到未提交資料,破壞一致性。 -
-
事務逾時與資料丟失
Flink 從 Checkpoint 恢複時,僅依賴該 Checkpoint 開始前已提交的事務。如果作業崩潰到重啟的時間超過 Kafka 事務逾時,Kafka 會自動中止事務,導致資料丟失。
-
Kafka Broker 預設
transaction.max.timeout.ms= 15 分鐘。 -
Flink Kafka Sink 預設設定
transaction.timeout.ms= 1 小時。 -
你必須在 Broker 端提高
transaction.max.timeout.ms,使其不小於 Flink 的設定。
-
-
Producer 池與 Checkpoint 並發
EXACTLY_ONCE 模式使用固定大小的 Kafka Producer 池。每個 Checkpoint 佔用池中的一個 Producer。如果並發 Checkpoint 數超過池大小,作業會失敗。
請根據最大並發 Checkpoint 數調整 Producer 池大小。
-
並行度縮容限制
如果作業在第一個 Checkpoint 前失敗,重啟後不會保留原有 Producer 池資訊。因此,在第一個 Checkpoint 完成前,不要縮減作業並行度。如必須縮容,並行度不得低於
FlinkKafkaProducer.SAFE_SCALE_DOWN_FACTOR。 -
事務阻塞讀取
在
read_committed模式下,任何未結束(未提交也未中止)的事務會阻塞整個 Topic 的讀取。例如:
-
事務 1 寫入資料。
-
事務 2 寫入並提交資料。
-
只要事務 1 未結束,事務 2 的資料對消費者不可見。
因此:
-
正常運行時,資料可見延遲約等於 Checkpoint 間隔。
-
作業失敗時,正在寫入的 Topic 會阻塞消費者,直到作業重啟或事務逾時,極端情況下,事務逾時甚至會影響讀取。
-