JavaによるApache Flinkの紹介

1概要

Apache Flinkは、プログラマが膨大な量のデータを非常に効率的でスケーラブルな方法で処理できるようにするビッグデータ処理フレームワークです。

この記事では、__https://ci.apache.org/projects/flink/flink-docs-release-1.2/index.html[Apache Flink]で利用可能な コアAPIの概念と標準のデータ変換についていくつか紹介します。]Java API 。このAPIの流暢なスタイルは、Flinkの中心的な構成要素である分散コレクションとの連携を容易にします。

まず、Flinkの DataSet API変換を見て、それらを使用してワードカウントプログラムを実装します。それでは、Flinkの DataStream APIを簡単に見ていきます。これにより、イベントのストリームをリアルタイムで処理できます。

2 Mavenの依存関係

始めるには、Mavenの依存関係をhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.apache.flink%22%20AND%20a%3A%22flinkに追加する必要があります。 -java%22[ flink-java ]およびhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.apache.flink%22%20AND%20a%3A%22flink-test -utils%22[ flink-test-utils ]ライブラリ:

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-java</artifactId>
    <version>1.2.0</version>
</dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-test-utils__2.10</artifactId>
    <version>1.2.0</version>
    <scope>test<scope>
</dependency>

3コアAPIの概念

Flinkを使うときは、そのAPIに関連するいくつかのことを知っておく必要があります。

  • ** すべてのFlinkプログラムは、分散ファイルに対して変換を実行します。

データの集まり データを変換するためのさまざまな関数は フィルタリング、マッピング、結合、グループ化を含む 集約する ** Flinkの sink 操作は、以下の目的でストリームの実行をトリガーします。

に結果を保存するなど、プログラム の目的の結果を生成します。 ファイルシステム、または標準出力に表示する ** Flink変換は怠惰です。つまり、Flink変換は実行されません。

sink 操作が呼び出されるまで ** Apache Flink APIは二つの操作モード - バッチと操作をサポートします

  • バッチモードで処理できる限られたデータソースを扱う場合は、 DataSet APIを使用します。無制限のデータストリームをリアルタイムで処理したい場合は、 DataStream APIを使用する必要があります。

4 DataSet APIの変換

Flinkプログラムへのエントリポイントはhttps://ci.apache.org/projects/flink/flink-docs-release-1.2/api/java/org/apache/flink/api/scala/ExecutionEnvironmentのインスタンスです。 html[ ExecutionEnvironment ]クラス - これはプログラムが実行されるコンテキストを定義します。

処理を開始するための ExecutionEnvironment を作成しましょう。

ExecutionEnvironment env
  = ExecutionEnvironment.getExecutionEnvironment();
  • ローカルマシンでアプリケーションを起動すると、ローカルJVMで処理が実行されます。** マシンのクラスタで処理を開始する場合は、https://ci.apache.org/をインストールする必要があります。それらのマシン上でprojects/flink/flink-docs-release-0.8/setup quickstart.html[Apache Flink]して、それに応じて ExecutionEnvironment__を設定します。

4.1. データセットの作成

データ変換の実行を開始するには、プログラムにデータを提供する必要があります。

ExecutionEnvironement を使用して DataSet クラスのインスタンスを作成しましょう。

DataSet<Integer> amounts = env.fromElements(1, 29, 40, 50);

あなたはApache Kafka、CSV、ファイルあるいは事実上他のどんなデータソースのような複数のソースからも DataSet を作成することができます。

4.2. フィルタして減らす

DataSet クラスのインスタンスを作成したら、それに変換を適用できます。

特定のしきい値を超える数値をフィルタリングし、次にそれらすべてを合計するとしましょう。これを実現するには、 filter() 変換と reduce() 変換を使用できます。

int threshold = 30;
List<Integer> collect = amounts
  .filter(a -> a > threshold)
  .reduce((integer, t1) -> integer + t1)
  .collect();

assertThat(collect.get(0)).isEqualTo(90);

collect() メソッドは、実際のデータ変換をトリガーする sink 操作です。

4.3. 地図

Person オブジェクトの DataSet があるとしましょう。

private static class Person {
    private int age;
    private String name;

   //standard constructors/getters/setters
}

次に、これらのオブジェクトの DataSet を作成しましょう。

DataSet<Person> personDataSource = env.fromCollection(
  Arrays.asList(
    new Person(23, "Tom"),
    new Person(75, "Michael")));

コレクションのすべてのオブジェクトから age フィールドのみを抽出したいとします。 map() 変換を使用して、 Person クラスの特定のフィールドのみを取得できます。

List<Integer> ages = personDataSource
  .map(p -> p.age)
  .collect();

assertThat(ages).hasSize(2);
assertThat(ages).contains(23, 75);

4.4. 参加する

2つのデータセットがあるときは、それらを id フィールドで結合したいことがあります。これには、 join() 変換を使用できます。

トランザクションのコレクションとユーザーの住所を作成しましょう。

Tuple3<Integer, String, String> address
  = new Tuple3<>(1, "5th Avenue", "London");
DataSet<Tuple3<Integer, String, String>> addresses
  = env.fromElements(address);

Tuple2<Integer, String> firstTransaction
  = new Tuple2<>(1, "Transaction__1");
DataSet<Tuple2<Integer, String>> transactions
  = env.fromElements(firstTransaction, new Tuple2<>(12, "Transaction__2"));

両方のタプルの最初のフィールドは Integer タイプで、これは両方のデータセットを結合する id フィールドです。

実際の結合ロジックを実行するには、 https://ci.apache.org/projects/flink/flink-docs-release-1.2/api/java/org/apache/flink/api/java/functions/を実装する必要があります。 KeySelector.html[KeySelector] 住所と取引のインタフェース:

private static class IdKeySelectorTransaction
  implements KeySelector<Tuple2<Integer, String>, Integer> {
    @Override
    public Integer getKey(Tuple2<Integer, String> value) {
        return value.f0;
    }
}

private static class IdKeySelectorAddress
  implements KeySelector<Tuple3<Integer, String, String>, Integer> {
    @Override
    public Integer getKey(Tuple3<Integer, String, String> value) {
        return value.f0;
    }
}

各セレクタは、結合が実行されるべきフィールドを返すだけです。

  • 残念ながら、Flinkはジェネリック型情報を必要とするので、ここでラムダ式を使うことはできません。**

次に、これらのセレクターを使用してマージロジックを実装しましょう。

List<Tuple2<Tuple2<Integer, String>, Tuple3<Integer, String, String>>>
  joined = transactions.join(addresses)
  .where(new IdKeySelectorTransaction())
  .equalTo(new IdKeySelectorAddress())
  .collect();

assertThat(joined).hasSize(1);
assertThat(joined).contains(new Tuple2<>(firstTransaction, address));

4.5. ソート

以下の__Tuple2のコレクションがあるとしましょう。

Tuple2<Integer, String> secondPerson = new Tuple2<>(4, "Tom");
Tuple2<Integer, String> thirdPerson = new Tuple2<>(5, "Scott");
Tuple2<Integer, String> fourthPerson = new Tuple2<>(200, "Michael");
Tuple2<Integer, String> firstPerson = new Tuple2<>(1, "Jack");
DataSet<Tuple2<Integer, String>> transactions = env.fromElements(
  fourthPerson, secondPerson, thirdPerson, firstPerson);

このコレクションをタプルの最初のフィールドでソートしたい場合は、 sortPartitions() 変換を使用できます。

List<Tuple2<Integer, String>> sorted = transactions
  .sortPartition(new IdKeySelectorTransaction(), Order.ASCENDING)
  .collect();

assertThat(sorted)
  .containsExactly(firstPerson, secondPerson, thirdPerson, fourthPerson);

5ワード数

ワードカウント問題は、ビッグデータ処理フレームワークの機能を紹介するために一般的に使用されている問題です。基本的な解決策は、テキスト入力内の単語の出現回数を数えることです。この問題に対する解決策を実装するためにFlinkを使ってみましょう。

このソリューションの最初のステップとして、入力をトークン(単語)に分割する LineSplitter クラスを作成し、各トークンに対してKey-Valueペアの Tuple2 を収集します。これらの各タプルでは、​​キーはテキスト内にある単語であり、値は整数の1です。

このクラスは FlatMapFunction インターフェースを実装します。これは String を入力として受け取り、 Tuple2 <文字列、整数>:

public class LineSplitter implements FlatMapFunction<String, Tuple2<String, Integer>> {

    @Override
    public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
        Stream.of(value.toLowerCase().split("\\W+"))
          .filter(t -> t.length() > 0)
          .forEach(token -> out.collect(new Tuple2<>(token, 1)));
    }
}

https://ci.apache.org/projects/flink/flink-docs-release-1.0/api/java/org/apache/flink/util/class-use/Collectorの collect() メソッドを呼び出します。処理パイプラインでデータを転送するためのhtml[ Collector ]クラス。

次の最後のステップでは、最初の要素(単語)でタプルをグループ化してから、2番目の要素に対して sum 集計を実行して単語の出現回数を算出します。

public static DataSet<Tuple2<String, Integer>> startWordCount(
  ExecutionEnvironment env, List<String> lines) throws Exception {
    DataSet<String> text = env.fromCollection(lines);

    return text.flatMap(new LineSplitter())
      .groupBy(0)
      .aggregate(Aggregations.SUM, 1);
}
  • 3種類のFlink変換を使用しています: flatMap() groupBy() 、および aggregate()

ワードカウントの実装が期待通りに機能していることを表明するテストを書きましょう。

List<String> lines = Arrays.asList(
  "This is a first sentence",
  "This is a second sentence with a one word");

DataSet<Tuple2<String, Integer>> result = WordCount.startWordCount(env, lines);

List<Tuple2<String, Integer>> collect = result.collect();

assertThat(collect).containsExactlyInAnyOrder(
  new Tuple2<>("a", 3), new Tuple2<>("sentence", 2), new Tuple2<>("word", 1),
  new Tuple2<>("is", 2), new Tuple2<>("this", 2), new Tuple2<>("second", 1),
  new Tuple2<>("first", 1), new Tuple2<>("with", 1), new Tuple2<>("one", 1));

6. DataStream API

6.1. データストリームの作成

Apache Flinkは、そのDataStream APIを介したイベントストリームの処理もサポートしています。イベントの利用を開始したい場合は、まず StreamExecutionEnvironment クラスを使用する必要があります。

StreamExecutionEnvironment executionEnvironment
 = StreamExecutionEnvironment.getExecutionEnvironment();

次に、さまざまなソースから executionEnvironment を使用してイベントのストリームを作成できます。それは Apache Kafka のようなメッセージバスかもしれませんが、この例では、単純に2つの文字列要素からソースを作成します。

DataStream<Tuple2<String, Integer>> dataStream = executionEnvironment.fromElements(
  "This is a first sentence",
  "This is a second sentence with a one word");

通常の DataSet クラスのように、 DataStream のすべての要素に変換を適用できます。

SingleOutputStreamOperator<String> upperCase = text.map(String::toUpperCase);

実行をトリガーするには、 StreamExecutionEnvironment クラスの execute() メソッドに従って、変換の結果を標準出力に出力する print() などのシンク操作を呼び出す必要があります。

upperCase.print();
env.execute();

次のような出力が生成されます。

1> THIS IS A FIRST SENTENCE
2> THIS IS A SECOND SENTENCE WITH A ONE WORD

6.2. イベントのウィンドウイング

イベントのストリームをリアルタイムで処理するときは、イベントをまとめてグループ化し、それらのイベントのウィンドウに計算を適用する必要がある場合があります。

各イベントが、イベントがシステムに送信されたときのイベント番号とタイムスタンプからなるペアであり、順序が正しくない場合に限って許容できるイベントのストリームがあるとします。 20秒以上遅れました。

この例では、まず、数分間隔の2つのイベントをシミュレートするストリームを作成し、遅延しきい値を指定するタイムスタンプ抽出機能を定義します。

SingleOutputStreamOperator<Tuple2<Integer, Long>> windowed
  = env.fromElements(
  new Tuple2<>(16, ZonedDateTime.now().plusMinutes(25).toInstant().getEpochSecond()),
  new Tuple2<>(15, ZonedDateTime.now().plusMinutes(2).toInstant().getEpochSecond()))
  .assignTimestampsAndWatermarks(
    new BoundedOutOfOrdernessTimestampExtractor
      <Tuple2<Integer, Long>>(Time.seconds(20)) {

        @Override
        public long extractTimestamp(Tuple2<Integer, Long> element) {
          return element.f1 **  1000;
        }
    });

次に、イベントを5秒のウィンドウにグループ化し、それらのイベントに変換を適用するウィンドウ操作を定義しましょう。

SingleOutputStreamOperator<Tuple2<Integer, Long>> reduced = windowed
  .windowAll(TumblingEventTimeWindows.of(Time.seconds(5)))
  .maxBy(0, true);
reduced.print();

5秒ごとのウィンドウの最後の要素を取得するので、次のように出力されます。

1> (15,1491221519)

2番目のイベントは、指定された遅延しきい値より遅く到着したため表示されません。

7. 結論

この記事では、Apache Flinkフレームワークを紹介し、そのAPIで提供されるいくつかの変換について調べました。

Flinkの流暢で機能的なDataSet APIを使用して単語カウントプログラムを実装しました。それから、DataStream APIを見て、一連のイベントに単純なリアルタイム変換を実装しました。

これらすべての例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/libraries[over GitHub]で見つけることができます - これはMavenプロジェクトなので、インポートおよび実行が簡単なはずです。そのまま。

前の投稿:Passayへのガイド
次の投稿:Java Type Systemのインタビューの質問