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 小时活跃度),近实时是性价比最高的选择。
  • 如果模型需要秒级响应(比如实时反欺诈、竞价广告),滑动窗口是唯一出路。

下面这张图,是我自己总结的三种延迟的对比关系,你可以直观感受一下。

三种延迟模式对比 T+1 延迟 离线批处理 · 12~24h 近实时延迟 流处理 · 1~10min 滑动窗口 准实时 · 毫秒~秒级 延迟递增 → 复杂度递增 → 成本递增 选型建议 不敏感 → T+1 近期行为 → 近实时 选型建议 秒级响应 → 滑动窗口 成本敏感 → 优先 T+1 选型建议 混合使用 T+1 + 滑动窗口

3.5 延迟与时间窗口的联动关系

最后说一个容易被忽略的点:时间窗口的长度,决定了你该用哪种延迟模式。

举个例子,如果你要算「过去 30 天的累计消费」,这个窗口很长,数据量也大。用滑动窗口实时算,成本极高,而且没必要——因为 30 天的累计值变化很慢。这时候 T+1 或近实时就足够了。

反过来,如果你要算「过去 5 秒内的点击次数」,窗口很短,数据变化快。这时候必须用滑动窗口,否则特征就失去了意义。

经验法则:窗口长度 > 1 小时,优先考虑 T+1 或近实时;窗口长度 < 10 分钟,滑动窗口是唯一选择。中间地带(10 分钟 ~ 1 小时),根据业务容忍度灵活选择。