大数据-130 - Flink CEP 详解 - 捕获超时事件提取全解析:从原理到完整实战代码教程 恶意登录案例实现

点一下关注吧!!!非常感谢!!持续更新!!!

🚀 AI篇持续更新中!(长期更新)

AI炼丹日志-31- 千呼万唤始出来 GPT-5 发布!"快的模型 + 深度思考模型 + 实时路由",持续打造实用AI工具指南!📐🤖

💻 Java篇正式开启!(300篇)

目前2025年10月20日更新到: Java-153 深入浅出 MongoDB 全面的适用场景分析与选型指南 场景应用指南 MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务正在更新!深入浅出助你打牢基础!

📊 大数据板块已完成多项干货更新(300篇):

包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈! 大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解

章节内容

上节我们完成了如下的内容:

  • Flink CEP 核心组件
  • CEP 的应用场景
  • CEP 的优势

超时事件提取

当一个模式通过within关键字定义了检测窗口时间时,部分事件序列可能因为超过窗口长度而被丢弃,为了能够处理这些超时的部分匹配,select和flatSelectAPI调用允许制定超时处理程序。

FlinkCEP开发流程详解

1. 数据源转换

  • 从数据源(Kafka、文件、Socket等)获取原始数据流
  • 将数据转换为DataStream:
java 复制代码
  DataStream<Event> inputStream = env.addSource(source)
      .map(record -> new Event(record));
  • 常见数据源处理:
    • Kafka数据源:使用FlinkKafkaConsumer
    • 文件数据源:使用readTextFile/readFile方法

2. 模式定义与模式流创建

  • 定义事件模式(Pattern):
java 复制代码
  Pattern<Event, ?> pattern = Pattern.<Event>begin("start")
      .where(new SimpleCondition<Event>() {
          @Override
          public boolean filter(Event event) {
              return event.getType().equals("login");
          }
      })
      .next("middle")
      .within(Time.seconds(10));
  • 将DataStream与Pattern组合:
java 复制代码
  PatternStream<Event> patternStream = CEP.pattern(inputStream.keyBy(Event::getUserId), pattern);

3. 模式流处理

  • 使用Select/Process算子处理匹配事件:
java 复制代码
  DataStream<Alert> alerts = patternStream.process(
      new PatternProcessFunction<Event, Alert>() {
          @Override
          public void processMatch(
              Map<String, List<Event>> match,
              Context ctx,
              Collector<Alert> out) {
              out.collect(new Alert(match));
          }
      });
  • 处理方式选择:
    • select():简单提取匹配数据
    • process():复杂处理匹配数据
    • flatSelect():一对多处理

4. 结果输出

  • 结果数据流处理:
java 复制代码
  alerts.map(alert -> alert.toString())
  • 输出到目标库:
java 复制代码
  alerts.addSink(new SinkFunction<Alert>() {
      @Override
      public void invoke(Alert value, Context context) {
          // 写入数据库/消息队列等
      }
  });
  • 常见输出目标:
    • Kafka:使用FlinkKafkaProducer
    • 数据库:使用JDBCSink
    • 文件系统:使用StreamingFileSink

应用场景示例

  1. 风险控制:检测连续登录失败
  2. 运维监控:识别异常日志序列
  3. 物联网:设备状态异常检测
  4. 金融交易:可疑交易模式识别

SELECT 方法:

java 复制代码
SingleOutputStreamOperator<PayEvent> result =
    patternStream.select(orderTimeoutOutput, new PatternTimeoutFunction<PayEvent, PayEvent>() {
    @Override
    public PayEvent timeout(Map<String, List<PayEvent>> map, long l) throws Exception {
        return map.get("begin").get(0);
    }
}, new PatternSelectFunction<PayEvent, PayEvent>() {
    @Override
    public PayEvent select(Map<String, List<PayEvent>> map) throws Exception {
        return map.get("pay").get(0);
    }
});

对检测到的序列模式序列应用选择函数,对于每个模式序列,调用提供的 PatternSelectFunction,模式选择函数只能产生一个结果元素。 对超时的部分模式序列应用超时函数,对于每个部分模式序列,调用提供的 PatternTimeoutFunction,模式超时函数只能产生一个结果元素。 你可以在使用相同 OutputTag 进行 Select 操作 SingleOutputStreamOperator上获得SingleOutputStreamOperator生成的超时数据流。

非确定有限自动机

FlinkCEP 在运行时会将用户的逻辑转换为这样一个 NFA Graph(NFA对象) 所以有限状态机的工作过程,就是从开始状态,根据不同的输入,自动进行转换的过程。

上图中的状态机的功能,是检测二进制数是否含有偶数个0。从图上可以看出,输入只有1和0两种。 从S1状态开始,只有输入0才会转换到S2状态,同样S2状态下只有输入0才会转换到S1。所以,二进制输入完毕,如果满足最终状态,也就是最后停在S1状态,那么输入的二进制数就含有偶数个0。

CEP开发流程

FlinkCEP开发流程:

  • DataSource中数据转换为DataStream、Watermark、keyby
  • 定义Pattern,并将DataStream和Pattern组合转换为PatternStream
  • PatternStream经过select、process等算子转换为 DataStream
  • 再次转换为 DataStream 经过处理后,Sink到目标库

添加依赖

xml 复制代码
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-cep_2.12</artifactId>
    <version>${flink.version}</version>
</dependency>

案例1:恶意登录检测

找出5秒内,连续登录失败的账号 以下是数据:

shell 复制代码
new CepLoginBean(1L, "fail", 1597905234000L),
new CepLoginBean(1L, "success", 1597905235000L),
new CepLoginBean(2L, "fail", 1597905236000L),
new CepLoginBean(2L, "fail", 1597905237000L),
new CepLoginBean(2L, "fail", 1597905238000L),
new CepLoginBean(3L, "fail", 1597905239000L),
new CepLoginBean(3L, "success", 1597905240000L)

整体思路

  • 获取到数据
  • 在数据源上做Watermark
  • 在Watermark上根据ID分组keyBy
  • 做出模式Pattern
  • 在数据流上进行模式匹配
  • 提取匹配成功的数据

编写代码

java 复制代码
package icu.wzk;
/**
 * Flink CEP 登录失败检测示例
 * 
 * 功能说明:
 * 该示例模拟了用户登录事件流,利用 Flink CEP 检测"同一用户在 5 秒内连续两次登录失败"的情况。
 * 
 * 核心步骤:
 * 1. 构造模拟数据流
 * 2. 设置事件时间(EventTime)与水位线(Watermark)
 * 3. 定义 CEP 模式(Pattern)
 * 4. 应用模式并处理匹配结果
 */
public class FlinkCepLoginTest {

    public static void main(String[] args) throws Exception {

        // 1️⃣ 创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 设置时间语义为事件时间(EventTime)
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        // 并行度设置为 1,方便观察控制台输出
        env.setParallelism(1);

        // 2️⃣ 模拟输入数据流(用户登录事件)
        DataStreamSource<CepLoginBean> data = env.fromElements(
                new CepLoginBean(1L, "fail", 1597905234000L),
                new CepLoginBean(1L, "success", 1597905235000L),
                new CepLoginBean(2L, "fail", 1597905236000L),
                new CepLoginBean(2L, "fail", 1597905237000L),
                new CepLoginBean(2L, "fail", 1597905238000L),
                new CepLoginBean(3L, "fail", 1597905239000L),
                new CepLoginBean(3L, "success", 1597905240000L)
        );

        // 3️⃣ 为数据流分配时间戳与水位线
        SingleOutputStreamOperator<CepLoginBean> watermarks = data
                .assignTimestampsAndWatermarks(
                        new WatermarkStrategy<CepLoginBean>() {

                            @Override
                            public WatermarkGenerator<CepLoginBean> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context) {
                                return new WatermarkGenerator<CepLoginBean>() {

                                    // 当前已观察到的最大事件时间戳
                                    long maxTimestamp = Long.MAX_VALUE;

                                    // 最大允许乱序时间(即事件允许延迟 500ms 到达)
                                    long maxOutOfOrderness = 500L;

                                    @Override
                                    public void onEvent(CepLoginBean event, long eventTimestamp, WatermarkOutput output) {
                                        // 更新当前最大时间戳
                                        maxTimestamp = Math.max(maxTimestamp, event.getTimestamp());
                                    }

                                    @Override
                                    public void onPeriodicEmit(WatermarkOutput output) {
                                        // 周期性发送水位线:当前最大时间戳 - 乱序时间
                                        output.emitWatermark(new Watermark(maxTimestamp - maxOutOfOrderness));
                                    }
                                };
                            }
                        }.withTimestampAssigner((element, recordTimestamp) -> element.getTimestamp())
                );

        // 4️⃣ 按用户 ID 分组(KeyBy)
        KeyedStream<CepLoginBean, Long> keyed = watermarks
                .keyBy(new KeySelector<CepLoginBean, Long>() {
                    @Override
                    public Long getKey(CepLoginBean value) {
                        return value.getUserId();
                    }
                });

        // 5️⃣ 定义 CEP 模式:连续两次失败且间隔不超过 5 秒
        Pattern<CepLoginBean, CepLoginBean> pattern = Pattern
                .<CepLoginBean>begin("start")
                .where(new IterativeCondition<CepLoginBean>() {
                    @Override
                    public boolean filter(CepLoginBean cepLoginBean, Context<CepLoginBean> context) {
                        // 第一个事件:登录失败
                        return cepLoginBean.getOperation().equals("fail");
                    }
                })
                .next("next")
                .where(new IterativeCondition<CepLoginBean>() {
                    @Override
                    public boolean filter(CepLoginBean cepLoginBean, Context<CepLoginBean> context) {
                        // 第二个事件:同样是登录失败
                        return cepLoginBean.getOperation().equals("fail");
                    }
                })
                // 限定两次失败必须发生在 5 秒内
                .within(Time.seconds(5));

        // 6️⃣ 将模式应用到 keyed 流
        PatternStream<CepLoginBean> patternStream = CEP.pattern(keyed, pattern);

        // 7️⃣ 处理匹配到的事件(输出报警或进一步操作)
        SingleOutputStreamOperator<CepLoginBean> process = patternStream
                .process(new PatternProcessFunction<CepLoginBean, CepLoginBean>() {
                    @Override
                    public void processMatch(Map<String, List<CepLoginBean>> map, Context context, Collector<CepLoginBean> collector) {
                        System.out.println("map: " + map);
                        // 获取第一个"fail"事件并输出
                        List<CepLoginBean> start = map.get("start");
                        collector.collect(start.get(0));
                    }
                });

        // 8️⃣ 打印匹配结果
        process.print();

        // 9️⃣ 启动作业
        env.execute("FlinkCepLoginTest");
    }
}

/**
 * 自定义登录事件数据类
 * 包含用户ID、操作类型、时间戳等字段
 */
class CepLoginBean {

    private Long userId;      // 用户ID
    private String operation; // 操作类型(如 "fail", "success")
    private Long timestamp;   // 事件时间(毫秒)

    public CepLoginBean(Long userId, String operation, Long timestamp) {
        this.userId = userId;
        this.operation = operation;
        this.timestamp = timestamp;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getOperation() {
        return operation;
    }

    public void setOperation(String operation) {
        this.operation = operation;
    }

    public Long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(Long timestamp) {
        this.timestamp = timestamp;
    }

    @Override
    public String toString() {
        return "CepLoginBean{" +
                "userId=" + userId +
                ", operation='" + operation + '\'' +
                ", timestamp=" + timestamp +
                '}';
    }
}

运行结果

可以看到程序输出:

shell 复制代码
map: {start=[CepLoginBean{userId=2, operation='fail', timestamp=1597905236000}], next=[CepLoginBean{userId=2, operation='fail', timestamp=1597905237000}]}...

Process finished with exit code 0

运行截图如下所示:

相关推荐
LaughingZhu13 分钟前
Product Hunt 每日热榜 | 2026-01-31
大数据·人工智能·经验分享·搜索引擎·产品运营
babe小鑫17 分钟前
中专学历进入快消大厂终端销售岗位的可行性分析
大数据
samFuB19 分钟前
【工具变量】区县5A级旅游景区DID数据集(2000-2025年)
大数据
百夜﹍悠ゼ31 分钟前
数据治理DataHub安装部署
大数据·数据治理
Loo国昌1 小时前
【垂类模型数据工程】第四阶段:高性能 Embedding 实战:从双编码器架构到 InfoNCE 损失函数详解
人工智能·后端·深度学习·自然语言处理·架构·transformer·embedding
wdfk_prog1 小时前
解决 `git cherry-pick` 引入大量新文件的问题
大数据·git·elasticsearch
ONE_PUNCH_Ge2 小时前
Go 语言泛型
开发语言·后端·golang
洛阳纸贵2 小时前
JAVA高级工程师--Elasticsearch
大数据·elasticsearch·搜索引擎
良许Linux2 小时前
DSP的选型和应用
后端·stm32·单片机·程序员·嵌入式
TracyCoder1232 小时前
ElasticSearch内存管理与操作系统(二):深入解析 Circuit Breakers(熔断器)机制
大数据·elasticsearch·搜索引擎