ApacheFlink® - 是针对于数据流的状态计算,Flink具有特殊类DataSet和DataStream来表示程序中的数据。您可以将它们视为可以包含重复项的不可变数据集合。在DataSet的情况下,数据是有限的,而对于DataStream,元素的数量可以是无限的。
这些集合在某些关键方面与常规Java集合不同。首先,它们是不可变的,这意味着一旦创建它们就无法添加或删除元素。你也不能简单地检查里面的元素。 最初通过在Flink程序中添加Source来创建集合,并通过使用诸如map,filter等API方法对它们进行转换来从这些集合中派生新集合。
结构分析
Flink程序看起来像是转换数据集合的常规程序。每个程序包含相同的基本部分:
- 获得执行环境,
- 加载/创建初始数据,
- 指定此数据的转换,
- 指定放置计算结果的位置,
- 触发程序执行
获取执行环境
Flink提供了三种运行Flink计算的方式:
- 远程jar包部署方式
var streamEnv = StreamExecutionEnvironment.getExecutionEnvironment()
- 本地执行
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
- 跨平台提交
var streamEnv = StreamExecutionEnvironment.createRemoteEnvironment("CentOS",8081,"jarFiles")
用户可以更具需求自行抉择选择哪种方式测试或者运行代码。
加载/创建初始数据
创建需要加载的数据源,一般该数据源来源于消息队列或者其他第三方系统,为了测试方便这里我们先使用socketTextStream
实现数据源的加载。
var source = streamEnv.socketTextStream("CentOS", 9999)
指定此数据的转换
通过指定数据转换规则对DataStream上应用转换以创建新的派生DataStream。
source.flatMap(_.split("\\W+"))
.map((_,1))
.keyBy(0)
.sum(1)
将计算结果存储到 文件中
writeAsCsv("D:/flinks/results")
触发程序执行
streamEnv.execute("wordcounts")
将以上程序放置在一起
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.api.scala._
//1.创建流处理执行环境 - 本地环境
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
//2.创建数据源
var source = streamEnv.socketTextStream("CentOS", 9999)
//3.数据转换
source.flatMap(_.split("\\W+"))
.map((_,1))
.keyBy(0)
.sum(1)
//4.指定输出路径
.writeAsCsv("D:/flinks/results", FileSystem.WriteMode.OVERWRITE)
//5.触发任务执行
streamEnv.execute("wordcounts");
Flink数据源
数据源是您的程序从中读取输入的位置。您可以使用StreamExecutionEnvironment.addSource(sourceFunction)将数据源附加到程序。 Flink附带了许多预先实现的源函数,但您可以通过为非并行源实现SourceFunction,或者通过实现ParallelSourceFunction接口或为并行源扩展RichParallelSourceFunction来编写自己的自定义源。
预定义的流源:
- readTextFile(path) - 读取文本文件,即逐行遵循TextInputFormat规范的文件,并将它们作为字符串返回。
- readFile(fileInputFormat,path) - 按指定的文件输入格式指定读取(一次)文件。
- readFile(fileInputFormat,path,watchType,interval,pathFilter,typeInfo) - 这是前两个调用的内部方法。它根据给定的fileInputFormat读取路径中的文件。根据提供的watchType,此源可以定期监视(每隔ms)新数据的路径(FileProcessingMode.PROCESS_CONTINUOUSLY),或者处理当前在路径中的数据并退出(FileProcessingMode.PROCESS_ONCE)。使用pathFilter,用户可以进一步排除处理文件。
在底层,Flink将文件读取过程分为两个子任务,即目录监控和数据读取。这些子任务中的每一个都由单独的实体实现。监视由单个非并行(并行度= 1)任务实现,而读取由并行运行的多个任务执行。后者的并行性等于job的并行性。单个监视任务的作用是扫描目录(定期或仅一次,具体取决于watchType),找到要处理的文件,将它们分成分割,并将这些分割分配给下游Reader。Reader是那些将读取实际数据的任务。每个分割仅由一个读取器读取,而读取器可以逐个读取多个Split数据。
重要: 如果watchType设置为FileProcessingMode.PROCESS_CONTINUOUSLY,则在修改文件时,将完全重新处理其内容。这可以打破“完全一次”的语义,因为在文件末尾附加数据将导致其所有内容被重新处理。 如果watchType设置为FileProcessingMode.PROCESS_ONCE,则源扫描路径一次并退出,而不等待读者完成读取文件内容。当然Reader将继续读取文件数据,直到读取所有文件内容。当读取完成后,不再有检查点保存。这可能会导致节点故障后恢复速度变慢,因为作业将从上一个检查点恢复读取。
<!--HDFS依赖-->
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>D:/Program Files/Java/jdk1.8.0_121/lib/tools.jar</systemPath>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>${hadoop.version}</version>
</dependency>
<!--Scala依赖-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-scala_${flink.scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_${flink.scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
- readTextFile
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var source = streamEnv.readTextFile("hdfs:///demo/words")
source.flatMap(_.split("\\W+"))
.map((_,1))
.keyBy(0)
.sum(1)
.writeAsCsv("D:/flinks/results", FileSystem.WriteMode.OVERWRITE)
streamEnv.execute("wordcounts");
- readFile
系统会每间隔10ms检测目录,如果有新文件产生立即采集,如果文本内容发生变化,立即重新读取。
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var inputFormat=new TextInputFormat(null)
var source = streamEnv.readFile(
inputFormat,
"hdfs:///demo/words",
FileProcessingMode.PROCESS_CONTINUOUSLY,
10,
new FilePathFilter {
override def filterPath(path: Path): Boolean = {
return !path.getPath().contains("log")
}
}
)
source.flatMap(_.split("\\W+"))
.map((_,1))
.keyBy(0)
.sum(1)
.print()
streamEnv.execute("wordcounts")
Socket-based
从套接字读取。元素可以用分隔符分隔。
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var source = streamEnv.socketTextStream("CentOS", 9999)
source.flatMap(_.split("\\W+"))
.map((_,1))
.keyBy(0)
.sum(1)
.writeAsCsv("D:/flinks/results", FileSystem.WriteMode.OVERWRITE)
streamEnv.execute("wordcounts");
Collection-based
- fromCollection(Collection) - 从Java Java.util.Collection创建数据流。集合中的所有元素必须属于同一类型。
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var source =streamEnv.fromCollection(List("this is a demo hello world"))
source.flatMap(_.split("\\W+"))
.map((_,1))
.keyBy(0)
.sum(1)
.print()
streamEnv.execute("wordcounts")
- fromElements(T …) - 根据给定的对象序列创建数据流。所有对象必须属于同一类型。
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var source =streamEnv.fromElements("this is a demo","good good study")
source.flatMap(_.split("\\W+"))
.map((_,1))
.keyBy(0)
.sum(1)
.print()
streamEnv.execute("wordcounts")
- fromCollection(Iterator,Class) - 从迭代器创建数据流。该类指定迭代器返回的元素的数据类型。
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var source =streamEnv.fromCollection(Iterator(User(1,"zhangsan",true),
User(2,"lisi",false),
User(3,"ww",true)))
source.map(user=>(user.sex,1))
.keyBy(0)
.sum(1)
.print()
streamEnv.execute("usersexcount")
- generateSequence(from,to) - 并行生成给定时间间隔内的数字序列。
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var source =streamEnv.generateSequence(0,1000)
source.filter(_%2==0)
.print()
streamEnv.execute("计算偶数")
addSource(流处理)
添加新的源功能。例如,要从Apache Kafka读取,您可以使用addSource(new FlinkKafkaConsumer <>(…))
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka_${flink.scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
val env = StreamExecutionEnvironment.getExecutionEnvironment
val props = new Properties()
props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "CentOS:9092")
props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "g1")
env.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(),props))
.flatMap(line => for( i <- line.split(" ")) yield (i,1))
.keyBy(_._1)
.reduce((in1,in2)=>(in1._1,in1._2+in2._2))
.print()
env.execute("kafka message count")
注意在测试机器上必须配置主机名和IP的映射关系,否则系统联系不上Kafka服务器。