3. 特征存储与实时特征计算:离线特征仓库与实时特征接入聚合
在信用评分模型的在线推理链路中,特征数据是模型决策的“燃料”。离线特征仓库(HBase/Redis)与实时特征计算(Flink/Spark Streaming)共同构成了特征体系的两大支柱。本章将深入剖析这两种特征存储与计算模式的架构设计、接入方式及聚合策略。
3.1 离线特征仓库:HBase 与 Redis 的存储模型
离线特征仓库主要存储经过批处理(如 Hive/Spark 离线任务)计算好的历史特征、用户画像标签、长期统计特征等。这些特征具有数据量大、更新频率低(通常 T+1)、查询 QPS 高的特点。
| 对比维度 | HBase | Redis |
|---|---|---|
| 数据模型 | 宽表、稀疏列族,适合存储多版本、多维度特征 | Key-Value 结构,适合存储简单、高频访问的特征 |
| 查询延迟 | 毫秒级(通常 5-20ms) | 微秒级(通常 <1ms) |
| 存储容量 | PB 级,支持海量历史数据 | GB ~ TB 级,受内存限制 |
| 典型特征 | 近 90 天逾期天数、近 6 个月平均额度使用率 | 用户风险等级、黑名单标记、当日查询次数 |
| 更新方式 | 离线批量写入(BulkLoad/Put) | 离线批量写入 + 实时更新(Set/Expire) |
架构选型建议: 对于需要存储大量历史序列特征(如过去 12 个月的还款行为)的场景,优先选择 HBase;对于需要极低延迟、高并发读取的实时决策特征(如当前设备指纹、IP 风险分),优先选择 Redis。实践中常采用“HBase 存全量 + Redis 存热数据”的混合架构。
3.2 实时特征计算:Flink 与 Spark Streaming 的接入
实时特征计算负责从 Kafka/Pulsar 等消息队列中消费实时事件流(如交易流水、登录行为、设备变更),在窗口内完成聚合计算,并产出实时特征。Flink 和 Spark Streaming 是当前主流的实时计算引擎。
3.2.1 Flink 实时特征计算架构
Flink 采用“事件驱动 + 精确一次语义”的架构,非常适合需要严格时间语义和状态管理的信用评分场景。
// Flink 实时特征计算核心逻辑示例:计算用户过去 5 分钟内的交易总金额
DataStream<TransactionEvent> transactionStream = env
.addSource(new FlinkKafkaConsumer<>("transaction_topic", ...));
DataStream<UserFeature> featureStream = transactionStream
.keyBy(TransactionEvent::getUserId)
.window(TumblingProcessingTimeWindows.of(Time.minutes(5)))
.aggregate(new AggregateFunction<TransactionEvent, Accumulator, UserFeature>() {
@Override
public Accumulator createAccumulator() {
return new Accumulator();
}
@Override
public Accumulator add(TransactionEvent event, Accumulator acc) {
acc.setTotalAmount(acc.getTotalAmount() + event.getAmount());
acc.setTransactionCount(acc.getTransactionCount() + 1);
return acc;
}
@Override
public UserFeature getResult(Accumulator acc) {
return new UserFeature(acc.getTotalAmount(), acc.getTransactionCount());
}
@Override
public Accumulator merge(Accumulator a, Accumulator b) {
a.setTotalAmount(a.getTotalAmount() + b.getTotalAmount());
a.setTransactionCount(a.getTransactionCount() + b.getTransactionCount());
return a;
}
});
3.2.2 Spark Streaming 微批处理架构
Spark Streaming 以微批(Micro-Batch)方式处理实时数据,适合对延迟要求稍宽松(秒级)的场景,且能与 Spark 离线生态无缝集成。
// Spark Streaming 实时特征计算示例:计算用户过去 1 小时内的登录失败次数
val loginStream = KafkaUtils.createDirectStream[String, String](
ssc, PreferConsistent, Subscribe[String, String](topics, kafkaParams))
val featureDStream = loginStream
.map(record => (record.key(), record.value()))
.map { case (userId, _) => (userId, 1L) }
.reduceByKeyAndWindow(
(a: Long, b: Long) => a + b,
(a: Long, b: Long) => a - b,
Seconds(3600), Seconds(60)) // 窗口 1 小时,滑动间隔 60 秒
.map { case (userId, failCount) => UserFeature(userId, "login_fail_count_1h", failCount) }
实时特征计算避坑: 1) 注意事件时间与处理时间的对齐,避免数据乱序导致特征计算偏差;2) 合理设置状态 TTL(Time-To-Live),防止状态无限膨胀;3) 实时特征产出后需与离线特征进行一致性校验,避免因数据延迟导致特征值突变。
3.3 特征聚合策略:从原始事件到模型特征
无论是离线仓库还是实时计算,最终都需要将原始数据聚合为模型可用的特征。聚合策略决定了特征的信息密度和计算效率。
3.4 特征接入与聚合的工程实践
3.4.1 特征接入流程
- 特征定义与注册: 在 Feature Store 中定义特征名称、类型、来源、聚合窗口等元信息。
- 离线特征导入: 通过 HBase BulkLoad 或 Redis Pipeline 批量写入离线特征。
- 实时特征接入: Flink/Spark Streaming 消费 Kafka 事件,按窗口聚合后写入 Redis。
- 特征拼接: 在线推理时,根据用户 ID 同时查询 HBase(离线特征)和 Redis(实时特征),拼接成完整的特征向量。
- 特征校验: 对拼接后的特征进行空值检查、范围校验、逻辑一致性校验。
3.4.2 聚合性能优化
| 优化方向 | 具体措施 | 预期效果 |
|---|---|---|
| 预聚合 | 在离线层按天/小时预聚合中间结果,减少实时计算压力 | 实时计算吞吐量提升 3-5 倍 |
| 状态后端 | Flink 使用 RocksDB 状态后端,管理大状态特征 | 支持 TB 级状态,避免 OOM |
| 缓存策略 | Redis 中设置特征 TTL,过期自动淘汰冷数据 | 内存利用率提升 40% |
| 特征降维 | 对高基数特征进行哈希分桶或 embedding 降维 | 存储空间减少 60%,查询延迟降低 30% |
关键指标: 在线推理链路中,特征获取阶段(HBase + Redis 查询)的 P99 延迟应控制在 20ms 以内,实时特征计算的端到端延迟(事件发生 → 特征可用)应控制在 10 秒以内,以保证模型决策的时效性。
3.5 离线与实时特征的一致性保障
离线特征与实时特征可能因数据源、计算逻辑或时间窗口差异而产生不一致。以下是常见的保障措施:
- 特征血缘追踪: 记录每个特征的来源表、计算 SQL、聚合窗口,便于排查差异。
- 周期性对账: 每小时/每天对比离线特征与实时特征在相同时间窗口内的聚合结果,差异超过阈值则触发告警。
- 特征版本管理: 离线特征和实时特征使用相同的特征版本号,确保模型训练和推理使用同一套特征逻辑。
- 兜底策略: 当实时特征缺失或异常时,自动降级使用离线特征或默认值,保证推理链路不中断。
通过上述架构设计与工程实践,信用评分模型在线推理链路中的特征存储与实时计算环节能够实现高可用、低延迟、强一致的目标,为模型提供稳定可靠的特征输入。