几乎所有的 Flink 应用程序,包括批处理和流处理,都依赖于外部配置参数,这些参数被用来指定输入和输出源(如路径或者地址),系统参数(并发数,运行时配置)和应用程序的可配参数(通常用在自定义函数中)。
Flink 提供了一个简单的叫做 ParameterTool 的使用工具,提供了一些基础的工具来解决这些问题,当然你也可以不用这里所描述的ParameterTool,使用其他的框架,如:Commons CLI 和 argparse4j 在 Flink 中也是支持的。
一、获取配置值,并传入 PratameterTool。
ParameterTool 提供了一系列预定义的静态方法来读取配置信息,ParameterTool 内部是一个 Map<String, String>,所以很容易与你自己的配置形式相集成。
1、从命令行中获取。
public static void main(String[] args) throws Exception {
ParameterTool parameter = ParameterTool.fromArgs(args);
……
}
在执行命令中使用:--name 张三 --age 20
2、从 properties 文件中获取。
// kafka.properties
ParameterTool parameter = ParameterTool.fromPropertiesFile(Constant.CONFIG_NAME_KAFKA);
3、从系统属性中获取。
当启动一个JVM时,你可以给它传递一些系统属性,如:-Dinput=hdfs:///mydata,你可以用这些系统属性来初始化 PrameterTool
ParameterTool parameter = ParameterTool.fromSystemProperties();
二、在程序中,使用 ParameterTool 参数。
1、直接从 ParameterTool 中获取,使用。
ParameterTool parameter = ParameterTool.fromPropertiesFile(Constant.CONFIG_NAME_KAFKA);
parameter.getNumberOfParameters(); // 参数个数
String topic = parameter.getRequired("kafka.topic");
String key = parameter.get("kafka.key", "test");
int port = parameter.getInt("kafka.port", 5672);
// kafka 配置信息
Properties properties = new Properties();
properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG, groupId);
properties.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, reset);
// kafka consumer
FlinkKafkaConsumer consumer = new FlinkKafkaConsumer(topic, new SimpleStringSchema(), properties);
2、因为 ParameterTool 是可序列化的,可将 ParameterToole 传递给函数使用。
// 数据处理
sourceStream
.connect(configBroadcastStream)
.process(new KafkaUserOpinionBroadcastProcessFunction(parameterTool))
.uid("broadcast-connect-process");
public class KafkaUserOpinionBroadcastProcessFunction extends KeyedBroadcastProcessFunction<Tuple, UserOpinionData, SensitiveWordConfig, String> implements Serializable {
private static final long serialVersionUID = 10000L;
// 参数信息
private Map<String, String> globalJobParametersMap;
public KafkaUserOpinionBroadcastProcessFunction(ParameterTool parameterTool) {
this.globalJobParametersMap = parameterTool.toMap();
}
……
}
3、将 ParameterTool 注册为全局参数使用。
在 ExecutionConfig 中注册为全作业参数的参数,可以被 JobManager 的 web 端以及用户所有自定义函数中以配置值的形式访问。
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
ParameterTool parameter = ParameterTool.fromPropertiesFile(Constant.CONFIG_NAME_KAFKA);
env.getConfig().setGlobalJobParameters(parameter);
static final class UserOpinionFilter extends RichFlatMapFunction<UserOpinionData, Tuple2<String, Integer>> {
ParameterTool parameterTool;
@Override
public void open(Configuration parameters) throws Exception {
parameterTool = (ParameterTool) getRuntimeContext().getExecutionConfig().getGlobalJobParameters();
}
@Override
public void flatMap(UserOpinionData userOpinionData, Collector<Tuple2<String, Integer>> collector) throws Exception {
}
}
该方法使用不当,可能会造成 flink on yarn 任务提交不了,如
[root@snd-gp2-slave bin]# ./flink run -m yarn-cluster -yn 1 -p 4 -yjm 1024 -ytm 4096 -ynm FlinkOnYarnSession-UserOpinionDataConsumer -d -c com.igg.flink.tool.userOpinionMonitor.kafka.consumer.JavaKafkaUserOpinionDataConsumer /home/flink/igg-flink-tool-1.0.0-SNAPSHOT.jar
2020-01-08 22:31:05,143 INFO org.apache.flink.yarn.cli.FlinkYarnSessionCli - No path for the flink jar passed. Using the location of class org.apache.flink.yarn.YarnClusterDescriptor to locate the jar
2020-01-08 22:31:05,143 INFO org.apache.flink.yarn.cli.FlinkYarnSessionCli - No path for the flink jar passed. Using the location of class org.apache.flink.yarn.YarnClusterDescriptor to locate the jar
或者 checkpoint 时间很长。
三、使用distributedCache
parametertool 进行参数传递会很方便,但是也仅仅适用于少量参数的传递,如果有比较大量的数据传递,flink则提供了另外的方
式来进行,其中之一即是 distributedCache。
在定义DAG图的时候指定缓存文件
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// Register a file from HDFS
env.registerCachedFile("hdfs:///path/to/file", "sensitiveInfo");
flink本身支持指定本地的缓存文件,但一般而言,建议指定分布式存储,如hdfs上的文件,并为其指定一个名称。
使用起来也很简单,继承Rich函数,在open方法中进行获取。
// 定义文件缓存变量
private File sensitiveInfo = null;
@Override
public void open(Configuration parameters) throws Exception {
sensitiveInfo = getRuntimeContext().getDistributedCache().getFile("sensitiveInfo");
}
应该说定义的缓存本身都是固定的,缓存不会变化,那么如果缓存本身随着时间也会发生变化,怎么办?
那就用connectStream,其实也是流的聚合了。
四、使用connectStream
这个也是在其他计算引擎中广泛使用的方法之一。
使用 ConnectedStream 的前提当然是需要有一个动态的流,比如在主数据之外,还有一些规则数据,这些规则数据会通过
Restful服务来发布。
// 广播流
BroadcastStream configBroadcastStream = configStream
.map((MapFunction<String, SensitiveWordConfig>) s -> SensitiveWordConfig.buildSensitiveWordConfig(s))
.filter((FilterFunction<SensitiveWordConfig>) sensitiveWordConfig -> sensitiveWordConfig != null)
.uid("sensitive-word-source")
.broadcast(new MapStateDescriptor(
"user-opinion-broadcast-state-desc",
BasicTypeInfo.STRING_TYPE_INFO,
TypeInformation.of(new TypeHint<SensitiveWordConfig>(){})
));
具体的使用代码,可以看笔者的博文 基于Kafka+Flink+Hutool的用户言论实时监控案例
对于 ConnectedStream,数据是从 JM 发送到 TM,有时我们需要将数据从 TM发送到 JM,要如何实现呢?可以使用
accumulator。
flink提供了accumulator来实现数据的回传,亦即从 TM 传回到 JM。
flink本身提供了一些内置的accumulator:
- IntCounter, LongCounter, DoubleCounter – allows summing together int, long, double values sent from task managers
- AverageAccumulator – calculates an average of double values
- LongMaximum, LongMinimum, IntMaximum, IntMinimum, DoubleMaximum, DoubleMinimum – accumulators to determine maximum and minimum values for different types
- Histogram – used to computed distribution of values from task managers
首先需要定义一个accumulator,然后在某个自定义函数中来注册它,这样在客户端就可以获取相应的的值。
new RichFlatMapFunction<String, Tuple2<String, Integer>>() {
// Create an accumulator
private IntCounter linesNum = new IntCounter();
@Override
public void open(Configuration parameters) throws Exception {
// Register accumulator
getRuntimeContext().addAccumulator("linesNum", linesNum);
}
@Override
public void flatMap(String line, Collector<Tuple2<String, Integer>> out) throws Exception {
String[] words = line.split("\\W+");
for (String word : words) {
out.collect(new Tuple2<>(word, 1));
}
// Increment after each line is processed
linesNum.add(1);
}
}
在定义DAG中获取回传数据
public static void main(String[] args) throws Exception {
// todo:
// Get accumulator result
int linesNum = env.getLastJobExecutionResult().getAccumulatorResult("linesNum");
System.out.println(linesNum);
env.execute();
}
上面介绍了几种参数传递的方式,在日常的使用中,可能不仅仅是使用其中一种,或许是某些的组合,比如通过parametertool来
传递hdfs的路径,再通过filecache来读取缓存。
如果有写的不对的地方,欢迎大家指正。有什么疑问,欢迎加QQ群:176098255