Flink 使用介紹相關文檔目錄
背景
本篇接Flink 使用之 MySQL CDC。在這篇博客,我們解析CDC數據的時候用的是StringDebeziumDeserializationSchema
。實際上它僅僅調用接收到SourceRecord
的toString()
方法,將結果傳遞到下游。SourceRecord.toString()
返回的內容過多,層級較為復雜,給下游的解析造成了很大的困難。
為了解決這個問題,本篇為大家講解如何自定義DeserializationSchema
。
目標
通常來說,我們關心的CDC內容包含如下:
- 數據庫名
- 表名
- 操作類型(增刪改)
- 變更前數據
- 變更后數據
- 其他字段(例如變更時間等,根據業務需要添加)
下面例子中,我們手工將SourceRecord
解析為一個Map類型。
實現方式
實現自己的DeserializationSchema
,我們可以參考官方給出的StringDebeziumDeserializationSchema
的源代碼。如下所示:
public class StringDebeziumDeserializationSchema implements DebeziumDeserializationSchema<String> {
private static final long serialVersionUID = -3168848963265670603L;
public StringDebeziumDeserializationSchema() {
}
public void deserialize(SourceRecord record, Collector<String> out) throws Exception {
out.collect(record.toString());
}
public TypeInformation<String> getProducedType() {
return BasicTypeInfo.STRING_TYPE_INFO;
}
}
它繼承了DebeziumDeserializationSchema
,實現了2個方法:
-
deserialize
:反序列化的邏輯在此,CDC捕獲到的原始數據為SourceRecord
,我們將其解析之后,通過Collector收集,即可傳遞給下游。 -
getProducedType
:返回解析之后的數據類型。
我們自己的解析邏輯只需要實現這個接口,在deserialize
完成自己的解析邏輯即可。當然,這個例子中僅僅是為了print看起來直觀,將轉換后的map轉換為String后發往下游。實際生產中建議使用Java Bean,Scala Case Class或者JSON格式。
完整的代碼和關鍵點講解如下所示:
public class CustomDebeziumDeserializer implements DebeziumDeserializationSchema<String> {
@Override
public void deserialize(SourceRecord sourceRecord, Collector<String> collector) throws Exception {
Map<String, Object> parsedObject = new HashMap<>();
// 注意,SourceRecord中并沒有獲取操作類型的方法。獲取操作類型需要這么寫
Envelope.Operation operation = Envelope.operationFor(sourceRecord);
parsedObject.put("operation", operation.toString());
// topic返回的內容為:mysql_binlog_source.dbName.tableName,即數據源.數據庫名.表名
// 按照業務使用要求解析即可
if (null != sourceRecord.topic()) {
String[] splitTopic = sourceRecord.topic().split("\\.");
if (splitTopic.length == 3) {
parsedObject.put("database", splitTopic[1]);
parsedObject.put("table", splitTopic[2]);
}
}
// value返回sourceRecord中攜帶的數據,它是一個Struct類型
// Struct的類型為org.apache.kafka.connect.data.Struct
Struct value = (Struct) sourceRecord.value();
// 變更前后的數據位于value這個Struct中,名稱分別為before和after
Struct before = value.getStruct("before");
Struct after = value.getStruct("after");
// 對于新增或刪除的數據,before和after可能不存在,需要做null檢查
if (null != before) {
Map<String, Object> beforeMap = new HashMap<>();
// 獲取Struct中包含所有字段名可以使用struct.schema().fields()方法,遍歷即可
Schema beforeSchema = before.schema();
for (Field field : beforeSchema.fields()) {
beforeMap.put(field.name(), before.get(field));
}
parsedObject.put("before", beforeMap);
}
if (null != after) {
Map<String, Object> afterMap = new HashMap<>();
Schema afterSchema = after.schema();
for (Field field : afterSchema.fields()) {
afterMap.put(field.name(), after.get(field));
}
parsedObject.put("after", afterMap);
}
// 調用collector的collect方法,將轉換后的數據發往下游
collector.collect(parsedObject.toString());
}
@Override
public TypeInformation<String> getProducedType() {
return BasicTypeInfo.STRING_TYPE_INFO;
}
}
最后和Flink 使用之 MySQL CDC類似。編寫Flink主程序:
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 使用MySQLSource創建數據源
// 使用自己編寫的CustomDebeziumDeserializer替換掉官方提供的StringDebeziumDeserializationSchema
val sourceFunction = MySQLSource.builder().hostname("your-ip").port(3306)
.databaseList("demo").username("root").password("123456")
.deserializer(new CustomDebeziumDeserializer).build();
// 單并行度打印,避免輸出亂序
env.addSource(sourceFunction).print.setParallelism(1)
env.execute()