3. 时间窗口与延迟:滑动窗口、T+1延迟、近实时延迟的定义
聊特征新鲜度,绕不开一个核心问题:你的特征到底有多「旧」?
说白了,就是从原始数据发生,到特征真正被模型用上,中间隔了多久。这个时间差,就是我们常说的延迟。而延迟的长短,直接决定了你该用哪种时间窗口来计算特征。
我个人习惯把延迟分成三个档次:T+1(天级)、近实时(分钟级)、滑动窗口(准实时)。这三者没有的好坏,关键看你的业务场景扛得住多久的等待。
核心观点:延迟越低,系统复杂度越高,成本也越高。别为了追求「实时」而实时,先想清楚你的模型是否真的需要秒级新鲜度。
3.1 T+1 延迟:离线批处理的「老黄牛」
T+1 是最经典的模式。我刚开始做特征工程时,大部分特征都是这么算的。它的逻辑很简单:今天用昨天的数据。
举个例子,用户昨天的总消费金额、过去7天的登录次数,这些特征通常在凌晨跑一个离线 Spark 任务,算好后写入 HBase 或 MySQL,白天模型直接读取。
适用场景:用户画像、长期偏好、统计类特征(如近30天平均客单价)。这些特征变化慢,延迟一天对模型效果影响微乎其微。
但 T+1 有个硬伤——无法应对突发事件。我记得有一次做风控模型,用户刚被盗刷,T+1 的特征还在用昨天的正常行为数据,结果模型完全没反应。这就是典型的「新鲜度不够」导致的模型失效。
| 维度 | T+1 延迟 |
|---|---|
| 计算方式 | 离线批处理(Spark/Hive) |
| 典型延迟 | 12h ~ 24h |
| 存储介质 | HBase / MySQL / Redis(离线写入) |
| 优点 | 实现简单、成本低、支持复杂计算 |
| 缺点 | 新鲜度差、无法响应实时事件 |
3.2 近实时延迟:流计算的「轻骑兵」
近实时延迟,通常指分钟级(1~10分钟)。它不像 T+1 那样等一天,也不像滑动窗口那样毫秒级响应,属于一个「折中方案」。
它的核心思路是:用流处理引擎(如 Flink、Spark Streaming)持续消费消息队列(Kafka)中的数据,每隔几分钟做一次微批计算,然后更新特征值。
我曾在推荐系统里用过这种方式。用户点击了一个商品,我们希望模型在几分钟内就能感知到这个行为,从而调整推荐结果。如果等 T+1,用户早就流失了。
注意:近实时不等于「真实时」。它仍然存在一个微批窗口,窗口内的数据是攒批处理的。如果你的业务要求秒级响应,近实时可能不够用。
近实时的典型实现方式有两种:
- Flink + Kafka: 设置 ProcessingTime 或 EventTime,每 5 分钟触发一次窗口计算。
- Spark Streaming + Kafka: 微批模式,批次间隔设为 1~5 分钟。
// Flink 近实时窗口示例(伪代码)
DataStream<Event> stream = env.addSource(kafkaSource);
stream
.keyBy(event -> event.getUserId())
.window(TumblingProcessingTimeWindows.of(Time.minutes(5)))
.aggregate(new CountAggregate())
.addSink(featureStoreSink);
3.3 滑动窗口:真正的「准实时」
滑动窗口,是我个人认为最有意思的一种模式。它不像 T+1 那样固定时间点计算,也不像近实时那样攒批处理。它是在事件发生的瞬间,基于过去一段时间窗口内的数据,实时计算出特征值。
举个例子:用户在过去 5 分钟内的点击次数。这个特征每时每刻都在变化。用户每点一下,窗口就滑动一次,旧数据被剔除,新数据被加入。
滑动窗口的核心在于「窗口长度」和「滑动步长」。步长越小,新鲜度越高,但计算压力也越大。
| 参数 | 说明 | 示例 |
|---|---|---|
| 窗口长度 | 统计的时间范围 | 5分钟、1小时 |
| 滑动步长 | 每次更新的时间间隔 | 1秒、10秒 |
| 延迟 | 从事件发生到特征可用 | 毫秒级 ~ 秒级 |
避坑指南:我曾经在一个广告点击率预估项目里,把滑动窗口的步长设成了 1 秒。结果 Flink 任务直接 OOM。后来改成 10 秒步长,效果几乎没变,但资源消耗降了 80%。别盲目追求细粒度。
滑动窗口的实现通常依赖 Flink 的 SlidingEventTimeWindows 或 Redis 的 Sorted Set + TTL 机制。后者在特征存储层用得比较多,适合轻量级场景。
// Flink 滑动窗口示例
stream
.keyBy(event -> event.getUserId())
.window(SlidingEventTimeWindows.of(Time.minutes(5), Time.seconds(10)))
.aggregate(new ClickCountAggregate())
.addSink(featureStoreSink);
3.4 三种延迟的对比与选型
你可能会问:到底该用哪种?我的经验是,先看业务容忍度。
- 如果模型对新鲜度不敏感(比如用户性别、年龄),T+1 完全够用,别折腾。
- 如果模型需要感知近期行为(比如近 1 小时活跃度),近实时是性价比最高的选择。
- 如果模型需要秒级响应(比如实时反欺诈、竞价广告),滑动窗口是唯一出路。
下面这张图,是我自己总结的三种延迟的对比关系,你可以直观感受一下。
3.5 延迟与时间窗口的联动关系
最后说一个容易被忽略的点:时间窗口的长度,决定了你该用哪种延迟模式。
举个例子,如果你要算「过去 30 天的累计消费」,这个窗口很长,数据量也大。用滑动窗口实时算,成本极高,而且没必要——因为 30 天的累计值变化很慢。这时候 T+1 或近实时就足够了。
反过来,如果你要算「过去 5 秒内的点击次数」,窗口很短,数据变化快。这时候必须用滑动窗口,否则特征就失去了意义。
经验法则:窗口长度 > 1 小时,优先考虑 T+1 或近实时;窗口长度 < 10 分钟,滑动窗口是唯一选择。中间地带(10 分钟 ~ 1 小时),根据业务容忍度灵活选择。