Flink CEP编程

本文介绍了Apache Flink CEP库的基本使用,包括依赖导入、开发步骤和模式API的详细讲解。通过实例展示了如何从传感器数据中检测特定事件序列,以及如何利用不同类型的模式(单例、循环、组合等)来构造复杂事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.FlinkCEP介绍

FlinkCEP(Complex event processing for Flink) 是在Flink实现的复杂事件处理库. 它可以让你在无界流中检测出特定的数据,有机会掌握数据中重要的那部分。

是一种基于动态环境中事件流的分析技术,事件在这里通常是有意义的状态变化,通过分析事件间的关系,利用过滤、关联、聚合等技术,根据事件间的时序关系和聚合关系制定检测规则,持续地从事件流中查询出符合要求的事件序列,最终分析得到更复杂的复合事件。

  1. 目标:从有序的简单事件流中发现一些高阶特征
  2. 输入:一个或多个由简单事件构成的事件流
  3. 处理:识别简单事件之间的内在联系,多个符合一定规则的简单事件构成复杂事件
  4. 输出:满足规则的复杂事件

二.CEP开发步骤

1.导入依赖

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-cep_${scala.binary.version}</artifactId>
    <version>${flink.version}</version>
</dependency>

2.基本使用

package com.atguigu.flink.java.chapter_9;

import com.atguigu.flink.java.chapter_5.WaterSensor;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.cep.CEP;
import org.apache.flink.cep.PatternSelectFunction;
import org.apache.flink.cep.PatternStream;
import org.apache.flink.cep.pattern.Pattern;
import org.apache.flink.cep.pattern.conditions.SimpleCondition;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

import java.time.Duration;
import java.util.List;
import java.util.Map;

public class Flink01_CEP_BasicUse {
    public static void main(String[] args) {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<WaterSensor> waterSensorStream = env
            .readTextFile("input/sensor.txt")
            .map(new MapFunction<String, WaterSensor>() {
                @Override
                public WaterSensor map(String value) throws Exception {
                    String[] split = value.split(",");
                    return new WaterSensor(split[0],
                                           Long.parseLong(split[1]) * 1000,
                                           Integer.parseInt(split[2]));
                }
            })
            .assignTimestampsAndWatermarks(WatermarkStrategy
                                               .<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(5))
                                               .withTimestampAssigner((element, recordTimestamp) -> element.getTs()));
        // 1. 模式
        Pattern<WaterSensor, WaterSensor> pattern = Pattern
            .<WaterSensor>begin("start")
            .where(new SimpleCondition<WaterSensor>() {
                @Override
                public boolean filter(WaterSensor value) throws Exception {
                    return "sensor_1".equals(value.getId());
                }
            });
        // 2. 在流上用模式
        PatternStream<WaterSensor> waterSensorPS = CEP.pattern(waterSensorStream, pattern);
        // 3. 取匹配到的
        waterSensorPS
            .select(new PatternSelectFunction<WaterSensor, String>() {
                @Override
                public String select(Map<String, List<WaterSensor>> pattern) throws Exception {
                    return pattern.toString();
                }
            })
            .print();

        try {
            env.execute();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

sensor.txt数据:

sensor_1,1,10
sensor_1,2,20
sensor_2,3,30
sensor_1,4,40
sensor_2,5,50
sensor_1,6,60

三.模式API

1.单个模式

单例模式

单例模式只接受一个事件. 默认情况模式都是单例模式. 

前面的例子就是一个单例模式

循环模式

循环模式可以接受多个事件.  单例模式配合上量词就是循环模式.(非常类似我们熟悉的正则表达式)

  • 固定次数

// 1. 模式
Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    });

// 1.1 使用量 出现两次
Pattern<WaterSensor, WaterSensor> patternWithQuantifier = pattern.times(2);

  • 范围内的次数

// 1.1 使用量 [2,4]   2,3次或4
Pattern<WaterSensor, WaterSensor> patternWithQuantifier = pattern.times(2, 4);

  • 一次或多次

// 表示大于等于1

Pattern<WaterSensor, WaterSensor> patternWithQuantifier = pattern.oneOrMore();

  • 多次及多次以上

// 2次或2以上
Pattern<WaterSensor, WaterSensor> patternWithQuantifier = pattern.timesOrMore(2);

条件

对每个模式你可以指定一个条件来决定一个进来的事件是否被接受进入这个模式,例如前面用到的where就是一种条件

  • 迭代条件

这是最普遍的条件类型。使用它可以指定一个基于前面已经被接受的事件的属性或者它们的一个子集的统计数据来决定是否接受时间序列的条件。

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new IterativeCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value, Context<WaterSensor> ctx) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    });

  • 简单条件

这种类型的条件扩展了前面提到的IterativeCondition类,它决定是否接受一个事件只取决于事件自身的属性。

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            System.out.println(value);
            return "sensor_1".equals(value.getId());
        }
    });

  • 组合条件

把多个条件结合起来使用. 这适用于任何条件,你可以通过依次调用where()来组合条件。 最终的结果是每个单一条件的结果的逻辑AND。

如果想使用OR来组合条件,你可以像下面这样使用or()方法。

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new IterativeCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value, Context<WaterSensor> ctx) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    })
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return value.getVc() > 30;
        }
    })
    .or(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return value.getTs() > 3000;
        }
    });

  • 停止条件

如果使用循环模式(oneOrMore, timesOrMore),在读无界流的时候,可以指定一个停止条件, 否则有可能会内存吃不消.

意思是满足了给定的条件的事件出现后,就不会再有事件被接受进入模式了。

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new IterativeCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value, Context<WaterSensor> ctx) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    })
    .timesOrMore(2)
    .until(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return value.getVc() >= 40;
        }
    });

2. 组合模式(模式序列)

把多个单个模式组合在一起就是组合模式.  组合模式由一个初始化模式(.begin(...))开头

严格连续

期望所有匹配的事件严格的一个接一个出现,中间没有任何不匹配的事件

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    })
    .next("end")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_2".equals(value.getId());
        }
    });

注意: 

notNext  如果不想后面直接连着一个特定事件

松散连续

忽略匹配的事件之间的不匹配的事件。

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    })
    .followedBy("end")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_2".equals(value.getId());
        }
    });

注意:

notFollowBy 如果不想一个特定事件发生在两个事件之间的任何地方。(notFollowBy不能位于事件的最后)

非确定的松散连续

更进一步的松散连续,允许忽略掉一些匹配事件的附加匹配    

当且仅当数据为a,c,b,b时,对于followedBy模式而言命中的为{a,b},对于followedByAny而言会有两次命中{a,b},{a,b}

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    })
    .followedByAny("end")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_2".equals(value.getId());
        }
    });

3 .模式知识补充

循环模式的连续性

前面的连续性也可以运用在单个循环模式中. 连续性会被运用在被接受进入模式的事件之间。

  • 严格连续

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    })
    .times(2)
    .consecutive();

  • 松散连续

默认是松散连续

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    })
    .times(2);

  • 非确定的松散连续

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    })
    .times(2)
    .allowCombinations();

循环模式的贪婪性

在组合模式情况下, 对次数的处理尽快能获取最多个的那个次数, 就是贪婪!当一个事件同时满足两个模式的时候起作用.

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    }).times(2, 3).greedy()
    .next("end")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return value.getVc() == 30;
        }
    });

数据:

sensor_1,1,10
sensor_1,2,20
sensor_1,3,30
sensor_2,4,30
sensor_1,4,40
sensor_2,5,50
sensor_2,6,60

结果:

{start=[WaterSensor(id=sensor_1, ts=1, vc=10), WaterSensor(id=sensor_1, ts=2, vc=20), WaterSensor(id=sensor_1, ts=3, vc=30)], end=[WaterSensor(id=sensor_2, ts=4, vc=30)]}

{start=[WaterSensor(id=sensor_1, ts=2, vc=20), WaterSensor(id=sensor_1, ts=3, vc=30)], end=[WaterSensor(id=sensor_2, ts=4, vc=30)]}

分析:

sensor_1,3,30  在匹配的的时候, 既能匹配第一个模式也可以匹配的第二个模式, 由于第一个模式使用量词则使用greedy的时候会优先匹配第一个模式, 因为要尽可能多的次数

注意:

  1. 一般贪婪比非贪婪结果要少!
  2. 模式组不能设置为greedy

模式可选性

可以使用pattern.optional()方法让所有的模式变成可选的,不管是否是循环模式

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    }).times(2).optional()  // 0次或2
    .next("end")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_2".equals(value.getId());
        }
    });

说明:

start模式可能会没有!

8.4.4 模式组

在前面的代码中次数只能用在某个模式上, 比如: .begin(...).where(...).next(...).where(...).times(2)  这里的次数只会用在next这个模式上, 而不会用在begin模式上.

如果需要用在多个模式上,可以使用模式组!

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .begin(Pattern
               .<WaterSensor>begin("start")
               .where(new SimpleCondition<WaterSensor>() {
                   @Override
                   public boolean filter(WaterSensor value) throws Exception {
                       return "sensor_1".equals(value.getId());
                   }
               })
               .next("next")
               .where(new SimpleCondition<WaterSensor>() {
                   @Override
                   public boolean filter(WaterSensor value) throws Exception {
                       return "sensor_2".equals(value.getId());
                   }
               }))
    .times(2);

结果: 

{begin=[WaterSensor(id=sensor_1, ts=2000, vc=20), WaterSensor(id=sensor_1, ts=4000, vc=40)], next=[WaterSensor(id=sensor_2, ts=3000, vc=30), WaterSensor(id=sensor_2, ts=5000, vc=50)]} 

8.4.5 超时数据

当一个模式上通过within加上窗口长度后,部分匹配的事件序列就可能因为超过窗口长度而被丢弃。

Pattern<WaterSensor, WaterSensor> pattern = Pattern
    .<WaterSensor>begin("start")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_1".equals(value.getId());
        }
    })
    .next("end")
    .where(new SimpleCondition<WaterSensor>() {
        @Override
        public boolean filter(WaterSensor value) throws Exception {
            return "sensor_2".equals(value.getId());
        }
    })
    .within(Time.seconds(2));

数据:

sensor_1,1,10
sensor_2,4,30
sensor_1,4,40
sensor_2,5,50

### Flink CEP与SQL结合使用的实例 Flink CEP(Complex Event Processing)是一种用于复杂事件处理的功能模块,能够帮助开发者定义复杂的模式并检测流数据中的匹配项。尽管Flink CEP本身主要基于API操作,但它可以通过扩展的方式与SQL集成,从而简化某些场景下的开发流程。 以下是关于如何将Flink CEP与SQL结合的一个示例代码: #### 示例代码:Flink CEP与SQL的结合 以下是一个简单的例子,展示如何利用Flink SQL来触发CEP模式匹配的结果[^1]。 ```sql -- 创建输入表,模拟网络流量日志 CREATE TABLE network_traffic ( eid STRING, ip STRING, event_time TIMESTAMP(3), WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND ) WITH ( 'connector' = 'kafka', 'topic' = 'network_logs', 'properties.bootstrap.servers' = 'localhost:9092', 'format' = 'json' ); -- 定义一个视图,表示连续两次登录失败的模式 WITH failed_login_pattern AS ( SELECT * FROM network_traffic MATCH_RECOGNIZE ( ORDER BY event_time MEASURES A.eid AS first_eid, B.ip AS second_ip AFTER MATCH SKIP PAST LAST ROW PATTERN (A B) DEFINE A AS A.event_type = 'login_failed', B AS B.event_type = 'login_failed' AND B.ip != A.ip ) ) -- 查询结果并将异常行为写入Kafka INSERT INTO alert_sink SELECT first_eid, second_ip, COUNT(*) as count_of_failures FROM failed_login_pattern GROUP BY first_eid, second_ip; ``` 此代码片段展示了如何通过`MATCH_RECOGNIZE`语法在Flink SQL中实现类似于CEP的操作逻辑。具体来说,它定义了一个模式,即两个连续的不同IP地址上的登录失败事件,并将其作为警报输出到目标存储系统中[^2]。 --- #### 关键点解析 1. **MATCH_RECOGNIZE** `MATCH_RECOGNIZE` 是一种强大的SQL功能,允许用户以声明式方式指定时间序列中的模式。这使得它可以替代部分传统的CEP API调用,特别是在简单模式的情况下更为直观和高效。 2. **Watermark机制** 在实时计算环境中,水印(Watermark)对于处理乱序事件至关重要。上述代码中设置了延迟时间为5秒,这意味着任何超过当前最大时间戳5秒的数据都将被忽略。 3. **Sink配置** 结果可以进一步发送至外部系统如Kafka或其他消息队列服务,以便后续分析或告警通知。 --- ### 技术优势与局限性 - **技术优势** - 使用SQL表达模式更加简洁明了,适合熟悉SQL语句而非编程接口的技术人员。 - 可无缝嵌入现有的ETL管道中,减少额外的学习成本。 - **局限性** - 对于高度定制化的复杂业务需求可能仍需依赖原生Java/Scala API完成更精细控制。 - 当前版本下可能存在性能瓶颈或者特定功能缺失的情况,在大规模生产环境部署之前应充分测试验证其稳定性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值