入門から熟練シリーズまでのフリンク (9)

11.7、関数

Flink SQL の関数は 2 つのカテゴリに分類できます。1 つは SQL の組み込みシステム関数で、関数名を通じて直接呼び出すことができ、COUNT() や CHAR_LENGTH() などの一般的な変換操作を実現できます。 、UPPER() などの前に使用しましたが、別のタイプの関数はユーザー定義関数 (UDF) で、使用する前にテーブル環境に登録する必要があります。

11.7.1. システム機能

システム関数は、組み込み関数とも呼ばれ、システムに事前に実装されている機能モジュールです。固定関数名を介して直接呼び出して、目的の変換操作を実現できます。

Flink SQL は多数のシステム関数を提供し、標準 SQL のほぼすべての操作をサポートしているため、SQL を使用してストリーム処理プログラムを作成するのに非常に便利です。Flink SQL のシステム関数は、主にスカラー関数 (Scalar Functions) と集計関数 (Aggregate Functions) の 2 つのカテゴリに分類できます。

11.7.1.1. スカラー関数

いわゆる「スカラー」とは、数値のみを持ち、方向を持たない量を指します。したがって、スカラー関数は、入力データに対して変換演算を実行して値を返すだけの関数を指します。ここでの入力データはテーブルに対応しており、テーブルは通常、データ行内の 1 つ以上のフィールドであるため、この操作はストリーム処理変換演算子のマップに少し似ています。また、入力パラメータを持たず、一意の結果を直接取得できる関数もスカラー関数です。

スカラー関数は、最も一般的で最も単純なタイプのシステム関数であり、その数は非常に多く、その多くは標準 SQL でも定義されています。したがって、ここでは、いくつかの一般的なタイプの機能のみをリストし、簡単な概要を説明します。特定のアプリケーションについては、公式 Web サイトで完全な機能リストを参照できます。

  • 比較関数 (Comparison Functions)
    比較関数とは、実際には 2 つの値の関係を判断し、ブール値を返すために使用される比較式です。この比較式は、2 つの値を <、>、= などの記号で接続するために使用したり、キーワードによって定義された特定の判定を行うことができます。例えば:
    • value1 = value2 は 2 つの値が等しいと判断します。
    • value1 <> value2 は 2 つの値が等しくないと判断します
    • value IS NOT NULL 値が空ではないことを判断します
  • 論理関数(Logical Function)
    論理関数とは、ブール値を論理積(AND)、または(OR)、ノット(NOT)を使って結合する論理式であり、判定文(IS、IS NOT)も使用できます。真実の判断; ブール値を返します。例えば:
    • boolean1 OR boolean2 ブール値 boolean1 とブール値 boolean2 は論理 OR をとります
    • boolean IS FALSE ブール値 boolean が false かどうかを判断します。
    • NOT boolean Boolean boolean は論理 NOT を取ります
  • 算術関数¶

算術記号にリンクされた演算や複雑な数学演算を含む算術計算を実行する関数。例えば:

  • numeric1 + numeric2 2 つの数値を加算します
  • POWER(numeric1, numeric2) べき乗、数値 numeric1 の numeric2 乗
  • RAND() は、間隔 (0.0, 1.0) で double 型の擬似乱数を返します。
  • 文字列関数¶

文字列操作のための関数。例えば:

  • string1 || string2 2 つの文字列の連結
  • UPPER(string) 文字列 string をすべて大文字に変換します。
  • CHAR_LENGTH(string) は文字列 string の長さを計算します。
  • 時間的機能

時間関連の操作を実行する関数。例えば:

  • DATE string 「yyyy-MM-dd」形式に従って文字列 string を解析し、戻り値の型は SQL Date です。
  • TIMESTAMP 文字列は「yyyy-MM-dd HH:mm:ss[.SSS]」の形式に従って解析され、戻り値の型は SQL タイムスタンプです。
  • CURRENT_TIME は、ローカル タイム ゾーンの現在時刻を返します。タイプは SQL 時間 (LOCALTIME と同等) です。
  • INTERVAL 文字列範囲 時間間隔を返します。文字列は値を表します。範囲には、DAY、MINUTE、DAT TO HOUR などの単位、または YEAR TO MONTH などの複合単位を指定できます。たとえば、「2 年 10 か月」は次のように記述できます。INTERVAL '2-10' YEAR TO MONTH

11.7.1.2. 集計関数

集計関数は、テーブル内の複数の行を入力として受け取り、集計操作のフィールドを抽出し、結果として一意の集計値を返す関数です。集計関数は広く使用されており、グループ集計、ウィンドウ集計、ウィンドウ (オーバー) 集計に関係なく、データに対する集計操作を同じ関数で定義できます。
標準 SQL の一般的な集計関数は Flink SQL でサポートされており、ストリーム処理アプリケーション向けにより強力な関数を提供するために現在拡張されています。例えば:

  • COUNT(*) は、すべての行の数、統計を返します。
  • SUM([ ALL | DISTINCT ] 式) フィールドを合計します。デフォルトでは、キーワード ALL は省略され、すべての行が合計されることを示します。DISTINCT が指定されている場合、データは重複排除され、各値は 1 回だけ重ねられます。
  • RANK() は、値のセット内の現在の値のランクを返します。
  • ROW_NUMBER() 一連の値を並べ替えた後、現在の値の行番号を返します。RANK() の関数と同様に、RANK() と ROW_NUMBER() は通常、OVER ウィンドウで使用されます。

11.7.1.2、ユーザー定義関数(UDF)

Flink のテーブル API と SQL は、抽象クラスの形式で定義されたさまざまなカスタム関数のインターフェイスを提供します。現在の UDF には主に次のカテゴリがあります。

  • スカラー関数: 入力スカラー値を新しいスカラー値に変換します。
  • テーブル関数: スカラー値を 1 つ以上の新しい行データに変換します。つまり、テーブルに展開します。
  • 集計関数: 複数行のデータのスカラー値を新しいスカラー値に変換します。
  • テーブル集計関数: 複数行のデータのスカラー値を 1 つ以上の新しいデータ行に変換します。
11.7.1.2.1、全体的な通話プロセス

コードでカスタム関数を使用するには、まず対応する UDF 抽象クラスの実装をカスタマイズし、この関数をテーブル環境に登録する必要があります。これにより、テーブル API および SQL で呼び出すことができるようになります。

  1. レジスタ機能

関数を登録するときは、テーブル環境の createTemporarySystemFunction() メソッドを呼び出し、登録された関数名と UDF クラスの Class オブジェクトを渡す必要があります。

// 注册函数
tableEnv.createTemporarySystemFunction("MyFunction", MyFunction.class);

カスタム UDF クラスは MyFunction と呼ばれ、上記の 4 つの UDF 抽象クラスの 1 つを具体的に実装する必要があり、MyFunction という名前の関数として環境に登録します。

ここでの createTemporarySystemFunction() メソッドは、「一時的なシステム関数」を作成することを意味するため、関数名 MyFunction はグローバルであり、システム関数として使用できます。createTemporaryFunction() メソッドも使用でき、登録された関数は現在の関数に依存します。データベース (database) とカタログ (catalog) なので、これはシステム関数ではなく、「カタログ関数」です。フルネームには、それが属するデータベースとカタログが含まれている必要があります。一般に、createTemporarySystemFunction() メソッドを直接使用して登録します。システム関数としての UDF。

  1. Table API を使用した関数の呼び出し
    Table API では、call() メソッドを使用してカスタム関数を呼び出す必要があります。
tableEnv.from("MyTable").select(call("MyFunction", $("myField")));

ここで、call() メソッドには 2 つのパラメータがあり、1 つは登録された関数名 MyFunction で、もう 1 つは関数が呼び出されるときのパラメータそのものです。ここでは、MyFunction が呼び出されるときに、渡される必要があるパラメーターが myField フィールドであることを定義します。

さらに、テーブル API では、関数を登録せずに、「インライン」モードで UDF を直接呼び出すことができます。

tableEnv.from("MyTable").select(call(SubstringFunction.class, $("myField")));

唯一の違いは、call() メソッドの最初のパラメーターが登録された関数名ではなく、直接関数クラスの Class オブジェクトになったことです。

  1. SQLで関数を呼び出す

関数をシステム関数として登録すると、SQL での呼び出しは組み込みシステム関数とまったく同じになります。

tableEnv.sqlQuery("SELECT MyFunction(myField) FROM MyTable");

SQL の呼び出し方法の方が便利であることがわかります。今後も SQL を例として UDF の使用法を紹介していきます。

11.7.1.2.2. スカラー関数

カスタム スカラー関数は、0、1、またはそれ以上のスカラー値をスカラー値に変換でき、対応する入力はデータ行のフィールドであり、出力は一意の値になります。したがって、入力テーブルと出力テーブルの行データの対応関係から見ると、スカラー関数は「1 対 1」の変換になります。

カスタム スカラー関数を実装するには、抽象クラス ScalarFunction を継承するクラスを定義し、eval() と呼ばれる評価メソッドを実装する必要があります。スカラー関数の動作は、評価メソッドの定義によって異なります。評価メソッドはパブリック (public) である必要があり、名前は eval である必要があります。評価メソッド eval は複数回オーバーロードでき、任意のデータ型を評価メソッドのパラメータおよび戻り値の型として使用できます。

ここで特別に説明する必要があるのは、eval() メソッドは ScalarFunction 抽象クラスで定義されていないため、コード内で直接オーバーライドすることはできませんが、Table API フレームワークの最下層では評価メソッドに名前を付ける必要があるということです。 eval() 。
ScalarFunction と他のすべての UDF インターフェイスは org.apache.flink.table.functions にあります。具体的な例を見てみましょう。カスタム ハッシュ関数 HashFunction を実装して、受信オブジェクトのハッシュ値を見つけます。

public static class HashFunction extends ScalarFunction {
    
    
 // 接受任意类型输入,返回 INT 型输出
 public int eval(@DataTypeHint(inputGroup = InputGroup.ANY) Object o) {
    
    
 return o.hashCode();
 }
}
// 注册函数
tableEnv.createTemporarySystemFunction("HashFunction", HashFunction.class);
// 在 SQL 里调用注册好的函数
tableEnv.sqlQuery("SELECT HashFunction(myField) FROM MyTable");

ここでは、ScalarFunction をカスタマイズし、eval() 評価メソッドを実装し、任意の型のオブジェクトを渡し、Int 型のハッシュ値を返しました。もちろん、特定のハッシュ演算は省略され、オブジェクトの hashCode() メソッドを直接呼び出すことができます。

また、Table API は関数の解析時に評価メソッド パラメーターの型参照を抽出する必要があるため、DataTypeHint(inputGroup = InputGroup.ANY) を使用して入力パラメーターの型をマークし、eval のパラメーターが次のいずれかであることを示します。あらゆる種類の。

11.7.1.2.3. テーブル関数

スカラー関数と同様に、テーブル関数は入力引数として 0、1、または複数のスカラー値を取ることができますが、異なる点は、任意の数の行を返すことができることです。「複数行のデータ」は実際にはテーブルを構成するものであるため、「テーブル関数」は「1対多」の変換関係であるテーブルを返す関数と考えることができます。

同様に、カスタム テーブル関数を実装するには、抽象クラス TableFunction を継承するカスタム クラスが必要であり、eval と呼ばれる評価メソッドが内部的に実装されている必要があります。スカラー関数とは異なり、TableFunction クラス自体には、テーブル関数によって返されるデータの型であるジェネリック パラメータ T があり、 eval() メソッドには戻り値の型がなく、内部に return ステートメントがありません。 collect()メソッドを呼び出して出力したい行データを送信します。

DataStream API の FlatMapFunction と ProcessFunction、それらの flatMap メソッドと processElement メソッドにも戻り値はなく、out.collect() を通じてデータをダウンストリームに送信します。

SQL でテーブル関数を呼び出すには、LATERAL TABLE() を使用して拡張された「サイド テーブル」を生成し、元のテーブルと結合する必要があります。ここでの結合操作は、FROM の後にカンマで 2 つのテーブルを区切るだけの直接クロス結合 (クロス結合) にすることも、条件として ON TRUE を指定する左結合 (LEFT JOIN) にすることもできます。

以下にテーブル関数の具体例を示します。文字列を (文字列, 長さ) タプルに変換できる、文字列を分離する関数 SplitFunction を実装しました。

// 注意这里的类型标注,输出是 Row 类型,Row 中包含两个字段:word 和 length。
@FunctionHint(output = @DataTypeHint("ROW<word STRING, length INT>"))
public static class SplitFunction extends TableFunction<Row> {
    
    
	 public void eval(String str) {
    
    
		 for (String s : str.split(" ")) {
    
    
			 // 使用 collect()方法发送一行数据
			 collect(Row.of(s, s.length()));
		 }
	 }
}
// 注册函数
tableEnv.createTemporarySystemFunction("SplitFunction", SplitFunction.class);
// 在 SQL 里调用注册好的函数
// 1. 交叉联结
tableEnv.sqlQuery( "SELECT myField, word, length " +
 "FROM MyTable, LATERAL TABLE(SplitFunction(myField))");
// 2. 带 ON TRUE 条件的左联结
tableEnv.sqlQuery(
	 "SELECT myField, word, length " +
	 "FROM MyTable " +
	 "LEFT JOIN LATERAL TABLE(SplitFunction(myField)) ON TRUE");
	// 重命名侧向表中的字段
tableEnv.sqlQuery(
	 "SELECT myField, newWord, newLength " +
	 "FROM MyTable " +
 	"LEFT JOIN LATERAL TABLE(SplitFunction(myField)) AS T(newWord, newLength) ON TRUE");

ここでは、テーブル関数の出力型を、取得したラテラル テーブルのデータ型である ROW として直接定義します。変換後のデータの各行は 1 行だけになります。SQL での呼び出しにはクロス結合と左結合の 2 つの方法を使用しました。また、サイド テーブルのフィールドの名前を変更することもできます。

11.7.1.2.4. 集計関数

ユーザー定義 AGGregate 関数 (UDAGG) は、1 つ以上のデータ行 (つまり、テーブル) をスカラー値に集約します。これは標準的な「多対 1」変換です。SUM()、MAX()、MIN()、AVG()、COUNT() などの集計関数の概念にはこれまでに何度も遭遇しました。組み込みの集計関数。また、システム関数を直接呼び出すだけでは解決できない要件がある場合、その機能を実現するために集計関数をカスタマイズする必要があります。

カスタム集計関数は、抽象クラス AggregateFunction を継承する必要があります。AggregateFunction には 2 つの汎用パラメータ <T, ACC> があり、T は集計出力の結果タイプを表し、ACC は集計の中間状態タイプを表します。Flink SQL の集計関数は次のように機能します。

  • まず、集計の中間結果を保存するために使用されるアキュムレータ (アキュムレータ) を作成する必要があります。これは DataStream API の AggregateFunction に非常に似ており、アキュムレータは集約状態と見なすことができます。createAccumulator() メソッドを呼び出して、空のアキュムレータを作成します。
  • 入力データの行ごとに、accumulator() メソッドが呼び出され、集計の中心的なプロセスであるアキュムレータが更新されます。
  • すべてのデータが処理されると、getValue() メソッドを呼び出すことによって最終結果が計算されて返されます。

したがって、各 AggregateFunction は次のメソッドを実装する必要があります。

  • createAccumulator()
    アキュムレータを作成するメソッドです。入力パラメータがない場合、戻り値の型はアキュムレータ型 ACC です。

  • accumulate() は
    集計計算の中核となるメソッドであり、データの行ごとに呼び出されます。最初のパラメータは確定しています。つまり、現在のアキュムレータです。そのタイプは ACC で、現在の集計の中間状態を示します。後のパラメータは、集計関数が呼び出されたときに渡されるパラメータであり、複数存在する可能性があり、タイプは次のとおりです。異なる場合もあります。このメソッドは主に集計の状態を更新するためのものであるため、戻り値の型はありません。accumulate() は前の評価メソッド eval() に似ていることに注意してください。また、基礎となるアーキテクチャでも必要です。パブリックである必要があり、メソッド名は累積する必要があり、直接オーバーライドすることはできません。手動でのみ実装してください。

  • getValue()
    最終的な戻り結果を取得するメソッドです。入力パラメータは ACC タイプのアキュムレータで、出力タイプは T です。複雑な型に遭遇した場合、Flink の型推定では正しい結果が得られない場合があります。そのため、AggregateFunction は、アキュムレータの型と返される結果 (getAccumulatorType() と getResultType() の 2 つのメソッドで指定) を具体的に宣言することもできます。

上記の方法に加えて、いくつかの方法がオプションです。これらのメソッドの中には、クエリをより効率的にできるものもありますが、特定のシナリオでは実装する必要があるものもあります。たとえば、セッション ウィンドウが集約されている場合、merge() メソッドを実装する必要があります。これはアキュムレータのマージ操作を定義します。また、このメソッドは一部のシナリオの最適化にも役立ちます。また、集約関数がOVER ウィンドウ集約では、データを確実に取り出せるように retract() メソッドを実装する必要があります。

replaceAccumulator() メソッドはアキュムレータをリセットします。これは一部のバッチ処理シナリオで役立ちます。AggregateFunction のすべてのメソッドは静的 (static) ではなくパブリック (public) である必要があり、名前は上記とまったく同じである必要があります。メソッド createAccumulator 、 getValue 、 getResultType および getAccumulatorType は抽象クラス AggregateFunction で定義されており、オーバーライドできますが、他のメソッドは基礎となるアーキテクチャによって合意されたメソッドです。

たとえば、学生のスコア テーブル ScoreTable から各学生の加重平均スコアを計算したいとします。加重平均を計算するには、入力の各行から 2 つの値 (計算するスコアとその重み) をパラメーターとして抽出する必要があります。集計プロセスでは、アキュムレータ (アキュムレータ) に現在の加重合計と現在のデータ数を保存する必要があります。これは 2 つのタプルで表すことも、sum と count の 2 つの属性を含むクラス WeightedAvgAccum を個別に定義し、そのオブジェクト インスタンスを集計アキュムレータとして使用することもできます。具体的なコードは次のとおりです。

// 累加器类型定义
public static class WeightedAvgAccumulator {
    
    
 public long sum = 0; // 加权和
 public int count = 0; // 数据个数
}
// 自定义聚合函数,输出为长整型的平均值,累加器类型为 WeightedAvgAccumulator
public static class WeightedAvg extends AggregateFunction<Long, WeightedAvgAccumulator> {
    
    
	 @Override
	 public WeightedAvgAccumulator createAccumulator() {
    
    
	 		return new WeightedAvgAccumulator(); // 创建累加器
	 }
	 @Override
	 public Long getValue(WeightedAvgAccumulator acc) {
    
    
		 if (acc.count == 0) {
    
    
		 	return null; // 防止除数为 0
		 } else {
    
    
			 return acc.sum / acc.count; // 计算平均值并返回
		 }
 	}
 // 累加计算方法,每来一行数据都会调用
 public void accumulate(WeightedAvgAccumulator acc, Long iValue, Integer iWeight) {
    
    
			 acc.sum += iValue * iWeight;
			 acc.count += iWeight;
		 }
	}
// 注册自定义聚合函数
tableEnv.createTemporarySystemFunction("WeightedAvg", WeightedAvg.class);
// 调用函数计算加权平均值
Table result = tableEnv.sqlQuery("SELECT student, WeightedAvg(score, weight) FROM ScoreTable GROUP BY student" );

集計関数のaccumulate()メソッドには3つの入力パラメータがあります。1 つ目は WeightedAvgAccum タイプのアキュムレータで、他の 2 つは関数の呼び出し時に入力されるフィールドです (計算される値 ivalue と対応する重み iweight)。

11.7.1.2.5. テーブル集計関数

カスタム テーブル集計関数は、抽象クラス TableAggregateFunction を継承する必要があります。TableAggregateFunction の構造と原理は AggregateFunction とよく似ており、2 つの汎用パラメータ <T, ACC> も持ち、ACC タイプのアキュムレータ (アキュムレータ) を使用して集計の中間結果を格納します。集計関数に実装する必要がある 3 つのメソッドは、TableAggregateFunction にも実装する必要があります。

  • createAccumulator()
    はアキュムレータを作成するメソッドであり、AggregateFunction で使用されるものと同じです。
  • accumulate()
    は集計計算の中核となるメソッドであり、AggregateFunction での使用法と同じです。
  • EmitValue()は、
    入力行をすべて処理した後に最終的な計算結果を出力するメソッドです。このメソッドは、AggregateFunction の getValue() メソッドに対応します。違いは、emitValue には出力タイプがなく、2 つの入力パラメータがあることです。1 つ目は ACC タイプのアキュムレータで、2 つ目は出力データ出力の「コレクタ」です。 、そのタイプは収集です。したがって、テーブル集計関数の出力データは直接返されるのではなく、 out.collect() メソッドを呼び出し、複数回呼び出すことで複数行のデータを出力できる点は、テーブル関数とよく似ています。さらに、emitValue() は抽象クラスで定義されていないため、オーバーライドできず、手動で実装する必要があります。

テーブル集計関数が取得するのはテーブルであるため、ストリーム処理で連続クエリを実行する場合は、毎回このテーブルの出力を再計算する必要があります。データを入力した後、結果テーブルの 1 つまたは複数の行だけが更新される (Update) 場合、テーブル全体を再計算してすべてを出力するのは明らかに効率的ではありません。処理効率を向上させるために、TableAggregateFunction には EmitUpdateWithRetract() メソッドも用意されています。このメソッドは、結果テーブルが変更されたときに古いデータを「撤回」し、新しいデータを送信することで段階的に更新できます。EmitValue() メソッドと EmitUpdateWithRetract() メソッドの両方が定義されている場合、更新時に EmitUpdateWithRetract() が最初に呼び出されます。

テーブル集計関数は比較的複雑で、一般的なアプリケーション シナリオは上位 N クエリです。たとえば、一連のデータを並べ替えた後、上位 2 つを選択したいとします。これは、最も単純な TOP-2 クエリです。スレッド システム関数がない場合は、テーブル集計関数をカスタマイズしてこの機能を実現できます。アキュムレータは 2 つの最大の現在値を保存できる必要があります。新しいデータが到着するたびに、accumulate() メソッドでそれを比較および更新し、最後に EmitValue() で out.collect() を 2 回呼び出して、上位 2 つを収集します。データ出力。具体的なコードは次のとおりです。

// 聚合累加器的类型定义,包含最大的第一和第二两个数据
public static class Top2Accumulator {
    
    
 public Integer first;
 public Integer second;
}
// 自定义表聚合函数,查询一组数中最大的两个,返回值为(数值,排名)的二元组
public static class Top2 extends TableAggregateFunction<Tuple2<Integer, Integer>, 
Top2Accumulator> {
    
    
	 @Override
	 public Top2Accumulator createAccumulator() {
    
    
		 Top2Accumulator acc = new Top2Accumulator();
		 acc.first = Integer.MIN_VALUE; // 为方便比较,初始值给最小值
		 acc.second = Integer.MIN_VALUE;
		 return acc;
	 }
 // 每来一个数据调用一次,判断是否更新累加器
 public void accumulate(Top2Accumulator acc, Integer value) {
    
    
	 if (value > acc.first) {
    
    
		 acc.second = acc.first;
		 acc.first = value;
	 } else if (value > acc.second) {
    
    
		 acc.second = value;
	 }
 }
 // 输出(数值,排名)的二元组,输出两行数据
 public void emitValue(Top2Accumulator acc, Collector<Tuple2<Integer, Integer>> out) {
    
    
		 if (acc.first != Integer.MIN_VALUE) {
    
    
		 	out.collect(Tuple2.of(acc.first, 1));
		 }
		 if (acc.second != Integer.MIN_VALUE) {
    
    
		 	out.collect(Tuple2.of(acc.second, 2));
		 }
	 }
}

現在、SQL でテーブル集計関数を直接使用する方法はないため、テーブル API を使用して以下を呼び出す必要があります。

// 注册表聚合函数函数
tableEnv.createTemporarySystemFunction("Top2", Top2.class);
// 在 Table API 中调用函数
tableEnv.from("MyTable")
 .groupBy($("myField"))
 .flatAggregate(call("Top2", $("value")).as("value", "rank"))
 .select($("myField"), $("value"), $("rank"));

ここでは flatAggregate() メソッドが使用されています。これは、テーブル集計関数を呼び出すために特別に使用されるインターフェイスです。myField フィールドに従って MyTable 内のデータをグループ化して集計し、値が最大の 2 つを数えます。集計結果の 2 つのフィールドの名前を value とrank に変更し、select() を使用して抽出します。

11.9. 外部システムへの接続

11.9.1、カフカ

Kafka の SQL コネクタは、Kafka のトピック (トピック) からテーブルにデータを読み取ることができ、また、テーブル データを Kafka のトピックに書き込むこともできます。つまり、テーブルの作成時にコネクタを Kafka として指定すると、そのテーブルを入力テーブルと出力テーブルの両方として使用できます。

11.9.1.1. 依存関係の導入

Flink プログラムで Kafka コネクタを使用するには、次の依存関係を導入する必要があります。

<dependency>
 <groupId>org.apache.flink</groupId>
 <artifactId>flink-connector-kafka_${
    
    scala.binary.version}</artifactId>
 <version>${
    
    flink.version}</version>
</dependency>

ここで紹介する Flink コネクタと Kafka コネクタは、以前の DataStream API で紹介したコネクタと同じです。SQL クライアントで Kafka コネクタを使用する場合は、対応する jar パッケージをダウンロードして lib ディレクトリに配置する必要もあります。さらに、Flink は、CSV、JSON、Avro、Parquet などのさまざまなコネクタ用の一連の「テーブル形式」を提供します。これらのテーブル形式は、最下層に格納されているバイナリ データとテーブルの列の間の変換方法を定義します。これは、テーブルのシリアル化ツールに相当します。Kafka では、CSV、JSON、Avro などの主要な形式がサポートされていますが、Kafka コネクタで構成された形式に応じて、対応する依存関係のサポートを導入する必要がある場合があります。CSV を例に挙げます。

<dependency>
 <groupId>org.apache.flink</groupId>
 <artifactId>flink-csv</artifactId>
 <version>${
    
    flink.version}</version>
</dependency>

SQL クライアントには CSV と JSON のサポートが組み込まれているため、使用時に導入する必要はありません。サポートが組み込まれていない形式 (Avro など) の場合は、対応する jar パッケージをダウンロードする必要があります。

11.9.1.2. Kafka に接続するテーブルの作成

Kafka テーブルへの接続を作成するには、CREATE TABLE の DDL の WITH 句でコネクタを Kafka として指定し、必要な構成パラメータを定義する必要があります。具体的な例を次に示します。

CREATE TABLE KafkaTable (
`user` STRING,
 `url` STRING,
 `ts` TIMESTAMP(3) METADATA FROM 'timestamp'
) WITH (
 'connector' = 'kafka',
 'topic' = 'events',
 'properties.bootstrap.servers' = 'localhost:9092',
 'properties.group.id' = 'testGroup',
 'scan.startup.mode' = 'earliest-offset',
 'format' = 'csv'
)

これは、Kafka コネクタに対応するトピック (トピック)、Kafka サーバー、コンシューマ グループ ID、コンシューマ起動モード、およびテーブル形式を定義します。KafkaTable のフィールドに ts があり、その宣言で METADATA FROM が使用されていることに注意してください。これは、Kafka コネクタが生成したメタデータ「タイムスタンプ」で構成される「メタデータ列」を意味します。ここでのタイムスタンプは実際には Kafka のデータに付属するタイムスタンプであり、それをメタデータとして直接抽出し、新しいフィールド ts に変換します。

11.9.1.3、カフカのアップサート

通常の状況では、Kafka はデータの順序を維持するメッセージ キューであり、テーブルの追加専用モードに対応して、読み取りと書き込みの両方がストリーミング データである必要があります。更新操作 (グループ化や集計など) を含む結果テーブルを Kafka に書き込みたい場合、Kafka は取り消し (retract) または更新 (upsert) メッセージを認識できないため、例外が発生します。

この問題を解決するために、Flink は特別に「Upsert Kafka」(Upsert Kafka) コネクタを追加します。このコネクタは、更新挿入 (UPSERT) の形式での Kafka トピックへのデータの読み取りと書き込みをサポートします。具体的には、Upsert Kafka コネクタは変更ログ (changlog) ストリームを処理します。TableSource として使用される場合、コネクタはトピック内の読み取りデータ (キー、値) を現在のキーのデータ値の更新 (UPDATE) として解釈します。つまり、テーブルソースに対応するデータ行を検索します。動的テーブルのキーを取得し、値を最新の値に変換します。Update は Upsert 操作であるため、キーに対応する行がない場合は INSERT 操作も実行されます。さらに、値が空 (null) の場合、コネクターはこのデータをキーに対応する行に対する DELETE 操作として解釈します。

TableSink として使用される場合、Upsert Kafka コネクタは更新操作の結果テーブルを変更ログ ストリームに変換します。挿入 (INSERT) または更新 (UPDATE_AFTER) されたデータが見つかった場合、それは追加 (add) メッセージに相当し、通常は Kafka トピックに直接書き込まれます。削除 (DELETE) または更新前のデータの場合は、これは、メッセージを撤回(撤回)し、値が空(null)のデータを Kafka に書き込むことに相当します。Flink はキー (キー) の値に従ってデータを分割するため、同じキーの更新メッセージと削除メッセージが同じパーティションに分類されることを保証できます。
Upsert Kafka テーブルの作成と使用の例を次に示します。

CREATE TABLE pageviews_per_region (
 user_region STRING,
 pv BIGINT,
 uv BIGINT,
 PRIMARY KEY (user_region) NOT ENFORCED
) WITH (
 'connector' = 'upsert-kafka',
 'topic' = 'pageviews_per_region',
 'properties.bootstrap.servers' = '...',
 'key.format' = 'avro',
 'value.format' = 'avro'
);
CREATE TABLE pageviews (
 user_id BIGINT,
 page_id BIGINT,
 viewtime TIMESTAMP,
 user_region STRING,
 WATERMARK FOR viewtime AS viewtime - INTERVAL '2' SECOND
) WITH (
 'connector' = 'kafka',
 'topic' = 'pageviews',
 'properties.bootstrap.servers' = '...',
 'format' = 'json'
);
-- 计算 pv、uv 并插入到 upsert-kafka 表中
INSERT INTO pageviews_per_region
SELECT
 user_region,
 COUNT(*),
 COUNT(DISTINCT user_id)
FROM pageviews
GROUP BY user_region;

ここでは、Kafka テーブルのページビューからデータを読み取り、各領域の PV (すべてのビュー) と UV (ユーザーの重複排除) をカウントします。これはグループ化と集計のための更新クエリであり、結果のテーブルはデータを継続的に更新します。

結果テーブルを Kafka の pageviews_per_region トピックに書き込むために、Upsert Kafka テーブルを定義します。このテーブルでは、PRIMARY KEY を使用してフィールド内の主キーを指定し、WITH 句でキーと値のシリアル化形式を指定する必要があります。

11.9.2、ファイルシステム

もう 1 つの非常に一般的な外部システムのタイプは、ファイル システム (ファイル システム) です。Flink は、ローカルまたは分散ファイル システムからのデータの読み取りと書き込みをサポートするファイル システム コネクタを提供します。このコネクタは Flink に組み込まれているため、使用するために追加の依存関係は必要ありません。
ファイルシステムへの接続の例を次に示します。

CREATE TABLE MyTable (
 column_name1 INT,
 column_name2 STRING,
 ...
 part_name1 INT,
 part_name2 STRING
) PARTITIONED BY (part_name1, part_name2) WITH (
 'connector' = 'filesystem', -- 连接器类型
 'path' = '...', -- 文件路径
 'format' = '...' -- 文件格式
)

ここでは、データを分割するために WITH の前に PARTITIONED BY が使用されています。ファイル システム コネクタは、パーティション ファイルへのアクセスをサポートします。

11.9.3、JDBC

リレーショナル データ テーブル自体は SQL が最初に適用される場所であるため、テーブル データをリレーショナル データベースに直接読み書きできることも期待しています。Flink が提供する JDBC コネクタは、MySQL、PostgreSQL、Derby などの JDBC ドライバー (ドライバー) を介して、任意のリレーショナル データベースにデータを読み書きできます。
TableSink としてデータベースにデータを書き込む場合、操作モードは、テーブルを作成する DDL が主キー (主キー) を定義しているかどうかによって異なります。主キーがある場合、JDBC コネクタは Upsert モードで実行され、指定されたキー (キー) に従って UPDATE および DELETE 操作を外部データベースに送信できます。主キーが定義されていない場合は、Append で実行されます。モードでは、更新および削除操作はサポートされません。

11.9.3.1. 依存関係の導入

Flink プログラムで JDBC コネクタを使用するには、次の依存関係を導入する必要があります。

<dependency>
 <groupId>org.apache.flink</groupId>
 <artifactId>flink-connector-jdbc_${
    
    scala.binary.version}</artifactId>
 <version>${
    
    flink.version}</version>
</dependency>

さらに、特定のデータベースに接続するために、MySQL などの関連ドライバーの依存関係もインポートします。

<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>5.1.38</version>
</dependency>

ここで紹介するドライバーのバージョンは 5.1.38 ですが、読者は自分の MySQL バージョンに応じて選択できます。

11.9.3.2. JDBCテーブルの作成

JDBC テーブルを作成する方法は、以前の Upsert Kafka と似ています。具体的な例を次に示します。

-- 创建一张连接到 MySQL 的 表
CREATE TABLE MyTable (
 id BIGINT,
 name STRING,
 age INT,
 status BOOLEAN,
 PRIMARY KEY (id) NOT ENFORCED
) WITH (
 'connector' = 'jdbc',
 'url' = 'jdbc:mysql://localhost:3306/mydatabase',
 'table-name' = 'users'
);
-- 将另一张表 T 的数据写入到 MyTable 表中
INSERT INTO MyTable
SELECT id, name, age, status FROM T;

ここでは、テーブル作成の DDL で主キーが定義されているため、データは Upsert モードで MySQL テーブルに書き込まれ、MySQL への接続は WITH 句の URL を通じて定義されます。なお、MySQL上で記述される実際のテーブル名はusersであり、MyTableはFlinkテーブル環境に登録されているテーブルです。

11.9.4、エラスティックサーチ

Elasticsearch は、分散型検索および分析エンジンとして、ビッグ データ アプリケーションにおける多くのシナリオを備えています。Flink が提供する Elasticsearch SQL コネクタは、Elasticsearch インデックス (インデックス) にテーブル データを書き込むことができる TableSink としてのみ使用できます。Elasticsearch コネクタの使用方法は、JDBC コネクタの使用方法と非常に似ており、データの書き込みモードは、テーブルを作成するための DDL に主キー定義があるかどうかによって決まります。

11.9.4.1. 依存関係の導入

Flink プログラムで Elasticsearch コネクタを使用するには、対応する依存関係を導入する必要があります。特定の依存関係は、Elasticsearch サーバーのバージョンに関連しています。バージョン 6.x の場合、依存関係は次のように導入されます。

<dependency>
 <groupId>org.apache.flink</groupId> 
<artifactId>flink-connector-elasticsearch6_${
    
    scala.binary.version}</artifactId>
<version>${
    
    flink.version}</version>
</dependency>

Elasticsearch 7 より上のバージョンの場合、導入される依存関係は次のとおりです。

<dependency>
 <groupId>org.apache.flink</groupId> 
<artifactId>flink-connector-elasticsearch7_${
    
    scala.binary.version}</artifactId>
<version>${
    
    flink.version}</version>
</dependency>

11.9.4.2. Elasticsearchに接続するテーブルの作成

Elasticsearchテーブルの作成方法は基本的にJDBCテーブルの作成方法と同じです。具体的な例を次に示します。

-- 创建一张连接到 Elasticsearch 的 表
CREATE TABLE MyTable (
 user_id STRING,
 user_name STRING
 uv BIGINT,
 pv BIGINT,
 PRIMARY KEY (user_id) NOT ENFORCED
) WITH (
 'connector' = 'elasticsearch-7',
 'hosts' = 'http://localhost:9200',
 'index' = 'users'
);

ここで主キーが定義されているため、データは Upsert モードで Elasticsearch に書き込まれます。

11.9.5、HBase

HBase は、高性能でスケーラブルな分散カラム ストレージ データベースとして、ビッグ データ分析において非常に重要なツールです。Flink が提供する HBase コネクタは、HBase クラスターの読み取りおよび書き込み操作をサポートします。

ストリーム処理シナリオでは、コネクタが TableSink として HBase にデータを書き込むとき、常に更新/挿入 (Upsert) モードを採用します。つまり、HBase では、コネクタが更新ログ (changelog) を送信するために定義された主キー (主キー) を渡す必要があります。したがって、テーブルを作成する DDL では、行キー (rowkey) フィールドを定義し、それを主キーとして宣言する必要があります。主キーが PRIMARY KEY 句で宣言されていない場合、コネクタはデフォルトで主キーとして rowkey を使用します。 。

11.9.5.1. 依存関係の導入

Flink プログラムで HBase コネクタを使用する場合は、対応する依存関係を導入する必要があります。現時点では、Flink は HBase 1.4.x および 2.2.x バージョンのコネクタ サポートのみを提供しており、導入された依存関係も特定の HBase バージョンに関連している必要があります。バージョン 1.4 の場合、導入された依存関係は次のとおりです。

<dependency>
 <groupId>org.apache.flink</groupId>
 <artifactId>flink-connector-hbase-1.4_${
    
    scala.binary.version}</artifactId>
 <version>${
    
    flink.version}</version>
</dependency>

HBase 2.2 バージョンの場合、導入される依存関係は次のとおりです。

<dependency>
 <groupId>org.apache.flink</groupId>
 <artifactId>flink-connector-hbase-2.2_${
    
    scala.binary.version}</artifactId>
 <version>${
    
    flink.version}</version>
</dependency>

11.9.5.2. HBaseに接続するテーブルの作成

HBaseはリレーショナルデータベースではないので、Flink SQLでテーブルに変換するのは少し面倒です。DDL によって作成された HBase テーブルでは、すべての列ファミリー (列ファミリー) が ROW 型として宣言され、テーブル内のフィールドを占有する必要があり、各ファミリーの列 (列修飾子) は ROW フィールドのネストに対応します。HBase のすべてのファミリーと修飾子を Flink SQL テーブルで宣言する必要はなく、クエリで使用されるものを宣言するだけです。すべての ROW 型フィールド (HBase のファミリーに対応) に加えて、テーブルには HBase の行キーとして認識されるアトミック型フィールドが存在する必要があります。テーブルでは、このフィールドに任意の名前を付けることができ、必ずしも rowkey と呼ぶ必要はありません。

具体的な例を次に示します。

-- 创建一张连接到 HBase 的 表
CREATE TABLE MyTable (
rowkey INT,
family1 ROW<q1 INT>,
family2 ROW<q2 STRING, q3 BIGINT>,
family3 ROW<q4 DOUBLE, q5 BOOLEAN, q6 STRING>,
PRIMARY KEY (rowkey) NOT ENFORCED
) WITH (
'connector' = 'hbase-1.4',
'table-name' = 'mytable',
'zookeeper.quorum' = 'localhost:2181'
);

-- 假设表 T 的字段结构是 [rowkey, f1q1, f2q2, f2q3, f3q4, f3q5, f3q6]
INSERT INTO MyTable
SELECT rowkey, ROW(f1q1), ROW(f2q2, f2q3), ROW(f3q4, f3q5, f3q6) FROM T;

別の T からデータを抽出し、ROW() 関数を使用して対応する列ファミリーを構築し、最後にそれを HBase の mytable という名前のテーブルに書き込みます。

おすすめ

転載: blog.csdn.net/prefect_start/article/details/129570461