Flink系列(二)-- Flink的数据源详解

 

原文地址(包含源码和图片):http://note.youdao.com/noteshare?id=c91f71fd16bedf7dfaac3b6fa663a243&sub=B79A8354FB1D4CB5BE44A1513C4F7A6C

 

一、DataSource

1、Flink 做为一款流式计算框架,它可用来做批处理,即处理静态的数据集、历史的数据集;也可以用来做流处理,即实时的处理些实时数据流,实时的产生数据流结果,只要数据源源不断的过来,Flink 就能够一直计算下去,这个 Data Sources 就是数据的来源地。

 

2、Flink 中你可以使用 StreamExecutionEnvironment.addSource(sourceFunction) 来为你的程序添加数据来源。

 

3、Flink 已经提供了若干实现好了的 source functions,当然你也可以通过实现 SourceFunction 来自定义非并行的 source 或者实现ParallelSourceFunction 接口或者扩展 RichParallelSourceFunction 来自定义并行的 source

 

 

 

 

二、基于集合

1、fromCollection(Collection) - 从 Java 的 Java.util.Collection 创建数据流。集合中的所有元素类型必须相同。

 

2、fromCollection(Iterator, Class) - 从一个迭代器中创建数据流。Class 指定了该迭代器返回元素的类型。

 

3、fromElements(T …) - 从给定的对象序列中创建数据流。所有对象类型必须相同。

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream<Event> input = env.fromElements( new Event(1, "barfoo", 1.0), new Event(2, "start", 2.0), new Event(3, "foobar", 3.0), ... );

 

4、fromParallelCollection(SplittableIterator, Class) - 从一个迭代器中创建并行数据流。Class 指定了该迭代器返回元素的类型。

 

5、generateSequence(from, to) - 创建一个生成指定区间范围内的数字序列的并行数据流。

 

 

三、基于文件

1、readTextFile(path) - 读取文本文件,即符合 TextInputFormat 规范的文件,并将其作为字符串返回。

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream<String> text = env.readTextFile("file:///path/to/file");

 

 

2、readFile(fileInputFormat, path) - 根据指定的文件输入格式读取文件(一次)。

 

3、readFile(fileInputFormat, path, watchType, interval, pathFilter, typeInfo) - 这是上面两个方法内部调用的方法。它根据给定的 fileInputFormat 和读取路径读取文件。根据提供的 watchType,这个 source 可以定期(每隔 interval 毫秒)监测给定路径的新数据(FileProcessingMode.PROCESS_CONTINUOUSLY),或者处理一次路径对应文件的数据并退出(FileProcessingMode.PROCESS_ONCE)。你可以通过 pathFilter 进一步排除掉需要处理的文件。

 

/* 在具体实现上,Flink 把文件读取过程分为两个子任务,即目录监控和数据读取。 每个子任务都由单独的实体实现。目录监控由单个非并行(并行度为1)的任务执行, 而数据读取由并行运行的多个任务执行。后者的并行性等于作业的并行性。 单个目录监控任务的作用是扫描目录(根据 watchType 定期扫描或仅扫描一次), 查找要处理的文件并把文件分割成切分片(splits),然后将这些切分片分配给下游 reader。reader 负责读取数据。每个切分片只能由一个 reader 读取, 但一个 reader 可以逐个读取多个切分片。 重要注意: 如果 watchType 设置为 FileProcessingMode.PROCESS_CONTINUOUSLY, 则当文件被修改时,其内容将被重新处理。这会打破“exactly-once”语义, 因为在文件末尾附加数据将导致其所有内容被重新处理。 如果 watchType 设置为 FileProcessingMode.PROCESS_ONCE, 则 source 仅扫描路径一次然后退出,而不等待 reader 完成文件内容的读取。 当然 reader 会继续阅读,直到读取所有的文件内容。关闭 source 后就不会再有检查点。 这可能导致节点故障后的恢复速度较慢,因为该作业将从最后一个检查点恢复读取。 */ final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream<MyEvent> stream = env.readFile( myFormat, myFilePath, FileProcessingMode.PROCESS_CONTINUOUSLY, 100, FilePathFilter.createDefaultFilter(), typeInfo);

 

 

 

四、基于 Socket

socketTextStream(String hostname, int port) - 从 socket 读取。元素可以用分隔符切分。

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream<Tuple2<String, Integer>> dataStream = env .socketTextStream("localhost", 9999) // 监听 localhost 的 9999 端口过来的数据 .flatMap(new Splitter()) .keyBy(0) .timeWindow(Time.seconds(5)) .sum(1);

 

 

五、基于自定义

addSource - 添加一个新的 source function。例如,你可以 addSource(new FlinkKafkaConsumer011<>(…)) 以从 Apache Kafka 读取数据

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream<KafkaEvent> input = env .addSource( new FlinkKafkaConsumer011<>( parameterTool.getRequired("input-topic"), //从参数中获取传进来的 topic new KafkaEventSchema(), parameterTool.getProperties()) .assignTimestampsAndWatermarks(new CustomWatermarkExtractor()));

 

如何自定义?

 

如果你想自己自定义自己的 Source 呢?

那么你就需要去了解一下 SourceFunction 接口了,它是所有 stream source 的根接口,它继承自一个标记接口(空接口)Function。

SourceFunction 定义了两个接口方法:

1、run : 启动一个 source,即对接一个外部数据源然后 emit 元素形成 stream(大部分情况下会通过在该方法里运行一个 while 循环的形式来产生 stream)。

2、cancel : 取消一个 source,也即将 run 中的循环 emit 元素的行为终止。

正常情况下,一个 SourceFunction 实现这两个接口方法就可以了。其实这两个接口方法也固定了一种实现模板。

比如,实现一个 XXXSourceFunction,那么大致的模板是这样的:(直接拿 FLink 源码的实例给你看看)

 

 

 

 

六、Flink Kafka source

 

[KafkaUtils.scala]

package com.test.flink.model import java.util.Properties import org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord} import org.slf4j.{Logger, LoggerFactory} import scala.collection.mutable /* * * 创建主题 ./kafka-topics.sh --create --zookeeper 172.16.208.149:2181,172.16.208.150:2181 --replication-factor 2 --partitions 8 --topic metric 控制台消费者 * * */ /*** * 往kafka中写数据 */ object KafkaUtils { val logger:Logger = LoggerFactory.getLogger(KafkaUtils.getClass) final val broker_list:String = "172.16.208.149:6667,172.16.208.150:6667,172.16.208.151:6667,172.16.208.152:6667" final val zk_list:String = "172.16.208.149:2181,172.16.208.150:2181" final val topic:String = "metric1" def writeToKafka(): Unit ={ val props:Properties = new Properties() props.put("bootstrap.servers", broker_list) props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer") //key 序列化 props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer") //value 序列化 val producer:KafkaProducer[String,String] = new KafkaProducer[String,String](props) val tags = new mutable.HashMap[String,String]() val fields = new mutable.HashMap[String,String]() tags.put("cluster", "ghf") tags.put("host_ip", "111.111.111.111") fields.put("used_percent","90d") fields.put("max", "27244873d") fields.put("used", "17244873d") fields.put("init", "27244873d") val metric = Metric( "mem", System.currentTimeMillis(), fields, tags ) val str = JavaDemoUtils.seq2Josn(metric) val record:ProducerRecord[String,String] = new ProducerRecord[String,String](topic,null,null,str) producer.send(record) println(s"发送数据: ${str}") producer.flush() } def main(args: Array[String]): Unit = { while(true){ Thread.sleep(300) writeToKafka() } } } import scala.beans.BeanProperty import scala.collection.mutable case class Metric(@BeanProperty var name:String, @BeanProperty var timestamp:Long, @BeanProperty var fields: mutable.HashMap[String,String], @BeanProperty var tags: mutable.HashMap[String,String]) { override def toString: String = { "Metric{" + "name='" + name + '\'' + ", timestamp='" + timestamp + '\'' + ", fields=" + fields + ", tags=" + tags + '}'; } }

 

 

[KafkaSource.scala]

package com.test.flink.kafkaSource import java.util.Properties import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer011; object KafkaSource { final val broker_list:String = "172.16.208.149:6667,172.16.208.150:6667,172.16.208.151:6667,172.16.208.152:6667" final val zk_list:String = "172.16.208.149:2181,172.16.208.150:2181" final val topic:String = "metric1" def main(args: Array[String]): Unit = { val env = StreamExecutionEnvironment.getExecutionEnvironment val props = new Properties() props.put("bootstrap.servers",broker_list) props.put("zookeeper.connect",zk_list) props.put("group.id", "metric-group") props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer") //key 反序列化 props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer") //value 反序列化 props.put("auto.offset.reset", "latest") val dataStream:DataStreamSource[String] = env.addSource( new FlinkKafkaConsumer011(topic, new SimpleStringSchema(), props)).setParallelism(1) dataStream.print() env.execute("flink kafka source") } }

 

 

 

七、自定义Source(SQL)

package com.ghf.test.flink import java.sql.{Connection, Driver, DriverManager, PreparedStatement, ResultSet} import org.apache.flink.configuration.Configuration import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment import org.apache.flink.streaming.api.functions.source.{RichSourceFunction, SourceFunction} class SourceFromMySQL extends RichSourceFunction[Student]{ var conn:Connection = null var ps:PreparedStatement = null /** * open() 方法中建立连接,这样不用每次 invoke 的时候都要建立连接和释放连接。 * * @param parameters * @throws Exception */ override def open(parameters: Configuration): Unit = { super.open(parameters) conn = SourceFromMySQL.getConn() val sql = "select * from student" ps = conn.prepareStatement(sql) } /** * 程序执行完毕就可以进行,关闭连接和释放资源的动作了 * * @throws Exception */ override def close(): Unit = { super.close() if(conn != null){ conn.close() } if(ps != null){ ps.close() } } /** * DataStream 调用一次 run() 方法用来获取数据 * * @param ctx * @throws Exception */ override def run(sourceContext: SourceFunction.SourceContext[Student]): Unit = { val rs: ResultSet = ps.executeQuery() while(rs.next()){ val student = Student( rs.getInt("id"), rs.getString("name").trim, rs.getString("password").trim, rs.getInt("age") ) sourceContext.collect(student) } } override def cancel(): Unit = ??? def getConn(): Connection ={ var con:Connection = null try{ Class.forName("com.mysql.jdbc.Driver") con = DriverManager.getConnection("jdbc:mysql://172.16.190.76:3306/test?useUnicode=true&characterEncoding=UTF-8", "root", "root") } catch{ case ex:Exception => println("-----------mysql get connection has exception , msg = "+ ex.getMessage()); } con } } object SourceFromMySQL{ val url:String = "jdbc:mysql://172.16.190.76:3306/hive" val user:String = "root" val password:String = "root" def main(args: Array[String]): Unit = { // getConn() val env = StreamExecutionEnvironment.getExecutionEnvironment env.addSource(new SourceFromMySQL).print env.execute("flink add sql source") } def getConn(): Connection ={ var con:Connection = null try{ Class.forName("com.mysql.jdbc.Driver") con = DriverManager.getConnection(url,user,password) } catch{ case ex:Exception => println("-----------mysql get connection has exception , msg = "+ ex.getMessage()); } con } } package com.ghf.test.flink import scala.beans.BeanProperty case class Student(@BeanProperty val id:Int, @BeanProperty val name:String, @BeanProperty val password:String, @BeanProperty val age:Int) { override def toString: String = "Student{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + ", age=" + age + '}'; }

 

 

 

八、RichSourceFunction 抽象类说明

 

  • 继承自 AbstractRichFunction。为实现一个 Rich SourceFunction 提供基础能力。该类的子类有三个,两个是抽象类,在此基础上提供了更具体的实现,另一个是 ContinuousFileMonitoringFunction。

 

  • MessageAcknowledgingSourceBase :它针对的是数据源是消息队列的场景并且提供了基于 ID 的应答机制。

 

  • MultipleIdsMessageAcknowledgingSourceBase : 在 MessageAcknowledgingSourceBase 的基础上针对 ID 应答机制进行了更为细分的处理,支持两种 ID 应答模型:session id 和 unique message id。

 

  • ContinuousFileMonitoringFunction:这是单个(非并行)监视任务,它接受 FileInputFormat,并且根据 FileProcessingMode 和 FilePathFilter,它负责监视用户提供的路径;决定应该进一步读取和处理哪些文件;创建与这些文件对应的 FileInputSplit 拆分,将它们分配给下游任务以进行进一步处理。

 

 

九、自定义SQLSink

package com.test.ghf.sqlSink import java.sql.{Connection, DriverManager, PreparedStatement} import org.apache.flink.configuration.Configuration import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment import org.apache.flink.streaming.api.functions.sink.{RichSinkFunction, SinkFunction} import scala.beans.BeanProperty class SQLSink extends RichSinkFunction[Student]{ var conn:Connection = null var ps:PreparedStatement = null val url:String = "jdbc:mysql://172.16.190.76:3306/hive" val user:String = "root" val password:String = "root" /** * open() 方法中建立连接,这样不用每次 invoke 的时候都要建立连接和释放连接。 * * @param parameters * @throws Exception */ override def open(parameters: Configuration): Unit = { super.open(parameters) conn = getConn() val sql = "insert into student1(id, name, password, age) values(?,?,?,?);" ps = conn.prepareStatement(sql) } def getConn(): Connection ={ var con:Connection = null try{ Class.forName("com.mysql.jdbc.Driver") con = DriverManager.getConnection(url,user,password) } catch{ case ex:Exception => println("-----------mysql get connection has exception , msg = "+ ex.getMessage()); } con } /** * 程序执行完毕就可以进行,关闭连接和释放资源的动作了 * * @throws Exception */ override def close(): Unit = { super.close() if(conn != null){ conn.close() } if(ps != null){ ps.close() } } /** * 每条数据的插入都要调用一次 invoke() 方法 * * @param value * @param context * @throws Exception */ override def invoke(value: Student, context: SinkFunction.Context[_]): Unit = { ps.setInt(1,value.getId) ps.setString(2,value.getName) ps.setString(3,value.getPassword) ps.setInt(4,value.getAge) ps.executeUpdate() } } case class Student(@BeanProperty val id:Int, @BeanProperty val name:String, @BeanProperty val password:String, @BeanProperty val age:Int) { override def toString: String = "Student{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + ", age=" + age + '}'; } object Mian { def main(args: Array[String]): Unit = { val env = StreamExecutionEnvironment.getExecutionEnvironment val stream = env.addSource(new SQLSource) stream.addSink(new SQLSink) env.execute("sql to sql") } }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值