4. 数据采集与预处理:实时数据流接入、数据清洗、特征工程基础

好,咱们进入实战环节的第一个硬骨头——数据。做风控的人都知道一句话:「数据决定上限,模型只是逼近这个上限」。我做了这么多年风控架构,见过太多团队在模型上死磕,结果回头一看,数据流断了、字段是脏的、特征算错了……全白搭。

这一章,咱们就聊聊实时风控场景下,数据是怎么进来的、怎么洗干净、怎么变成特征的。说白了,就是给后面的阈值模型喂「好饭」。

4.1 实时数据流接入:别让数据迟到

实时风控,核心就一个字——。用户点一下「支付」,你必须在几百毫秒内给出决策。数据晚到一秒,可能钱已经没了。

我个人习惯用 Kafka 作为消息中间件。为什么?吞吐量高、延迟低、还能扛住突发流量。双十一那种峰值,Kafka 基本是标配。

核心架构思路: 业务系统 → 埋点SDK → Kafka集群 → 实时计算引擎(Flink/Spark Streaming) → 特征存储 → 决策引擎

这里有个坑,我踩过。数据接入时,一定要做「流量整形」。你想想看,如果一瞬间涌进来 10 万条消息,下游的 Flink 任务直接被打满,GC 频繁,延迟飙升。怎么办?

  • 设置合理的分区数:Kafka 分区数 = 下游消费者并行度 × 2~3 倍,留点余量。
  • 消息体不要太大:我见过有人把整个 HTTP 请求体塞进 Kafka,一个消息 1MB,直接拉爆网络。建议控制在 10KB 以内,只传关键字段。
  • 做好序列化:用 Avro 或 Protobuf,别用 JSON 明文。JSON 解析慢,而且 schema 变更容易出问题。
避坑指南: 我曾经遇到过一个线上事故——Kafka 消息积压了 30 分钟,原因是某个字段突然变成了 null,下游解析直接抛异常,消费线程卡死。从那以后,我强制要求所有消息必须做「空值校验」和「类型校验」,不合格的直接进死信队列。

4.2 数据清洗:脏数据比没数据更可怕

数据进来了,但你能直接用吗?不能。实时数据流里,脏数据比比皆是。我总结了几类最常见的「毒瘤」:

脏数据类型 典型表现 处理方式
缺失值 用户ID为空、金额为0 丢弃或填充默认值(如 -1)
异常值 交易金额 999999999 设定上下限,超过则截断或标记
重复数据 同一笔交易被发送两次 基于唯一ID做去重(幂等性)
时间乱序 事件时间比当前时间晚 1 小时 设置 watermark,丢弃超时数据
格式错误 手机号多了字母、IP 地址格式不对 正则校验,不合规的丢弃

嗯,这里要注意。清洗不是「一刀切」。比如缺失值,如果用户ID为空,这笔交易基本没法做风控,直接丢弃。但如果只是「用户年龄」为空,你可以用 -1 填充,或者用均值/中位数替代。具体怎么选?看业务容忍度。

我一般会在 Flink 里写一个 通用的清洗算子,把清洗逻辑做成配置化。这样业务方改规则,不用重启任务,热加载就行。

// 伪代码:Flink 清洗算子
DataStream<RawEvent> cleanedStream = rawStream
    .filter(event -> event.getUserId() != null)  // 丢弃无效用户
    .filter(event -> event.getAmount() > 0)      // 丢弃零金额
    .map(new CleanFunction());                   // 自定义清洗逻辑

// CleanFunction 内部
public RawEvent map(RawEvent event) {
    // 金额截断:超过 100 万视为异常
    if (event.getAmount() > 1_000_000) {
        event.setAmount(1_000_000);
        event.setFlag("amount_truncated");
    }
    // 时间校正:如果事件时间比当前时间早 5 分钟以上,丢弃
    if (event.getEventTime() < System.currentTimeMillis() - 300_000) {
        return null;  // 会被 filter 掉
    }
    return event;
}
警告: 千万别在清洗阶段做太重的计算。比如「用机器学习模型预测缺失值」——那是离线干的活。实时清洗,只做规则明确的、计算量小的操作。否则延迟会爆炸。

4.3 特征工程基础:从原始数据到「风控语言」

数据洗干净了,接下来就是特征工程。说白了,就是把原始数据翻译成模型能听懂的语言。

实时风控的特征,跟离线不一样。离线你可以算 1000 个特征,跑个 XGBoost 慢慢调。实时不行,你只有几十毫秒。所以特征要少而精,计算要

我一般把特征分成三类:

4.3.1 基础特征

直接从原始字段提取,几乎不需要计算。比如:

  • 交易金额(数值型)
  • 交易时间(转为小时、星期几)
  • 用户注册天数(当前时间 - 注册时间)
  • 设备类型(枚举型,做 one-hot 或 embedding)

4.3.2 统计特征

这是实时风控的重头戏。比如:

  • 过去 1 小时内,该用户的交易次数
  • 过去 24 小时内,该 IP 地址的失败次数
  • 过去 5 分钟内,该设备的平均金额

这些特征怎么做?用 Flink 的 滑动窗口 或者 状态编程。我习惯用 KeyedProcessFunction,按用户ID分组,维护一个状态列表,记录最近 N 笔交易的时间戳和金额。窗口滑动时,实时更新统计值。

// 伪代码:实时统计过去1小时交易次数
DataStream<FeatureEvent> featureStream = cleanedStream
    .keyBy(event -> event.getUserId())
    .process(new KeyedProcessFunction<String, RawEvent, FeatureEvent>() {
        private ValueState<List<Long>> timestampsState;

        @Override
        public void processElement(RawEvent event, Context ctx, Collector<FeatureEvent> out) {
            List<Long> timestamps = timestampsState.value();
            if (timestamps == null) {
                timestamps = new ArrayList<>();
            }
            long now = event.getEventTime();
            // 移除超过1小时的数据
            timestamps.removeIf(t -> t < now - 3600_000);
            // 加入当前时间戳
            timestamps.add(now);
            timestampsState.update(timestamps);
            // 输出特征:过去1小时交易次数
            out.collect(new FeatureEvent(event.getUserId(), "txn_count_1h", timestamps.size()));
        }
    });
个人经验: 统计特征的窗口大小怎么定?我一般遵循「业务直觉 + 数据分布」原则。比如盗刷通常发生在短时间内(几分钟到几小时),所以 1 小时、24 小时窗口最常用。太长的窗口(比如 30 天)计算量大,而且对实时决策意义不大。

4.3.3 衍生特征

这类特征需要一点「脑洞」。比如:

  • 金额与历史均值的偏差:当前金额 / 过去 7 天平均金额。如果比值 > 5,说明异常。
  • 设备聚集度:同一 IP 下关联了多少个不同设备。设备越多,越可能是团伙作案。
  • 行为序列特征:用户最近 5 次操作是否都是「登录-查询-支付」这种模式?如果突然变成「登录-改密-支付」,风险就高了。

衍生特征的计算,我建议放在 Flink 的 ProcessFunction 里做,因为需要维护状态和上下文。但注意,状态不要太大。比如「行为序列特征」,我最多保留最近 20 条记录,超过就丢弃旧的。

4.4 知识体系总览

说了这么多,咱们用一张图把整个流程串起来。这张图是我自己画的,涵盖了从数据接入到特征产出的核心链路。

实时风控数据采集与预处理流程 数据接入层 Kafka / 消息队列 数据清洗层 Flink 清洗算子 特征工程层 Flink ProcessFunction 数据清洗子流程 缺失值处理 异常值截断 重复数据去重 时间乱序校正 特征工程子流程 基础特征 金额、时间、设备 统计特征 窗口计数、均值 衍生特征 偏差、聚集度、序列 特征向量 → 决策引擎 注:所有计算均在毫秒级完成,状态存储使用 RocksDB 或 Redis 关键指标:延迟 < 50ms | 吞吐量 > 10万 TPS | 特征维度 50~200

这张图里,我把整个流程分成了三层:接入层、清洗层、特征层。每一层都有明确的职责和边界。你想想看,如果这三层混在一起写,后期维护就是噩梦。我见过一个项目,所有逻辑写在一个 Flink 算子里面,3000 行代码,改一个 bug 要半天。所以,分层设计是架构师的底线。

核心总结: 数据采集与预处理,是实时风控的「地基」。地基不稳,楼盖得再高也得塌。我个人建议,在这个阶段多花 30% 的时间做设计和测试,后面调阈值、上线迭代会省 80% 的麻烦。

公众号:蓝海资料掘金营,微信deep3321