2. CEP模式API入门:Pattern API核心概念、个体模式与组合模式

交易风控规则的本质是“在连续事件流中识别特定序列”。Flink CEP 的 Pattern API 正是用来描述这种序列的声明式编程接口。本章将系统讲解 Pattern API 的三大支柱:核心概念、个体模式(begin/where)与组合模式(next/followedBy),并通过风控场景的代码示例帮助你快速上手。

2.1 Pattern API 核心概念

Pattern API 将规则定义抽象为“模式图”(Pattern Graph),每个模式节点代表一个事件条件,节点间的边代表事件间的时序关系。理解以下三个核心概念是掌握 CEP 的基础:

概念 说明 风控类比
模式(Pattern) 描述一个事件应满足的条件,可包含量词(次数、贪婪等) “单笔交易金额 > 5000”是一个模式
模式序列(Pattern Sequence) 多个模式按时间或逻辑顺序组合成的链 “先登录失败3次,再转账成功”是一个序列
模式匹配(Pattern Match) 事件流中满足整个模式序列的一组事件 检测到一次完整的“撞库+盗转”攻击
关键认知:Pattern API 不直接处理事件流,而是先构建一个“规则模板”,再由 CEP 引擎将模板应用到 DataStream 上执行匹配。规则与数据分离,是风控规则可配置化的基础。
Pattern API 核心概念关系 事件流 DataStream<Transaction> 模式定义 Pattern API 构建 CEP 引擎 CEP.pattern() 匹配结果 PatternStream 核心流程 1. 定义模式序列 2. 应用到事件流 3. 输出匹配结果

2.2 个体模式:begin 与 where

个体模式(Individual Pattern)是模式序列的起点和基本单元。通过 begin() 定义第一个模式,where() 设置过滤条件。

2.2.1 begin() — 模式序列的起点

每个模式序列必须以 begin() 开头,参数为模式名称,用于后续结果提取。

// 定义第一个模式:检测单笔交易金额超过 10000
Pattern<Transaction, Transaction> start = Pattern
    .<Transaction>begin("large_transaction")
    .where(new SimpleCondition<Transaction>() {
        @Override
        public boolean filter(Transaction event) {
            return event.getAmount() > 10000;
        }
    });

2.2.2 where() — 条件过滤

where() 支持多种条件定义方式,适用于不同风控场景:

条件类型 API 方法 风控示例
简单条件 where(SimpleCondition) 交易金额 > 阈值
组合条件 where(Condition).or(Condition) 金额 > 10000 或 跨行交易
迭代条件 where(IterativeCondition) 当前事件与之前匹配事件比较
子类型条件 subtype(Class) 只处理特定类型的事件子类
风控最佳实践:在 where() 中优先使用 IterativeCondition,因为它可以访问之前已匹配的事件上下文,适合实现“同一设备短时间内多次尝试”等跨事件规则。
// 迭代条件示例:检测同一设备在 5 分钟内第 2 次交易
Pattern<Transaction, Transaction> deviceRepeat = Pattern
    .<Transaction>begin("first_tx")
    .where(new IterativeCondition<Transaction>() {
        @Override
        public boolean filter(Transaction current, Context<Transaction> ctx) {
            // 获取之前匹配的事件
            for (Transaction prev : ctx.getEventsForPattern("first_tx")) {
                if (prev.getDeviceId().equals(current.getDeviceId())) {
                    return true; // 同一设备重复交易
                }
            }
            return false;
        }
    });

2.3 组合模式:next 与 followedBy

个体模式只能描述单一事件条件,风控规则通常需要多个事件的时序组合。Pattern API 提供两种核心组合方式:严格连续(next) 和 宽松连续(followedBy)。

组合操作 语义 中间允许无关事件 风控典型场景
next() 严格连续:模式 B 必须紧跟在模式 A 之后 不允许 “登录成功 → 立即转账”的紧邻操作
followedBy() 宽松连续:模式 B 在模式 A 之后出现即可 允许 “登录失败 → 后续任意时刻转账成功”
followedByAny() 非确定性宽松连续:允许跳过事件,且可重复匹配 允许 “多次尝试登录 → 任意一次成功”
常见误区:next() 要求事件在时间上严格相邻,如果中间插入了其他事件(即使不满足条件),匹配也会失败。在风控场景中,followedBy() 更常用,因为攻击行为之间往往夹杂正常事件。
next() 与 followedBy() 对比 事件流: A X B Y C 时间 → next(A, B): 要求 A 后面紧跟着 B,中间不能有 X → 匹配失败(X 插在中间) 不匹配 followedBy(A, B): 允许 A 后面出现任意事件后再出现 B → 匹配成功(A → X → B) 匹配

2.4 实战:构建一个简单的风控模式序列

下面结合个体模式和组合模式,构建一个“异常登录后大额转账”的检测规则:

// 步骤1:定义事件类型
public class LoginEvent {
    public String userId;
    public boolean success;
    public String ip;
    // getter/setter 省略
}

public class TransferEvent {
    public String userId;
    public double amount;
    public String targetAccount;
    // getter/setter 省略
}

// 步骤2:构建模式序列
Pattern<Object, ?> fraudPattern = Pattern
    .<Object>begin("failed_login")
    .subtype(LoginEvent.class)
    .where(new SimpleCondition<LoginEvent>() {
        @Override
        public boolean filter(LoginEvent login) {
            return !login.isSuccess();  // 登录失败
        }
    })
    .followedBy("large_transfer")
    .subtype(TransferEvent.class)
    .where(new SimpleCondition<TransferEvent>() {
        @Override
        public boolean filter(TransferEvent transfer) {
            return transfer.getAmount() > 50000;  // 大额转账
        }
    })
    .within(Time.minutes(10));  // 10 分钟内发生

// 步骤3:应用到 DataStream
DataStream<Object> inputStream = ...;  // 合并后的登录+交易流
PatternStream<Object> patternStream = CEP.pattern(inputStream, fraudPattern);

// 步骤4:输出告警
patternStream.select(new PatternSelectFunction<Object, Alert>() {
    @Override
    public Alert select(Map<String, List<Object>> pattern) throws Exception {
        LoginEvent failedLogin = (LoginEvent) pattern.get("failed_login").get(0);
        TransferEvent largeTransfer = (TransferEvent) pattern.get("large_transfer").get(0);
        return new Alert(
            "高风险操作:登录失败后大额转账",
            failedLogin.getUserId(),
            largeTransfer.getAmount()
        );
    }
}).print();
代码要点:
  • 使用 subtype() 区分不同类型的事件,避免类型转换异常
  • followedBy() 比 next() 更适合风控场景,因为攻击行为之间可能夹杂正常事件
  • within() 设置时间窗口,防止历史事件无限期参与匹配

2.5 本章小结

  • Pattern API 核心概念:模式、模式序列、模式匹配,三者构成 CEP 规则的基础框架
  • 个体模式:begin() 定义起点,where() 设置条件,IterativeCondition 支持跨事件上下文
  • 组合模式:next() 严格连续,followedBy() 宽松连续,followedByAny() 非确定性宽松连续
  • 实战要点:合理选择组合方式、设置时间窗口、使用 subtype() 处理多事件类型