请确认这是使用Flink将数据流式传输到Hadoop的正确方法

时间:2015-12-28 21:04:50

标签: apache-flink flink-streaming

我需要一些Flink Streaming的帮助。我在下面制作了一个简单的Hello-world类型的代码。这会从RabbitMQ中传输Avro消息并将其持久保存到HDFS。我希望有人可以查看代码,也许它可以帮助其他人。

我发现Flink流式传输的大多数示例都会将结果发送到std-out。我实际上想将数据保存到Hadoop。我读到这一点,理论上,您可以使用Flink流式传输到任何您喜欢的地方。我还没有找到任何将数据保存到HDFS的示例。但是,基于我找到的例子,以及试验和错误,我已经提供了以下代码。

这里的数据来源是RabbitMQ。我使用客户端应用程序发送" MyAvroObjects"到RabbitMQ。 MyAvroObject.java(不包括)是从avro IDL生成的...可以是任何avro消息。

下面的代码使用RabbitMQ消息,并将其保存到HDFS,作为avro文件......嗯,这就是我的希望。

@onclick = "studentChecker('" + o.student_id + "');"

如果您更喜欢RabbitMQ以外的其他来源,那么使用其他来源可以正常工作。例如。使用Kafka消费者:

package com.johanw.flink.stackoverflow;

import java.io.IOException;

import org.apache.avro.io.Decoder;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.mapred.AvroKey;
import org.apache.avro.mapred.AvroOutputFormat;
import org.apache.avro.mapred.AvroWrapper;
import org.apache.avro.mapreduce.AvroJob;
import org.apache.avro.specific.SpecificDatumReader;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.hadoop.mapred.HadoopOutputFormat;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.api.java.typeutils.TypeExtractor;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.FileSinkFunctionByMillis;
import org.apache.flink.streaming.connectors.rabbitmq.RMQSource;
import org.apache.flink.streaming.util.serialization.DeserializationSchema;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapreduce.Job;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RMQToHadoop {
    public class MyDeserializationSchema implements DeserializationSchema<MyAvroObject> {
        private static final long serialVersionUID = 1L;

        @Override
        public TypeInformation<MyAvroObject> getProducedType() {
             return TypeExtractor.getForClass(MyAvroObject.class);
        }

        @Override
        public MyAvroObject deserialize(byte[] array) throws IOException {
            SpecificDatumReader<MyAvroObject> reader = new SpecificDatumReader<MyAvroObject>(MyAvroObject.getClassSchema());
            Decoder decoder = DecoderFactory.get().binaryDecoder(array, null);
            MyAvroObject MyAvroObject = reader.read(null, decoder);
            return MyAvroObject;
        }

        @Override
        public boolean isEndOfStream(MyAvroObject arg0) {
            return false;
        }
    }

    private String hostName;
    private String queueName;

    public final static String path = "/hdfsroot";

    private static Logger logger = LoggerFactory.getLogger(RMQToHadoop.class);

    public RMQToHadoop(String hostName, String queueName) {
        super();
        this.hostName = hostName;
        this.queueName = queueName;
    }

    final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

    public void run() {
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        logger.info("Running " + RMQToHadoop.class.getName());
        DataStream<MyAvroObject> socketStockStream = env.addSource(new RMQSource<>(hostName, queueName, new MyDeserializationSchema()));
        Job job;
        try {
            job = Job.getInstance();
            AvroJob.setInputKeySchema(job, MyAvroObject.getClassSchema());
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        try {
            JobConf jobConf = new JobConf(Job.getInstance().getConfiguration());
            jobConf.set("avro.output.schema", MyAvroObject.getClassSchema().toString());
            org.apache.avro.mapred.AvroOutputFormat<MyAvroObject> akof = new AvroOutputFormat<MyAvroObject>();
            HadoopOutputFormat<AvroWrapper<MyAvroObject>, NullWritable> hof = new HadoopOutputFormat<AvroWrapper<MyAvroObject>, NullWritable>(akof, jobConf);
            FileSinkFunctionByMillis<Tuple2<AvroWrapper<MyAvroObject>, NullWritable>> fileSinkFunctionByMillis = new FileSinkFunctionByMillis<Tuple2<AvroWrapper<MyAvroObject>, NullWritable>>(hof, 10000l);
            org.apache.hadoop.mapred.FileOutputFormat.setOutputPath(jobConf, new Path(path));

            socketStockStream.map(new MapFunction<MyAvroObject, Tuple2<AvroWrapper<MyAvroObject>, NullWritable>>() {
                private static final long serialVersionUID = 1L;
                @Override
                public Tuple2<AvroWrapper<MyAvroObject>, NullWritable> map(MyAvroObject envelope) throws Exception {
                    logger.info("map");
                    AvroKey<MyAvroObject> key = new AvroKey<MyAvroObject>(envelope);
                    Tuple2<AvroWrapper<MyAvroObject>, NullWritable> tupple = new Tuple2<AvroWrapper<MyAvroObject>, NullWritable>(key, NullWritable.get());
                    return tupple;
                }
            }).addSink(fileSinkFunctionByMillis);
            try {
                env.execute();
            } catch (Exception e) {
                logger.error("Error while running " + RMQToHadoop.class + ".", e);
            }
        } catch (IOException e) {
            logger.error("Error while running " + RMQToHadoop.class + ".", e);
        }
    }

    public static void main(String[] args) throws IOException {
        RMQToHadoop toHadoop = new RMQToHadoop("localhost", "rabbitTestQueue");
        toHadoop.run();
    }
}

问题:

  1. 请检讨。这是将数据保存到HDFS的良好做法吗?

  2. 如果流式传输过程导致问题,比如在序列化过程中会出现问题。它生成和异常,代码只是退出。 Spark流式传输依赖于Yarn自动重启应用程序。使用Flink时这也是一个好习惯吗?

  3. 我正在使用FileSinkFunctionByMillis。我实际上希望使用类似HdfsSinkFunction的东西,但这并不存在。所以FileSinkFunctionByMillis是最接近它的,这对我来说很有意义。我发现的文档再次没有任何解释该做什么,所以我只是在猜测。

  4. 当我在本地运行时,我找到了一个类似&#34; C:\ hdfsroot_temporary \ 0_temporary \ attempt__0000_r_000001_0&#34;的目录结构,这是... basare。这里有什么想法吗?

  5. 顺便说一下,当你想将数据保存到Kafka时,我能够使用...

    import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer082;
    
    ...
    
    DataStreamSource<MyAvroObject> socketStockStream = env.addSource(new FlinkKafkaConsumer082<MyAvroObject>(topic, new MyDeserializationSchema(), sourceProperties));
    

    非常感谢提前!!!!

1 个答案:

答案 0 :(得分:0)

我认为可以使用FileSinkFunctionByMillis,但这意味着您的流式传输程序不具备容错能力。这意味着如果您的源或机器或写入失败,那么您的程序将崩溃而无法恢复。

我建议您使用RollingSinkhttps://ci.apache.org/projects/flink/flink-docs-release-0.10/apis/streaming_guide.html#hadoop-filesystem)。这可用于创建类似Flum的管道以将数据摄取到HDFS(或其他文件系统)中。滚动接收器是一个可恢复的接收器,这意味着您的程序将具有容错能力,因为Kafka消费者也具有容错能力。您还可以指定自定义Writer以您想要的任何格式写入数据,例如Avro。