文章基于flink1.15,谨防走错房间
flink官网
Flink旨在以闪电般的速度处理连续的数据流。这篇简短的指南将向您展示如何下载、安装和运行Flink的最新稳定版本。你还将运行一个Flink作业示例,并在web UI中查看它。
Flink运行在所有类unix环境中,例如Linux、Mac OS X和Cygwin(用于Windows)。您需要安装Java 11。要检查安装的Java版本,在你的终端输入:
$ java -version
进入到下载页面download
将flink压缩包上传到linux服务器(虚拟机啥的都行,容器请自行移步官网),执行解压命令
$ tar -xzf flink-*.tgz
# 进入flink解压目录,查看列表信息
$ cd flink-* && ls -l
bin目录下是各种脚本,conf目录下是各种配置文件,examples是各种程序demo
执行命令:
$ ./bin/start-cluster.sh
$ ./bin/stop-cluster.sh
Flink提供了一个CLI工具,bin/ Flink,可以运行打包成Java ARchives (JAR)的程序,并控制它们的执行。提交作业意味着将作业的JAR文件和相关依赖项上传到正在运行的Flink集群并执行它。
Flink发行版附带了示例作业,可以在examples/文件夹中找到。
要将示例单词计数作业部署到正在运行的集群,发出以下命令:
$ ./bin/flink run examples/streaming/WordCount.jar
您可以通过查看日志来验证输出结果:
$ tail log/flink-*-taskexecutor-*.out
样例输出
(nymph,1)
(in,3)
(thy,1)
(orisons,1)
(be,4)
(all,2)
(my,1)
(sins,1)
(remember,1)
(d,4)
此外,您可以检查Flink的web UI来监控集群的状态和正在运行的作业。
查看执行的数据流计划:
对于作业执行,Flink有两个操作符。第一个是源操作符,它从集合源读取数据。第二个操作符是转换操作符,用于汇总单词计数。了解有关DataStream操作符的更多信息。
您还可以查看作业执行的时间轴:
您已经成功运行了Flink应用程序!请随意从示例/文件夹中选择任何其他JAR归档文件,或者部署您自己的作业!
您下载了Flink,研究了项目目录,启动和停止了一个本地集群,并提交了一个示例Flink作业!
要了解更多关于Flink的基础知识,请查看概念部分。如果您想尝试更多的实际操作,请尝试其中一个教程。
Apache Flink提供了一个DataStream API,用于构建健壮的、有状态的流应用程序。它提供了对状态和时间的细粒度控制,从而支持高级事件驱动系统的实现。在这个分步指导中,您将学习如何使用Flink的DataStream API构建一个有状态的流媒体应用程序。
在数字时代,信用卡诈骗日益受到关注。犯罪分子通过诈骗或侵入不安全的系统来盗取信用卡号码。被盗号码的检测方法是进行一次或多次小额购买,通常是一美元或更少。如果这种方法有效,他们就会进行更重要的购买,以获得自己可以出售或保留的物品。
在本教程中,您将构建一个欺诈检测系统,以便对可疑的信用卡交易发出警报。通过使用一组简单的规则,您将看到Flink如何允许我们实现高级业务逻辑和实时操作。
所提供的Flink Maven原型将快速创建一个带有所有必要依赖项的框架项目,因此您只需要专注于填写业务逻辑。这些依赖关系包括Flink -stream -java,它是所有Flink流应用程序的核心依赖关系,以及Flink -walkthrough-common,它具有数据生成器和特定于此演练的其他类。
$ mvn archetype:generate \
-DarchetypeGroupId=org.apache.flink \
-DarchetypeArtifactId=flink-walkthrough-datastream-java \
-DarchetypeVersion=1.15.0 \
-DgroupId=frauddetection \
-DartifactId=frauddetection \
-Dversion=0.1 \
-Dpackage=spendreport \
-DinteractiveMode=false
如果你喜欢,你可以编辑groupId, artifactId和package。使用上述参数,Maven将创建一个名为frauddetection的文件夹,其中包含完成本教程所需的所有依赖项。将项目导入编辑器后,你可以找到一个文件FraudDetectionJob.java(或FraudDetectionJob.scala),其中包含以下代码,你可以直接在IDE中运行。尝试在数据流中设置断点,并在DEBUG模式下运行代码,以了解一切是如何工作的。
FraudDetectionJob.java
package spendreport;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.walkthrough.common.sink.AlertSink;
import org.apache.flink.walkthrough.common.entity.Alert;
import org.apache.flink.walkthrough.common.entity.Transaction;
import org.apache.flink.walkthrough.common.source.TransactionSource;
public class FraudDetectionJob {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<Transaction> transactions = env
.addSource(new TransactionSource())
.name("transactions");
DataStream<Alert> alerts = transactions
.keyBy(Transaction::getAccountId)
.process(new FraudDetector())
.name("fraud-detector");
alerts
.addSink(new AlertSink())
.name("send-alerts");
env.execute("Fraud Detection");
}
}
FraudDetector.java
package spendreport;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
import org.apache.flink.walkthrough.common.entity.Alert;
import org.apache.flink.walkthrough.common.entity.Transaction;
public class FraudDetector extends KeyedProcessFunction<Long, Transaction, Alert> {
private static final long serialVersionUID = 1L;
private static final double SMALL_AMOUNT = 1.00;
private static final double LARGE_AMOUNT = 500.00;
private static final long ONE_MINUTE = 60 * 1000;
@Override
public void processElement(
Transaction transaction,
Context context,
Collector<Alert> collector) throws Exception {
Alert alert = new Alert();
alert.setId(transaction.getAccountId());
collector.collect(alert);
}
}
对于上面那个版本,欺诈检测器应该输出一个警报,用于任何进行小交易后立即进行大交易的帐户。小的是小于1美元,大的是大于500美元。假设您的欺诈检测器为特定帐户处理以下交易流。
交易3和交易4应该被标记为欺诈,因为它是一笔0.09美元的小交易,然后是一笔510美元的大交易。或者,交易7、8和9不是欺诈,因为小金额0.02美元并没有紧接着出现大金额;相反,有一个打破该模式的中间事务。
要做到这一点,欺诈检测器必须跨事件记住信息;只有在前一笔交易很小的情况下,大交易才属于欺诈。记住跨事件的信息需要状态,这就是我们决定使用KeyedProcessFunction的原因。它提供了对状态和时间的细粒度控制,这将允许我们在本演练中使用更复杂的需求来发展我们的算法。
最简单的实现是一个布尔标记,在处理小事务时设置。当一个大的事务通过时,您可以简单地检查是否为该帐户设置了标志。
然而,仅仅在FraudDetector类中将标志实现为成员变量是不行的。Flink处理多个帐户的交易FraudDetector的同一个对象实例,这意味着如果账户A和B是FraudDetector的路由通过相同的实例,一个事务的账户可以设置国旗为true,然后交易帐户B可以引发一个错误的警报。当然,我们可以使用像Map这样的数据结构来跟踪各个键的标志,但是,一个简单的成员变量是不容错的,而它的所有信息是容错的。
为了解决这些挑战,Flink提供了容错状态的原语,几乎与常规成员变量一样容易使用。
Flink中最基本的状态类型是ValueState,这是一种为它包装的任何变量添加容错性的数据类型。ValueState是键控状态的一种形式,这意味着它只在应用于键控上下文的操作符中可用;DataStream#keyBy后面的任何操作符。运算符的键控状态自动限定为当前处理的记录的键。在本例中,键是当前事务的帐户id(由keyBy()声明),并且FraudDetector为每个帐户维护一个独立的状态。ValueState是使用ValueStateDes创建的。
FraudDetector.java 可以这样子写
public class FraudDetector extends KeyedProcessFunction<Long, Transaction, Alert> {
private static final long serialVersionUID = 1L;
private transient ValueState<Boolean> flagState;
@Override
public void open(Configuration parameters) {
ValueStateDescriptor<Boolean> flagDescriptor = new ValueStateDescriptor<>(
"flag",
Types.BOOLEAN);
flagState = getRuntimeContext().getState(flagDescriptor);
}
ValueState是一个包装器类,类似于Java标准库中的AtomicReference或AtomicLong。它提供了三种与内容交互的方法;Update设置状态,value获取当前值,clear删除其内容。如果一个特定键的状态为空,例如在应用程序的开始或调用ValueState#clear之后,那么ValueState#value将返回null。对ValueState#值返回的对象的修改不能保证被系统识别,因此所有的更改必须通过ValueState#update来执行。
下面,您可以看到如何使用flag state来跟踪潜在的欺诈交易的示例。
@Override
public void processElement(
Transaction transaction,
Context context,
Collector<Alert> collector) throws Exception {
// Get the current state for the current key
Boolean lastTransactionWasSmall = flagState.value();
// Check if the flag is set
if (lastTransactionWasSmall != null) {
if (transaction.getAmount() > LARGE_AMOUNT) {
// Output an alert downstream
Alert alert = new Alert();
alert.setId(transaction.getAccountId());
collector.collect(alert);
}
// Clean up our state
flagState.clear();
}
if (transaction.getAmount() < SMALL_AMOUNT) {
// Set the flag to true
flagState.update(true);
}
}
对于每个交易,欺诈检测器检查该帐户的标志状态。记住,ValueState总是限定为当前键,即帐户。如果标志为非空,则该帐户的最后一个事务很小,因此,如果该事务的金额很大,则检测器输出欺诈警报。
检查之后,标志状态被无条件清除。要么当前事务引起了欺诈警报,模式已经结束,要么当前事务没有引起警报,模式已被破坏,需要重新启动。
最后,检查交易金额以查看它是否很小。如果是这样,则设置该标志,以便下一个事件可以对其进行检查。请注意,ValueState 有三种状态:unset (null)、true 和 false,因为所有 ValueState 都是可以为 null 的。此作业仅使用 unset (null) 和 true 来检查是否设置了标志。
骗子不会等太久才进行大额购买,以减少他们的测试交易被注意到的机会。例如,假设您想为欺诈检测器设置1分钟超时;也就是说,在前面的示例中,只有在交易3和交易4发生的时间间隔小于1分钟时,才会被认为是欺诈。Flink的KeyedProcessFunction允许您设置计时器,以便在未来的某个时间点调用回调方法。
让我们看看如何修改我们的Job以符合我们的新需求:
要取消计时器,必须记住它设置的时间,记住意味着状态,所以首先要创建计时器状态和标志状态。
private transient ValueState<Boolean> flagState;
private transient ValueState<Long> timerState;
@Override
public void open(Configuration parameters) {
ValueStateDescriptor<Boolean> flagDescriptor = new ValueStateDescriptor<>(
"flag",
Types.BOOLEAN);
flagState = getRuntimeContext().getState(flagDescriptor);
ValueStateDescriptor<Long> timerDescriptor = new ValueStateDescriptor<>(
"timer-state",
Types.LONG);
timerState = getRuntimeContext().getState(timerDescriptor);
}
使用包含计时器服务的上下文调用KeyedProcessFunction#processElement。定时器服务可用于查询当前时间、注册定时器和删除定时器。这样,您就可以在以后设置标志时设置一个1分钟的计时器,并将时间戳存储在timerState中。
if (transaction.getAmount() < SMALL_AMOUNT) {
// set the flag to true
flagState.update(true);
// set the timer and timer state
long timer = context.timerService().currentProcessingTime() + ONE_MINUTE;
context.timerService().registerProcessingTimeTimer(timer);
timerState.update(timer);
}
处理时间是挂钟时间,由操作员运行的机器的系统时钟决定。
当计时器触发时,它调用KeyedProcessFunction#onTimer。重写此方法是实现回调以重置标志的方法。
public void onTimer(long timestamp, OnTimerContext ctx, Collector<Alert> out) {
// remove flag after 1 minute
timerState.clear();
flagState.clear();
}
最后,要取消定时器,需要删除已注册的定时器,并删除定时器的状态。您可以将其封装在helper方法中,并调用该方法而不是flagState.clear()。
private void cleanUp(Context ctx) throws Exception {
// delete timer
Long timer = timerState.value();
ctx.timerService().deleteProcessingTimeTimer(timer);
// clean up all state
timerState.clear();
flagState.clear();
}
就是这样,一个功能完整、有状态的分布式流媒体应用程序!
Final Application
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
import org.apache.flink.walkthrough.common.entity.Alert;
import org.apache.flink.walkthrough.common.entity.Transaction;
public class FraudDetector extends KeyedProcessFunction<Long, Transaction, Alert> {
private static final long serialVersionUID = 1L;
private static final double SMALL_AMOUNT = 1.00;
private static final double LARGE_AMOUNT = 500.00;
private static final long ONE_MINUTE = 60 * 1000;
private transient ValueState<Boolean> flagState;
private transient ValueState<Long> timerState;
@Override
public void open(Configuration parameters) {
ValueStateDescriptor<Boolean> flagDescriptor = new ValueStateDescriptor<>(
"flag",
Types.BOOLEAN);
flagState = getRuntimeContext().getState(flagDescriptor);
ValueStateDescriptor<Long> timerDescriptor = new ValueStateDescriptor<>(
"timer-state",
Types.LONG);
timerState = getRuntimeContext().getState(timerDescriptor);
}
@Override
public void processElement(
Transaction transaction,
Context context,
Collector<Alert> collector) throws Exception {
// Get the current state for the current key
Boolean lastTransactionWasSmall = flagState.value();
// Check if the flag is set
if (lastTransactionWasSmall != null) {
if (transaction.getAmount() > LARGE_AMOUNT) {
//Output an alert downstream
Alert alert = new Alert();
alert.setId(transaction.getAccountId());
collector.collect(alert);
}
// Clean up our state
cleanUp(context);
}
if (transaction.getAmount() < SMALL_AMOUNT) {
// set the flag to true
flagState.update(true);
long timer = context.timerService().currentProcessingTime() + ONE_MINUTE;
context.timerService().registerProcessingTimeTimer(timer);
timerState.update(timer);
}
}
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<Alert> out) {
// remove flag after 1 minute
timerState.clear();
flagState.clear();
}
private void cleanUp(Context ctx) throws Exception {
// delete timer
Long timer = timerState.value();
ctx.timerService().deleteProcessingTimeTimer(timer);
// clean up all state
timerState.clear();
flagState.clear();
}
}
使用提供的TransactionSource运行此代码将对帐户3发出欺诈警报。您应该在任务管理器日志中看到以下输出:
2022-05-19 14:22:06,220 INFO org.apache.flink.walkthrough.common.sink.AlertSink - Alert{id=3}
2022-05-19 14:22:11,383 INFO org.apache.flink.walkthrough.common.sink.AlertSink - Alert{id=3}
2022-05-19 14:22:16,551 INFO org.apache.flink.walkthrough.common.sink.AlertSink - Alert{id=3}
2022-05-19 14:22:21,723 INFO org.apache.flink.walkthrough.common.sink.AlertSink - Alert{id=3}
2022-05-19 14:22:26,896 INFO org.apache.flink.walkthrough.common.sink.AlertSink - Alert{id=3}