3.1 什么是复杂事件处理(CEP)

复杂事件处理(Complex Event Processing, CEP)是一种在连续数据流中检测符合特定规则的事件组合的技术。其核心思想是:从多个简单事件中识别出有意义的复杂事件模式。

在实时风控场景中,单次登录失败可能只是用户输错密码,但“5分钟内连续登录失败3次+异地IP+更换设备”这一事件组合,就极大概率是暴力破解攻击。CEP正是用来描述和检测这类组合规则的计算范式。

CEP 核心定义:CEP 通过定义事件之间的时间顺序、逻辑关系和量词约束,从无界流中匹配出符合预设模式的事件序列,并触发后续动作(告警、阻断、统计)。

3.2 CEP 与普通流处理的区别

普通流处理(如简单的 map/filter/reduce)与 CEP 在抽象层次、关注点和表达能力上有本质差异。下表从多个维度进行对比:

对比维度 普通流处理 CEP 复杂事件处理
处理单元 单条事件独立处理 多条事件的组合/序列
状态跨度 无状态或简单状态(如 sum/count) 跨多条事件的有状态匹配
时间维度 通常只关注当前事件时间 关注事件间的时间间隔、顺序、超时
逻辑关系 AND/OR 等布尔逻辑 顺序(next)、跟随(followedBy)、循环(oneOrMore)、否定(notNext)
输出结果 转换后的单条事件 匹配到的完整事件序列(复杂事件)
典型算子 map, filter, reduce, window Pattern API, select, flatSelect
适用场景 ETL、简单统计、实时数仓 风控规则、设备告警、交易反欺诈
一句话区分:普通流处理回答“这条事件是什么?”,CEP 回答“这些事件组合在一起意味着什么?”。

3.3 Flink CEP 核心组件

Flink 提供了完整的 CEP 库,其核心由三个组件构成:Pattern(模式定义)、PatternStream(模式流)、Select/FlatSelect(结果提取)。三者形成一条清晰的“定义→匹配→输出”流水线。

3.3.1 Pattern:模式定义

Pattern 是 CEP 的“规则描述语言”,通过链式 API 定义事件序列的约束条件。每个 Pattern 节点包含:

  • 事件类型:通过 .where() 指定过滤条件
  • 量词:.oneOrMore()、.times(n)、.optional()
  • 连续性策略:.next()(严格连续)、.followedBy()(宽松连续)、.notNext()(否定)
  • 时间约束:.within(Time.minutes(5)) 限定匹配窗口
// 示例:定义"5分钟内连续登录失败3次"的模式
Pattern<LoginEvent, LoginEvent> failPattern = Pattern
    .<LoginEvent>begin("first")
    .where(new SimpleCondition<LoginEvent>() {
        @Override
        public boolean filter(LoginEvent event) {
            return event.getStatus().equals("FAIL");
        }
    })
    .times(3)
    .consecutive()
    .within(Time.minutes(5));

3.3.2 PatternStream:模式流

将定义好的 Pattern 应用到数据流上,即得到 PatternStream。它是 CEP 匹配引擎的运行时视图,内部维护了 NFA(非确定性有限自动机)状态机,负责在事件到达时进行状态转移和匹配判定。

// 将 Pattern 应用到 DataStream 上
DataStream<LoginEvent> inputStream = ...;
PatternStream<LoginEvent> patternStream = CEP.pattern(
    inputStream,
    failPattern
);

PatternStream 本身不包含事件数据,它只是一个“已绑定模式的数据流句柄”,后续通过 select/flatSelect 从中提取匹配结果。

3.3.3 Select / FlatSelect:结果提取

匹配完成后,需要从 PatternStream 中提取出匹配的事件序列。Flink 提供两种方式:

方法 返回类型 适用场景
select() 单条输出(1:1) 每个匹配序列输出一条结果,如告警消息
flatSelect() 多条输出(1:N) 一个匹配序列拆成多条输出,如逐事件打标
// 使用 select 提取匹配结果
patternStream.select(new PatternSelectFunction<LoginEvent, Alert>() {
    @Override
    public Alert select(Map<String, List<LoginEvent>> pattern) throws Exception {
        List<LoginEvent> first = pattern.get("first");
        // 构建告警对象
        return new Alert(first.get(0).getUserId(), "暴力破解", System.currentTimeMillis());
    }
});
注意:select 函数的输入参数 Map<String, List<LoginEvent>> 中,key 是 Pattern 中定义的事件名称(如 "first"),value 是该名称匹配到的所有事件列表(受量词影响)。如果使用 .times(3),则 list 中会有 3 个事件。

3.4 核心组件协作流程

下图展示了 Flink CEP 三个核心组件在运行时的协作关系:

Flink CEP 核心组件协作流程 Flink CEP 核心组件协作流程 输入 DataStream (无界事件流) Pattern .begin("first") .where(...).times(3) .within(5min) CEP.pattern() NFA 状态机 Pattern Stream Select / FlatSelect 提取匹配结果 输出 DataStream 关键说明 • Pattern 是规则模板 • PatternStream 是运行时 • Select 提取最终结果

3.5 三组件关系总结

下表从职责、输入输出、生命周期三个维度总结三个核心组件:

组件 职责 输入 输出 生命周期
Pattern 定义匹配规则(事件类型、量词、时间窗口) 无(API 构建) Pattern 对象 作业启动前定义,静态不变
PatternStream 将 Pattern 绑定到 DataStream,驱动 NFA 匹配 DataStream + Pattern PatternStream 句柄 作业运行时持续存在
Select/FlatSelect 从匹配结果中提取所需字段,生成输出事件 PatternStream DataStream(结果流) 每次匹配触发一次回调
实战建议:在风控稳定性巡检场景中,建议将 Pattern 定义封装为独立的规则配置类,通过参数化方式动态调整时间窗口和阈值,避免硬编码。PatternStream 的 NFA 状态大小与匹配窗口内的事件量成正比,需关注内存水位。