(四)Flink初体验-1

文章基于flink1.15,谨防走错房间
flink官网

first steps

Flink旨在以闪电般的速度处理连续的数据流。这篇简短的指南将向您展示如何下载、安装和运行Flink的最新稳定版本。你还将运行一个Flink作业示例,并在web UI中查看它。

下载flink

Flink运行在所有类unix环境中,例如Linux、Mac OS X和Cygwin(用于Windows)。您需要安装Java 11。要检查安装的Java版本,在你的终端输入:

$ java -version

进入到下载页面download
(四)Flink初体验-1_第1张图片
将flink压缩包上传到linux服务器(虚拟机啥的都行,容器请自行移步官网),执行解压命令

$ tar -xzf flink-*.tgz
# 进入flink解压目录,查看列表信息
$ cd flink-* && ls -l

(四)Flink初体验-1_第2张图片
上面图片中,有几个地方需要记下来:

  • bin/ directory contains the flink binary as well as several bash scripts that manage various jobs and tasks
  • conf/ directory contains configuration files, including flink-conf.yaml
  • examples/ directory contains sample applications that can be used as is with Flink

bin目录下是各种脚本,conf目录下是各种配置文件,examples是各种程序demo

启动和停止flink本地集群

执行命令:

$ ./bin/start-cluster.sh

在这里插入图片描述
执行jps命令
在这里插入图片描述
执行停止命令

$ ./bin/stop-cluster.sh

在这里插入图片描述

提交一个flink作业到本地集群

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初体验-1_第3张图片
对于作业执行,Flink有两个操作符。第一个是源操作符,它从集合源读取数据。第二个操作符是转换操作符,用于汇总单词计数。了解有关DataStream操作符的更多信息。

您还可以查看作业执行的时间轴:
(四)Flink初体验-1_第4张图片
您已经成功运行了Flink应用程序!请随意从示例/文件夹中选择任何其他JAR归档文件,或者部署您自己的作业!

小结

您下载了Flink,研究了项目目录,启动和停止了一个本地集群,并提交了一个示例Flink作业!

要了解更多关于Flink的基础知识,请查看概念部分。如果您想尝试更多的实际操作,请尝试其中一个教程。

two steps

使用DataStream API进行欺诈检测

Apache Flink提供了一个DataStream API,用于构建健壮的、有状态的流应用程序。它提供了对状态和时间的细粒度控制,从而支持高级事件驱动系统的实现。在这个分步指导中,您将学习如何使用Flink的DataStream API构建一个有状态的流媒体应用程序。

在数字时代,信用卡诈骗日益受到关注。犯罪分子通过诈骗或侵入不安全的系统来盗取信用卡号码。被盗号码的检测方法是进行一次或多次小额购买,通常是一美元或更少。如果这种方法有效,他们就会进行更重要的购买,以获得自己可以出售或保留的物品。

在本教程中,您将构建一个欺诈检测系统,以便对可疑的信用卡交易发出警报。通过使用一组简单的规则,您将看到Flink如何允许我们实现高级业务逻辑和实时操作。

准备

  • Java 11
  • Maven
  • idea(根据个人喜好)

所提供的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);
    }
}

基于上面版本写一个实时app(v1)

对于上面那个版本,欺诈检测器应该输出一个警报,用于任何进行小交易后立即进行大交易的帐户。小的是小于1美元,大的是大于500美元。假设您的欺诈检测器为特定帐户处理以下交易流。
(四)Flink初体验-1_第5张图片
交易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 来检查是否设置了标志。

欺诈检测器v2:状态+时间=❤️

骗子不会等太久才进行大额购买,以减少他们的测试交易被注意到的机会。例如,假设您想为欺诈检测器设置1分钟超时;也就是说,在前面的示例中,只有在交易3和交易4发生的时间间隔小于1分钟时,才会被认为是欺诈。Flink的KeyedProcessFunction允许您设置计时器,以便在未来的某个时间点调用回调方法。

让我们看看如何修改我们的Job以符合我们的新需求:

  • 当该标志被设置为true时,在以后也设置一个1分钟的计时器。
  • 当定时器触发时,通过清除其状态重置该标志。
  • 如果该标志被清除,则应该取消计时器。

要取消计时器,必须记住它设置的时间,记住意味着状态,所以首先要创建计时器状态和标志状态。

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}

你可能感兴趣的:(flink,flink)