web-dev-qa-db-ja.com

Flink Streaming:データに応じて1つのデータストリームを異なる出力に出力する方法は?

Apache Flinkには、タプルのストリームがあります。本当に単純な_Tuple1<String>_を想定しましょう。タプルは、その値フィールドに任意の値を持つことができます(たとえば、「P1」、「P2」など)。可能な値のセットは有限ですが、完全なセットは事前にわかりません(したがって、「P362」が存在する可能性があります)。タプル内の値に応じて、そのタプルを特定の出力位置に書き込みたい。だから例えば次のファイル構造が必要です。

  • _/output/P1_
  • _/output/P2_

ドキュメントでは、事前に知っている場所(stream.writeCsv("/output/somewhere")など)に書き込む可能性しかありませんでしたが、データの内容にデータが実際にどこに到達するかを決定させる方法はありませんでした。

ドキュメントで出力分割について読みましたが、これは、出力を希望の方法で別の宛先にリダイレクトする方法を提供していないようです(または、これがどのように機能するかを理解していません)。

これはFlinkAPIで実行できますか?もしそうなら、どのように?そうでない場合は、それを実行できるサードパーティのライブラリがありますか、それとも自分でそのようなものを構築する必要がありますか?

更新

Matthiasの提案に従って、出力パスを決定し、シリアル化した後にタプルをそれぞれのファイルに書き込むふるい分けシンク関数を思いつきました。参考までにここに置いておきますが、他の人にも役立つかもしれません。

_public class SiftingSinkFunction<IT> extends RichSinkFunction<IT> {

    private final OutputSelector<IT> outputSelector;
    private final MapFunction<IT, String> serializationFunction;
    private final String basePath;
    Map<String, TextOutputFormat<String>> formats = new HashMap<>();

    /**
     * @param outputSelector        the selector which determines into which output(s) a record is written.
     * @param serializationFunction a function which serializes the record to a string.
     * @param basePath              the base path for writing the records. It will be appended with the output selector.
     */
    public SiftingSinkFunction(OutputSelector<IT> outputSelector, MapFunction<IT, String> serializationFunction, String basePath) {
        this.outputSelector = outputSelector;
        this.serializationFunction = serializationFunction;
        this.basePath = basePath;
    }


    @Override
    public void invoke(IT value) throws Exception {
        // find out where to write.
        Iterable<String> selection = outputSelector.select(value);
        for (String s : selection) {
            // ensure we have a format for this.
            TextOutputFormat<String> destination = ensureDestinationExists(s);
            // then serialize and write.
            destination.writeRecord(serializationFunction.map(value));
        }
    }

    private TextOutputFormat<String> ensureDestinationExists(String selection) throws IOException {
        // if we know the destination, we just return the format.
        if (formats.containsKey(selection)) {
            return formats.get(selection);
        }

        // create a new output format and initialize it from the context.
        TextOutputFormat<String> format = new TextOutputFormat<>(new Path(basePath, selection));
        StreamingRuntimeContext context = (StreamingRuntimeContext) getRuntimeContext();
        format.configure(context.getTaskStubParameters());
        format.open(context.getIndexOfThisSubtask(), context.getNumberOfParallelSubtasks());

        // put it into our map.
        formats.put(selection, format);
        return format;
    }

    @Override
    public void close() throws IOException {
        Exception lastException = null;
        try {
            for (TextOutputFormat<String> format : formats.values()) {
                try {
                    format.close();
                } catch (Exception e) {
                    lastException = e;
                    format.tryCleanupOnError();
                }
            }
        } finally {
            formats.clear();
        }

        if (lastException != null) {
            throw new IOException("Close failed.", lastException);
        }
    }
}
_
19
Jan Thomä

カスタムシンクを実装できます。両方のいずれかから継承します。

  • _org.Apache.flink.streaming.api.functions.sink.SinkFunction_
  • _org.Apache.flink.streaming.api.functions.sink.RichSinkFunction_

プログラムでの使用:

_stream.addSink(SinkFunction<T> sinkFunction);
_

stream.writeCsv("/output/somewhere")の代わりに。

7
Matthias J. Sax