在线服务架构:特征服务在推荐/搜索系统中的位置与低延迟要求
聊到实时特征存储,很多人第一反应是“存得快、查得快”。但说实话,存储只是地基。真正让特征产生价值的,是它和在线服务之间的那套配合机制。今天我们就来拆解一下,特征服务在推荐和搜索系统里到底站在哪个位置,以及为什么延迟这个指标会被盯得那么死。
特征服务在推荐系统中的位置
先画一张图,帮你快速建立全局感。下面是我自己常用的一个分层示意,把特征服务放在整个推荐链路里看,它的角色就很清楚了。
你看,用户请求进来之后,第一站不是直接跑模型,而是先经过特征服务。特征服务负责把用户画像、物品属性、上下文信号、实时行为这些散落在不同地方的数据拼起来,组装成模型能吃的特征向量。我习惯把这个过程叫做“特征拼接”。
在推荐系统里,特征服务通常夹在召回和排序之间。召回阶段用粗粒度的特征筛出候选集,排序阶段才用精细特征做打分。特征服务主要服务排序阶段,因为排序对特征的完整性和实时性要求最高。
特征服务在搜索系统中的位置
搜索系统和推荐系统不太一样。搜索多了一个 Query 理解环节,特征服务的位置也略有偏移。
| 环节 | 特征服务参与方式 | 典型特征 |
|---|---|---|
| Query 理解 | 提取 Query 侧特征 | 分词、意图、实体识别 |
| 召回 | 使用倒排 + 粗粒度特征 | 类目、品牌、价格区间 |
| 精排 | 特征服务全量拼接 | 用户实时行为、物品实时 CTR |
| 重排 | 业务规则 + 多样性特征 | 已曝光列表、同质化惩罚 |
我个人觉得,搜索场景下特征服务最大的挑战是 Query 侧特征的实时计算。比如用户搜“今天能到的手机”,特征服务需要实时解析出“今天能到”这个时效性意图,然后去查库存和物流特征。这个计算路径比推荐场景更复杂。
低延迟要求:为什么这么苛刻?
好,位置搞清楚了。接下来聊聊延迟。你可能听过“推荐系统 p99 延迟要控制在 50ms 以内”这种说法。为什么是 50ms?不是 100ms 也不是 200ms?
原因其实很朴素。一个完整的推荐请求,从网关到召回、到特征服务、到模型推理、到排序、再到返回,整个链路通常只有 100-200ms 的预算。特征服务如果吃掉 50ms,留给模型推理的时间就只剩 30-40ms。你想想看,模型本身可能就要跑 20ms,特征服务再慢一点,整个请求就超时了。
低延迟要求具体体现在几个方面:
- 特征读取延迟: 从 Redis、HBase 或本地缓存拿特征,单次读取必须控制在 1-5ms 以内。
- 特征计算延迟: 实时聚合、交叉特征、时间衰减等计算,不能超过 10ms。
- 网络开销: 特征服务与下游模型服务之间的 RPC 调用,序列化/反序列化要尽量轻量。
- 批量 vs 单条: 能批量获取就不要逐条查,减少网络往返。
延迟预算的分配策略
在实际项目中,我习惯把整个在线链路的延迟预算做一个表格,贴在团队 Wiki 上。这样每个人都知道自己的模块该控制在多少毫秒以内。
| 模块 | 延迟预算(ms) | 说明 |
|---|---|---|
| 网关 & 路由 | 5 | 请求分发、鉴权、限流 |
| 召回 | 15 | 多路召回并行,取最快结果 |
| 特征服务 | 20 | 实时拼接 + 特征计算 |
| 模型推理 | 30 | DNN 模型前向计算 |
| 排序 & 过滤 | 10 | 业务规则、多样性控制 |
| 序列化 & 返回 | 5 | Protobuf 序列化、网络传输 |
| 总计 | 85 | 预留 15ms 缓冲,p99 控制在 100ms 以内 |
你看,特征服务占了 20ms。这 20ms 里,又得拆成特征读取、特征计算、网络传输三部分。每一部分都要精打细算。
避坑指南:我曾经踩过的延迟坑
说到延迟优化,我踩过不少坑。挑几个典型的说说:
- 缓存穿透: 有一回线上特征服务延迟突然飙升,查了半天发现是某个热门物品的特征在缓存里过期了,大量请求同时去查数据库,直接把 DB 打挂了。后来我加了布隆过滤器和本地缓存兜底,才稳住。
- 序列化选型: 早期我们用 JSON 传特征,一个特征向量序列化出来 2KB,后来换成 Protobuf,压缩到 400 字节。网络开销直接降了 80%。
- 批量获取的粒度: 我见过有人把 100 个物品的特征拆成 100 次 Redis 请求去拿,延迟直接爆炸。正确的做法是用 mget 或者 pipeline,一次网络往返搞定。
- 特征计算的热点: 实时 CTR 计算如果放在特征服务里做,遇到高并发场景很容易成为热点。我后来把这类计算前置到 Flink 任务里,特征服务只做读取,不做计算。
特征服务的高可用设计
低延迟和高可用是孪生兄弟。特征服务一旦挂了,模型拿不到特征,整个推荐链路就断了。我常用的高可用手段包括:
- 多级缓存: 本地缓存(Caffeine)→ 分布式缓存(Redis)→ 持久化存储(HBase)。本地缓存命中率能做到 70% 以上,延迟在微秒级。
- 降级策略: 如果某个特征源超时,直接返回默认值或者上次缓存的值,不要阻塞整个请求。
- 超时控制: 每个特征源的读取都要设置超时,我一般设 5ms,超时就走降级。
- 熔断机制: 如果某个特征源连续失败超过阈值,暂时切断对该源的请求,避免雪崩。
嗯,这里要注意一点:降级不是随便给个默认值就完事了。你得想清楚,这个特征降级之后对模型打分的影响有多大。有些特征(比如用户实时点击序列)降级了影响不大,但有些特征(比如价格、库存)降级了可能导致用户看到错误的信息。所以降级策略要分特征等级来设计。
小结
特征服务在推荐和搜索系统里,位置很明确——它是模型推理的前置数据处理器。低延迟是它的生命线,20ms 以内的预算要求我们必须在缓存、序列化、批量获取、计算前置这些方面做到极致。我个人的经验是,先把延迟预算分清楚,再逐段优化,最后用压测验证。别想着一步到位,迭代优化才是正道。