大数据-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

运行截图如下所示:

相关推荐
uzong3 小时前
后端线上发布计划模板
后端
uzong4 小时前
软件工程师应该关注的几种 UML 图
后端
上进小菜猪5 小时前
基于 YOLOv8 的 100 类中药材智能识别实战 [目标检测完整源码]
后端
AI智能探索者6 小时前
揭秘大数据领域特征工程的核心要点
大数据·ai
码事漫谈7 小时前
AI 技能工程入门:从独立能力到协作生态
后端
码事漫谈7 小时前
构建高并发AI服务网关:C++与gRPC的工程实践
后端
做cv的小昊7 小时前
【TJU】信息检索与分析课程笔记和练习(8)(9)发现系统和全文获取、专利与知识产权基本知识
大数据·笔记·学习·全文检索·信息检索
AC赳赳老秦7 小时前
DeepSeek 私有化部署避坑指南:敏感数据本地化处理与合规性检测详解
大数据·开发语言·数据库·人工智能·自动化·php·deepseek
颜酱8 小时前
前端必备动态规划的10道经典题目
前端·后端·算法
C7211BA9 小时前
通义灵码和Qoder的差异
大数据·人工智能