Flink完成Socketwordcount 本机测试与提交集群

一、任务简述

本次实验是基础的Flink应用构建,运行代码是分布式计算领域的“Hello world”——“wordcount”。
具体内容是通过netcat在指定端口发布信息,由Flink应用程序对端口进行监听,在一定的时间窗口内接收数据并进行相应的词频率统计。实验代码地址[Link]

二、实验环境

主机
OS:Linux Manjaro
java:openjdk1.8
scala:2.11.11
Maven:3.6.3(不重要)
IDEA:2020-3(不重要)
———————————————————
Flink集群
Scala:2.11.11
Flink:1.11.2

三、实验过程与问题解决

实验分为本地测试和打包提交集群运行两个部分,下面分别进行实验:

1.本地测试

在正式提交集群之间我们应当在本地进行代码的测试。这里使用的IDE是IDEA,使用Maven构建项目。可以执行以下代码:

mvn archetype:generate \
    -DarchetypeGroupId=org.apache.flink \
    -DarchetypeArtifactId=flink-quickstart-java \
    -DarchetypeVersion=1.6.1 \
    -DgroupId=my-flink-project \
    -DartifactId=my-flink-project \
    -Dversion=0.1 \
    -Dpackage=myflink \
    -DinteractiveMode=false

也可以直接在IDEA软件中进行配置,如下:

1.新建一个Maven项目
在这里插入图片描述2.填写项目的名称等信息
在这里插入图片描述完成上述配置之后基本的项目框架就有了。
3. 在java目录下新建一个package,这是一个包的目录,在这个包下新建java程序即可(导入前面所述的代码)。
4.pom文件的配置
注意在此之前所以IDEA的Maven环境应当配置完成,不再赘述。

pom文件修改如下:

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>my-flink-project</groupId>
	<artifactId>my-flink-project</artifactId>
	<version>0.1</version>
	<packaging>jar</packaging>

	<name>Flink Quickstart Job</name>
	<url>http://www.myorganization.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<flink.version>1.11.2</flink.version>
		<java.version>1.8</java.version>
		<scala.binary.version>2.11</scala.binary.version>
		<maven.compiler.source>${java.version}</maven.compiler.source>
		<maven.compiler.target>${java.version}</maven.compiler.target>
	</properties>

	<repositories>
		<repository>
			<id>apache.snapshots</id>
			<name>Apache Development Snapshot Repository</name>
			<url>https://repository.apache.org/content/repositories/snapshots/</url>
			<releases>
				<enabled>false</enabled>
			</releases>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
	</repositories>

	<dependencies>
		<!-- Apache Flink dependencies -->
		<!-- These dependencies are provided, because they should not be packaged into the JAR file. -->
		<dependency>
			<groupId>org.apache.flink</groupId>
			<artifactId>flink-java</artifactId>
			<version>${flink.version}</version>
<!--			<scope>provided</scope>-->
		</dependency>
		<dependency>
			<groupId>org.apache.flink</groupId>
			<artifactId>flink-core</artifactId>
			<version>${flink.version}</version>
			<!--			<scope>provided</scope>-->
		</dependency>
		<dependency>
			<groupId>org.apache.flink</groupId>
			<artifactId>flink-streaming-java_${scala.binary.version}</artifactId>
			<version>${flink.version}</version>
<!--			<scope>provided</scope>-->
		</dependency>


		<!-- Add connector dependencies here. They must be in the default scope (compile). -->

		<!-- Example:

		<dependency>
			<groupId>org.apache.flink</groupId>
			<artifactId>flink-connector-kafka-0.10_${scala.binary.version}</artifactId>
			<version>${flink.version}</version>
		</dependency>
		-->

		<!-- Add logging framework, to produce console output when running in the IDE. -->
		<!-- These dependencies are excluded from the application JAR by default. -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.7</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
			<scope>runtime</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>

			<!-- Java Compiler -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
				</configuration>
			</plugin>

			<!-- We use the maven-shade plugin to create a fat jar that contains all necessary dependencies. -->
			<!-- Change the value of <mainClass>...</mainClass> if your program entry point changes. -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
				<version>3.0.0</version>
				<executions>
					<!-- Run shade goal on package phase -->
					<execution>
						<phase>package</phase>
						<goals>
							<goal>shade</goal>
						</goals>
						<configuration>
							<artifactSet>
								<excludes>
									<exclude>org.apache.flink:force-shading</exclude>
									<exclude>com.google.code.findbugs:jsr305</exclude>
									<exclude>org.slf4j:*</exclude>
									<exclude>log4j:*</exclude>
								</excludes>
							</artifactSet>
							<filters>
								<filter>
									<!-- Do not copy the signatures in the META-INF folder.
									Otherwise, this might cause SecurityExceptions when using the JAR. -->
									<artifact>*:*</artifact>
									<excludes>
										<exclude>META-INF/*.SF</exclude>
										<exclude>META-INF/*.DSA</exclude>
										<exclude>META-INF/*.RSA</exclude>
									</excludes>
								</filter>
							</filters>
							<transformers>
								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
									<mainClass>myflink.StreamingJob</mainClass>
								</transformer>
							</transformers>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>

		<pluginManagement>
			<plugins>

				<!-- This improves the out-of-the-box experience in Eclipse by resolving some warnings. -->
				<plugin>
					<groupId>org.eclipse.m2e</groupId>
					<artifactId>lifecycle-mapping</artifactId>
					<version>1.0.0</version>
					<configuration>
						<lifecycleMappingMetadata>
							<pluginExecutions>
								<pluginExecution>
									<pluginExecutionFilter>
										<groupId>org.apache.maven.plugins</groupId>
										<artifactId>maven-shade-plugin</artifactId>
										<versionRange>[3.0.0,)</versionRange>
										<goals>
											<goal>shade</goal>
										</goals>
									</pluginExecutionFilter>
									<action>
										<ignore/>
									</action>
								</pluginExecution>
								<pluginExecution>
									<pluginExecutionFilter>
										<groupId>org.apache.maven.plugins</groupId>
										<artifactId>maven-compiler-plugin</artifactId>
										<versionRange>[3.1,)</versionRange>
										<goals>
											<goal>testCompile</goal>
											<goal>compile</goal>
										</goals>
									</pluginExecutionFilter>
									<action>
										<ignore/>
									</action>
								</pluginExecution>
							</pluginExecutions>
						</lifecycleMappingMetadata>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>

	<!-- This profile helps to make things run out of the box in IntelliJ -->
	<!-- Its adds Flink's core classes to the runtime class path. -->
	<!-- Otherwise they are missing in IntelliJ, because the dependency is 'provided' -->
	<profiles>
		<profile>
			<id>add-dependencies-for-IDEA</id>

			<activation>
				<property>
					<name>idea.version</name>
				</property>
			</activation>

			<dependencies>
				<dependency>
					<groupId>org.apache.flink</groupId>
					<artifactId>flink-java</artifactId>
					<version>${flink.version}</version>
					<scope>compile</scope>
				</dependency>
				<dependency>
					<groupId>org.apache.flink</groupId>
					<artifactId>flink-streaming-java_${scala.binary.version}</artifactId>
					<version>${flink.version}</version>
					<scope>compile</scope>
				</dependency>
			</dependencies>
		</profile>
	</profiles>

</project>

这个pom文件主要是为了告诉Maven,本项目需要那些依赖,以及对应的版本信息。编辑好pom文件之后,build即可完成项目运行环境的配置。
将上述链接中的代码导入到java目录下的包的目录中去,将log4j复制到resource目录下。
由于本实验需要监听某个端口的通讯,需要安装netcat对端口通信进行模拟。在系统的包管理软件中下载并安装:
在这里插入图片描述安装第一个即可。到这里,基本的实验环境配置完成。尝试运行代码SocketWindowWordCount.java
在这里插入图片描述出现错误,No ExecutorFactory found to execute the application。在网上查阅资料发现原因是缺少了一个依赖:

<dependency>
	<groupId>org.apache.flink</groupId>
	<artifactId>flink-clients_${scala.binary.version}</artifactId>
	<version>${flink.version}</version>
</dependency>

在pom文件中添加上述依赖,即可。注意pom中jar包的Provided属性最好隐掉,不然可能会出现找不到依赖的情况。
同时需要注意要在运行代码前先启动netcat程序,如下:

nc -l -p 9000

不然,会出现如下错误:
在这里插入图片描述按照上述步骤完成之后,即可在本地测试代码了:
在这里插入图片描述

2.提交集群

完成本地测试之后,即可将代码提交到集群,采用的方法是打成Jar包上传到集群。比较简单,步骤如下:
在这里插入图片描述
注意选择元数据目录为src下,做如下修改:
在这里插入图片描述
而后在最上方菜单栏中选择build,等待即可。随后在项目目录下出现out目录,里面的jar包就是打好的。
在这里插入图片描述
将Jar包拷贝到集群上,在master节点上安装netcat:

yum install nc -y

启动集群:

start-cluster.sh

而后启动netcat,再提交jar包到任意位置即可(注意顺序不要搞反):

flink run test.jar

在这里插入图片描述
实验的结果在浏览器的UI界面中可以看到,在netcat连接的终端输入数据,在UI界面中看到计算结果。
注意,需要对代码做修改的是,localhost修改为master。我尝试过localhost和127.0.0.1,都不可以,报错信息都是连接拒绝。

四、实验总结

本次实验完成了Flink的第一个应用,整个流程比较简单,但是仍然有很多细小的东西需要注意。以上就是第一个Flink应用程序的全部内容,欢迎交流!

ps:代码解析
package myflink;

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;

public class SocketWindowWordCount {

	public static void main(String[] args) throws Exception {
	
		// 创建 execution environment
		StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

		// Flink提供了这样的接口:通过连接 socket 获取输入数据,这里连接本地的9000,注意提交集群修改为master
		DataStream<String> text = env.socketTextStream("localhost", 9000, "\n");
		// 解析数据,按 word 分组,开窗(5s统计一次),聚合
		DataStream<Tuple2<String, Integer>> windowCounts = text
				.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
					@Override
					public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
						for (String word : value.split("\\s")) {
							out.collect(Tuple2.of(word, 1));
						}
					}
				})
				.keyBy(0)
				.timeWindow(Time.seconds(5))
				.sum(1);
		// 将结果打印到控制台,注意这里使用的是单线程打印,而非多线程
		windowCounts.print().setParallelism(1);
		env.execute("Socket Window WordCount");
	}
}

五、计算热门商品

1.概述

对任务背景进行介绍,本部分是对商品售卖数据作为数据源,数据是CSV格式的,需要模拟流~
统计指定时间窗口内的热门商品类别,计算TOPN即可。
在部分将侧重于对代码本身的分析而不对IDEA和集群的操作做过多的介绍。

2.代码解析
2.1 商品类
	/** 用户行为数据结构 **/
	public static class UserBehavior {
		public long userId;         // 用户ID
		public long itemId;         // 商品ID
		public int categoryId;      // 商品类目ID
		public String behavior;     // 用户行为, 包括("pv", "buy", "cart", "fav")
		public long timestamp;      // 行为发生的时间戳,单位秒
	}

构建用户的行为数据的结构,在整个代码中是主要数据结构,通过csv构建数据源。

2.2 主函数
	public static void main(String[] args) throws Exception {
		// 创建 execution environment
		StreamExecutionEnvironment env  =  StreamExecutionEnvironment.getExecutionEnvironment();
		// 告诉系统按照 EventTime 处理
		env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
		// 为了打印到控制台的结果不乱序,我们配置全局的并发为1,改变并发对结果正确性没有影响
		env.setParallelism(1);
		// UserBehavior.csv 的本地文件路径, 在 resources 目录下
		URL fileUrl = HotItems.class.getClassLoader().getResource("UserBehavior.csv");
		Path filePath = Path.fromLocalFile(new File(fileUrl.toURI()));
		// 抽取 UserBehavior 的 TypeInformation,是一个 PojoTypeInfo
		PojoTypeInfo<UserBehavior> pojoType = (PojoTypeInfo<UserBehavior>) TypeExtractor.createTypeInfo(UserBehavior.class);
		// 由于 Java 反射抽取出的字段顺序是不确定的,需要显式指定下文件中字段的顺序
		String[] fieldOrder = new String[]{"userId", "itemId", "categoryId", "behavior", "timestamp"};
		// 创建 PojoCsvInputFormat
		PojoCsvInputFormat<UserBehavior> csvInput = new PojoCsvInputFormat<>(filePath, pojoType, fieldOrder);
		env
			// 创建数据源,得到 UserBehavior 类型的 DataStream
			.createInput(csvInput, pojoType)
			// 抽取出时间和生成 watermark
			.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<UserBehavior>() {
				@Override
				public long extractAscendingTimestamp(UserBehavior userBehavior) {
				// 原始数据单位秒,将其转成毫秒
					return userBehavior.timestamp * 1000;
				}
			})
			// filter函数过滤出只有点击的数据
			.filter(new FilterFunction<UserBehavior>() {
				@Override
				public boolean filter(UserBehavior userBehavior) throws Exception {
					// 过滤出只有点击的数据
					return userBehavior.behavior.equals("pv");
				}
			})
			.keyBy("itemId")
			.timeWindow(Time.minutes(60), Time.minutes(5))
			.aggregate(new CountAgg(), new WindowResultFunction())
			.keyBy("windowEnd")
			.process(new TopNHotItems(3))
			.print();

		env.execute("Hot Items Job");
	}

主函数中大多代码都是为了读取csv文件中的数据而产生流数据,首先给出URL,放在resources目录下。指定pojoType,UserBehavior.class用于pojoType的初始化!

// 创建 PojoCsvInputFormat
		PojoCsvInputFormat<UserBehavior> csvInput = new PojoCsvInputFormat<>(filePath, pojoType, fieldOrder);

使用PojoCsvInputFormat类初始化使用文件路径pojoType还有属性的顺序
env中涉及到的对时间戳的指定使用AssignTimestampsAndWatermarks方法,由于我们后续需要计算时间窗口内的实时热门商品,所以采用的时间为事件时间。
从CSV中读取数据,这是一个标准的范式。

2.3 流计算操作

在2.2的主函数中可以看到完整的流计算过程:
在这里插入图片描述
在聚合和处理两个部分使用自编函数进行处理,函数如下:

/** 用于输出窗口的结果 */
	public static class WindowResultFunction implements WindowFunction<Long, ItemViewCount, Tuple, TimeWindow> {

		@Override
		public void apply(
			Tuple key,  // 窗口的主键,即 itemId
			TimeWindow window,  // 窗口
			Iterable<Long> aggregateResult, // 聚合函数的结果,即 count 值
			Collector<ItemViewCount> collector  // 输出类型为 ItemViewCount
		) throws Exception {
			Long itemId = ((Tuple1<Long>) key).f0;
			Long count = aggregateResult.iterator().next();
			collector.collect(ItemViewCount.of(itemId, window.getEnd(), count));
		}
	}

	/** COUNT 统计的聚合函数实现,每出现一条记录加一 */
	public static class CountAgg implements AggregateFunction<UserBehavior, Long, Long> {

		@Override
		public Long createAccumulator() {
			return 0L;
		}
		@Override
		public Long add(UserBehavior userBehavior, Long acc) {
			return acc + 1;
		}
		@Override
		public Long getResult(Long acc) {
			return acc;
		}
		@Override
		public Long merge(Long acc1, Long acc2) {
			return acc1 + acc2;
		}
	}
	/** 求某个窗口中前 N 名的热门点击商品,key 为窗口时间戳,输出为 TopN 的结果字符串 */
	public static class TopNHotItems extends KeyedProcessFunction<Tuple, ItemViewCount, String> {

		private final int topSize;

		public TopNHotItems(int topSize) {
			this.topSize = topSize;
		}

		// 用于存储商品与点击数的状态,待收齐同一个窗口的数据后,再触发 TopN 计算
		private ListState<ItemViewCount> itemState;

		@Override
		public void open(Configuration parameters) throws Exception {
			super.open(parameters);
			ListStateDescriptor<ItemViewCount> itemsStateDesc = new ListStateDescriptor<>(
				"itemState-state",
				ItemViewCount.class);
			itemState = getRuntimeContext().getListState(itemsStateDesc);
		}

		@Override
		public void processElement(
			ItemViewCount input,
			Context context,
			Collector<String> collector) throws Exception {

			// 每条数据都保存到状态中
			itemState.add(input);
			// 注册 windowEnd+1 的 EventTime Timer, 当触发时,说明收齐了属于windowEnd窗口的所有商品数据
			context.timerService().registerEventTimeTimer(input.windowEnd + 1);
		}

		@Override
		public void onTimer(
			long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
			// 获取收到的所有商品点击量
			List<ItemViewCount> allItems = new ArrayList<>();
			for (ItemViewCount item : itemState.get()) {
				allItems.add(item);
			}
			// 提前清除状态中的数据,释放空间
			itemState.clear();
			// 按照点击量从大到小排序
			allItems.sort(new Comparator<ItemViewCount>() {
				@Override
				public int compare(ItemViewCount o1, ItemViewCount o2) {
					return (int) (o2.viewCount - o1.viewCount);
				}
			});
			// 将排名信息格式化成 String, 便于打印
			StringBuilder result = new StringBuilder();
			result.append("====================================\n");
			result.append("时间: ").append(new Timestamp(timestamp-1)).append("\n");
                        for (int i=0; i<allItems.size() && i < topSize; i++) {
				ItemViewCount currentItem = allItems.get(i);
				// No1:  商品ID=12224  浏览量=2413
				result.append("No").append(i).append(":")
					.append("  商品ID=").append(currentItem.itemId)
					.append("  浏览量=").append(currentItem.viewCount)
					.append("\n");
			}
			result.append("====================================\n\n");

			// 控制输出频率,模拟实时滚动结果
			Thread.sleep(1000);

			out.collect(result.toString());
		}
	}

由于Flink函数内置的一些api要求,在自己实现这个过程的时候要对内置的方法进行实现。

2.4 计算结果

在这里插入图片描述

关于Flink一些关键问题我将在后续的博客中更新。

2021.01.11 by hash怪

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值