想象你开了一家餐厅。起初生意冷清,后来短视频一爆,客人蜂拥而至。你发现后厨开始撑不住了:每道菜都从洗菜、切菜、下锅现做,客人催单声此起彼伏。你站在出菜口,脑子里只有一个问题——这一秒到底能端出几道菜? 这个“每秒出菜数”,就是特征服务的 QPS(Queries Per Second)。特征服务是 AI 系统背后的“大厨房”:推荐系统要查用户偏好、广告系统要查上下文、风控系统要查设备画像,每来一个请求,后厨就要快速端出一道“特征菜”。菜出不来,整个 AI 业务就瘫痪。 好消息是,优化 QPS 的思路和经营餐厅完全同构:把热销菜提前做成半成品(缓存)、一锅炒十份同样菜(请求合并)、先收菜单再慢慢做(异步化)、开门前先把锅烧热(预热)、客满就发号排队(限流熔断)、墙上挂数据看板(监控压测)。这七个动作,就是特征服务 QPS 优化的完整路线图。

岗位关联

对 AI 从业者而言,特征服务是推荐、搜索、广告等系统的“食材供应链”。掌握 QPS 优化,是数据工程师与 AI 架构师落地高并发系统的硬核能力,直接决定模型上线的真实响应速度与服务稳定性。

目录
  1. 可视化演示
  2. 指标与瓶颈
  3. 缓存加速
  4. 请求合并
  5. 异步化改造
  6. 资源与预热
  7. 熔断限流
  8. 监控压测
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

多级缓存架构 特征服务QPS优化的多级缓存架构与性能指标监控,展示不同优化阶段的吞吐量和延迟变化

指标与瓶颈

开餐厅的第一步,不是急着炒菜,而是先盘清自己手里有几口锅、每道菜要炒多久。你站在后厨门口,看着厨师满头大汗却端不出菜,心里只有一个疑问:这一秒到底能出几道菜?这个“每秒出菜数”就是特征服务的 QPS——每秒能处理的请求数量,它是衡量服务产能最直接的标尺。

但 QPS 不是孤立数字,它由两个更底层的指标决定:并发请求数和平均响应时间。并发请求数是同一时刻同时在处理的请求有多少个,就像后厨同时开火的灶位;平均响应时间是从客人下单到菜端上桌的耗时。两者相除,就是出菜速率——流水线上同时有 C 道菜在做,每道菜花 T 秒,每秒就能流出 C÷T 道菜。

生产环境还要盯住 P99 延迟:把一万次请求的响应时间从小到大排列,排在第 99% 位置的那个值,就是 P99。它代表“最倒霉的 1% 客人要多等多久”,专门用来揪出尾部慢请求。瓶颈定位通常从这两个指标入手:QPS 上不去,要么是并发通道太窄,要么是单次响应太慢。当你发现并发已经很高但 QPS 仍低,多半是响应时间失控了。

所以,优化 QPS 之前要像店长盘点后厨一样,先算清并发数和平均响应时间:是灶台不够(并发低),还是做菜太慢(耗时高)?有了这两个数字,后面所有优化手段才有的放矢。

QPS=CTavg\displaystyle QPS = \frac{C}{T_{avg}}
QPS 就是“每秒出菜数”,等于同时开火的灶位数除以每道菜的平均耗时。灶位再多,菜做得慢,出菜数一样上不去。
  1. 并发请求数 C同一时刻正在系统中处理的请求总数。好比后厨同时开火的灶台数,灶台越多,同时在做的菜越多;但灶台本身也占地方,不能无限增加。
    CC
  2. 平均响应时间 T_avg从请求进入系统到返回结果的平均耗时。每道菜从下单、排队到下锅、上桌的平均时长,包含等待和加工两部分。
    TavgT_{avg}
  3. QPS 的计算用并发数除以平均耗时,得到每秒能完成的请求数。注意并发上升的同时若耗时也上升,QPS 未必变大——就像灶台加多了人挤人,每道菜反而更慢。
    CTavg\frac{C}{T_{avg}}

缓存加速

想提高出菜速度,最聪明的办法不是把每道菜都现洗现切,而是把天天有人点的菜提前做成半成品:卤好的牛肉、切好的葱姜、调好的酱汁,客人一点单,热一热就能上桌。这些“半成品库存”,就是特征服务里的缓存。

特征服务的缓存通常分两层:本地缓存和分布式缓存。本地缓存像后厨灶台边的小料盒,取用最快,但容量小、每台灶各自独立;分布式缓存像后门的大冷库,容量大、所有灶台共享,但多一次网络往返。生产系统常常两层都上:先翻小料盒,没有再去冷库,还不行才回源现做。

缓存对平均响应时间的影响可以精确量化:命中率越高,平均耗时越接近缓存耗时。但缓存不是白拿的——特征源更新后,缓存可能变馊。所以要有更新策略:设置过期时间(TTL)、主动失效、异步刷新。为了守住命中率,热点特征要重点保活,冷门特征不必长期占用空间。漫无目的地堆缓存,就像把整个菜单都做成半成品,库存爆炸还大量过期。

厨房的半成品库越丰富,客人等菜时间越短,QPS 自然水涨船高。但半成品也会过期变质,缓存必须配上更新策略,否则端出去的就是一盘馊菜。

Tavg=h×Tcache+(1−h)×Tsource\displaystyle T_{avg} = h \times T_{cache} + (1 - h) \times T_{source}
平均出菜时间 = 命中率×翻热半成品的时间 + 未命中率×现炒的时间。命中率越高,客人越快吃到菜。
  1. 命中率 h请求在缓存中找到结果的概率。客人点卤牛肉,一下就在小料盒里翻到半成品,这就是命中。
    hh
  2. 缓存耗时 T_cache读一次缓存的延迟,毫秒级。相当于翻一下小料盒、热一下半成品的时间。
    TcacheT_{cache}
  3. 回源耗时 T_source缓存没命中时,去特征源重新计算或查询的耗时,通常是几十毫秒甚至上百毫秒。相当于从洗菜切菜开始现炒。
    TsourceT_{source}
  4. 加权平均命中走快路、未命中走慢路,按比例加权得到平均耗时。半成品库越全,走快路的请求越多,平均出菜就越快。
    h×Tcache+(1−h)×Tsourceh \times T_{cache} + (1 - h) \times T_{source}

请求合并

如果你点了宫保鸡丁,隔壁桌也点了宫保鸡丁,厨师会怎么做?当然是凑够一批一起下锅,一次颠勺出十份。这正是请求合并的思想:与其为一个请求单独跑一趟上游,不如把多个相同或相似的请求攒起来,一次性处理。

在特征服务里,大量请求会拿同一个特征(比如“用户城市”“商品分类”),或者同一批用户的不同特征。按一个时间窗口(比如 10 毫秒)收集请求,合并成批量请求向下游发送,这就是窗口聚合。更进一步,服务端可以主动把重复查询折叠成一次,让所有等待方共享同一份结果,这叫请求折叠——相当于几个桌一起点宫保鸡丁,后厨只炒一锅,各家分菜。

合并后的吞吐量可以用一个简洁式子表达:每秒能处理的请求数,约等于“每批能装几个请求 ÷ 每次上游调用耗时”。合并前 N 个请求要打 N 次上游,合并后只需 N 除以批大小那么多次。批越大,削掉的重复调用越多。但注意,合并不是无限的:窗口太长会让响应时间变长,批量太大可能撑爆上游,批大小和窗口时间必须按 P99 预算来调。

后厨的大锅饭策略看似简单,却立竿见影——菜还是那些菜,锅还是那口锅,只因为“一锅炒十份”,翻台率立刻上了一个台阶。

QPSmerged=BTupstream\displaystyle QPS_{merged} = \frac{B}{T_{upstream}}
把 N 次单独点单变成 N/B 次批量下单,吞吐量直接放大 B 倍。锅还是那口锅,一次炒十份,产能就上去了。
  1. 批量大小 B一次合并处理的请求数。好比一锅能炒几份宫保鸡丁,锅越大,一次出菜越多。
    BB
  2. 上游耗时 T_upstream一次批量上游调用的总耗时。B 个请求合并后只付一次网络往返和一次源端计算,而不是 B 次。
    TupstreamT_{upstream}
  3. 合并后吞吐每秒能完成的批量调用次数乘以每批的请求数,就得到 QPS。
    BTupstream\frac{B}{T_{upstream}}

异步化改造

客人多的时候,聪明的服务员会先收下菜单、说声“马上好”,让客人安心落座等,而不是让所有人堵在柜台前干瞪眼。这个“先收单、后做菜”的思路,就是异步化。

同步处理时,每个请求线程全程占着一个灶台,菜没好之前火不能停。而异步化让请求线程发出任务后立刻释放,去接待下一批客人;真正做菜由后台任务完成,结果好了再通知客人。在特征服务里,异步通常体现为非阻塞 IO、Future 回调和消息队列。一个请求往往要查多个特征源(用户画像、商品信息、环境特征):串行查询时耗时累加,并发发出、统一聚合,总耗时只取决于最慢的那个源。

异步化还要处理背压:下游处理不过来时,队列不能无限堆积,否则就像等位的客人把门口堵死。常见做法是限制队列长度、触发降级,宁可拒绝一小部分请求,也不能让系统崩溃。并发与串行的耗时差异,用数学表达就是下面的不等式。

异步化把“堵在柜台前等”变成“落座等菜”,灶位不再被空等浪费,同样的人手和灶台,翻台率却能翻好几倍。

Tparallel=max⁡(T1,T2,…,Tn)≤∑i=1nTi=Tserial\displaystyle T_{parallel} = \max(T_1, T_2, \dots, T_n) \le \sum_{i=1}^{n} T_i = T_{serial}
并发等待多个特征源,总耗时取最慢的那个;串行等待则要把所有耗时加起来。异步化的收益,就是让“等菜时间”从相加变成取最大值。
  1. 每个源耗时 T_i第 i 个特征源的响应时间。比如用户画像 20ms,商品信息 30ms,环境特征 10ms。
    TiT_i
  2. 并发聚合多个源同时请求,总耗时等于最慢的那个源——30ms。相当于三个灶台同时开火,最晚熟的那道菜决定开饭时间。
    max⁡(T1,T2,…,Tn)\max(T_1, T_2, \dots, T_n)
  3. 串行求和一个接一个查询,总耗时是累加——60ms。前面没查完,后面就得排队等着。
    ∑i=1nTi\sum_{i=1}^{n} T_i
  4. 不等关系并发永不慢于串行,多数时候快得多。这是异步化改造最根本的数学依据。
    Tparallel≤TserialT_{parallel} \le T_{serial}

资源与预热

餐厅每天开门前有一件必做的事:开火、热锅、烧油。如果等第一位客人点完菜才起火,第一桌客人要等上半小时。特征服务完全同理——刚启动时缓存是空的、连接是冷的,如果流量直接压上来,第一批请求会全部打到特征源上,延迟飙高、错误率上升。

因此,服务上线前要“热锅”:预先把热点特征灌入缓存、预先建立数据库和下游连接、预热线程池。这就是冷启动预热。它让服务在承接真实流量之前,已经把最耗时的准备工作做完。很多大厂在发布新版本时,会先让服务“空转”几分钟再切流量,原理就是如此。

线程池和连接池的规模则决定厨房能承受的火力。线程不是越多越好:开太多线程,CPU 忙着切换上下文反而变慢。工程上常用一个经验公式估算最优线程数:IO 占比越高,越应该多开线程去并行等待;CPU 密集型的计算,线程数接近核数就够了。连接池同理——池太小请求排队,池太大浪费资源,需要结合压测结果微调。

提前烧热的锅、备好的连接,让流量高峰到来时服务从容不迫。就像老练的餐厅开门迎客前一切就绪,客人涌入也依然行云流水。

Nthreads=Ncpu×(1+TioTcpu)\displaystyle N_{threads} = N_{cpu} \times \left(1 + \frac{T_{io}}{T_{cpu}}\right)
最优灶台数 = CPU 核数 ×(1 + 等菜时间/炒菜时间)。等菜越久,越需要更多灶台同时等着,别让 CPU 闲着。
  1. CPU 核数 N_cpu机器可用的 CPU 核心数,是处理能力的硬上限。好比后厨能摆下的灶台总数。
    NcpuN_{cpu}
  2. IO 与计算比IO 等待时间与 CPU 计算时间的比值。特征服务大量时间在等网络返回,这个比值通常远大于 1。
    TioTcpu\frac{T_{io}}{T_{cpu}}
  3. 线程数公式每个核至少要有一份计算线程,再按等待比例增加并发等待线程。
    Ncpu×(1+TioTcpu)N_{cpu} \times \left(1 + \frac{T_{io}}{T_{cpu}}\right)

熔断限流

生意火爆也可能成为灾难:客人源源不断涌进来,后厨迟早崩盘——灶台烧穿、盘子摔碎、厨师累倒。聪明的餐厅会在门口放一个取号机,满员就发号排队;如果某道菜的供应商突然断货,就先暂停这道菜,而不是让客人空等。这就是限流与熔断。

限流为服务划定一个最大速率:每秒最多放 N 个请求进后端,超出的请求要么排队,要么直接返回“稍后再试”。常见算法有令牌桶和滑动窗口。熔断则是在下游持续异常时主动断开:当错误率超过阈值,后续请求不再打向下游,而是快速返回兜底结果——默认值、缓存副本、降级文案——给下游喘息恢复的时间。

令牌桶的限流能力可以这样理解:一个桶以固定速率补充令牌,请求进来必须取走一个令牌才能放行。桶里攒下的令牌允许短时间应付流量尖峰,但长期平均速率仍被补充速率锁死。这就像取号机:平时客流平稳,攒一批号可以集中放行,但总的接待能力是固定的,队伍不会无限放进来。

取号机不是赶客,而是保护厨房不超载;熔断不是没菜吃,而是把有限资源留给真正能上桌的菜。稳定,才是高 QPS 的前提。

QPSmax=r+bΔt\displaystyle QPS_{max} = r + \frac{b}{\Delta t}
瞬时最大放行量 = 稳定速率 + 桶里攒的余量。取号机既允许短时集中放行,又保证后厨不被超载压垮。
  1. 补充速率 r每秒往桶里放的令牌数,决定了服务长期稳定的处理速率。
    rr
  2. 桶容量 b桶里最多攒多少令牌,决定了能承受多大的突发流量。好比取号机后排队的人数上限。
    bb
  3. 突发余量把桶里攒的令牌匀到一段时间里,就形成了突发速率。
    bΔt\frac{b}{\Delta t}
  4. 最大 QPS稳定速率加突发余量,得到瞬时允许的最大请求速率。
    r+bΔtr + \frac{b}{\Delta t}

监控压测

一家成熟的餐厅,后厨墙上一定挂着数据看板:今日翻台率、每道菜出餐用时、食材消耗速度、差评关键词。店长每天的功课就是看数据、复盘、调菜单。特征服务的 QPS 优化同样是一场需要数据支撑的持久战,没有监控的优化等于蒙眼开车。

核心监控指标包括:QPS、平均响应时间、P99、错误率、缓存命中率、线程池活跃数、队列长度、下游依赖耗时。这些指标要能按服务、接口、特征维度拆分,才能定位到底是哪台灶、哪道菜拖了后腿。压测则是把问题提前暴露的手段:在灰度环境用模拟流量逐步加压,直到指标恶化,摸出单实例的真实上限。

有了单实例上限,容量规划就变成一道算术题:目标流量除以单实例能力,再乘上冗余系数。比如业务目标 10 万 QPS,单实例实测能扛 5000,留 50% 冗余,大约需要 30 个实例。压测还能帮我们揪出“纸面达标、尾部爆炸”的隐患——平均延迟很漂亮,P99 却已经超时。

数据看板让店长心里有数,压测演习让每个岗位知道自己的极限。只有持续监控、定期压测、复盘调优,餐厅才能在客流高峰中稳稳接住每一单。

Ninstances=⌈QPStargetQPSsingle×(1+冗余率)⌉\displaystyle N_{instances} = \left\lceil \frac{QPS_{target}}{QPS_{single}} \times (1 + \text{冗余率}) \right\rceil
需要几个厨房分店 = 目标客流 ÷ 单店产能,再乘以冗余系数向上取整。数据不会说谎,压测让你提前知道极限在哪。
  1. 目标 QPS业务期望的峰值流量。好比节假日预估的客流。
    QPStargetQPS_{target}
  2. 单实例上限单台机器压测出的稳定 QPS 上限。相当于一家分店实测的接待能力。
    QPSsingleQPS_{single}
  3. 冗余率为突发流量预留的系数,通常 20%~50%。多留的椅子,是为了旺季不拒客。
    冗余率\text{冗余率}
  4. 实例数计算目标除以单实例,乘上冗余系数,再向上取整得到部署规模。
    ⌈QPStargetQPSsingle×(1+冗余率)⌉\left\lceil \frac{QPS_{target}}{QPS_{single}} \times (1 + \text{冗余率}) \right\rceil

AI 应用场景

电商大促推荐特征服务

阶段:在线特征存储与特征供给

解决的问题:大促流量尖峰是日常的 10 倍以上,特征服务成为推荐系统的瓶颈。传统做法是每来一个请求就实时算一遍特征,大促时直接打爆数据库。

具体案例:某电商平台大促前夕,推荐系统每秒向特征服务发起峰值 20 万 QPS 的请求。团队采用三层优化:Redis 缓存热点商品与用户特征,命中率超过 95%;设置 10ms 窗口批量合并同批特征的查询;异步并发拉取用户与商品特征。最终 P99 延迟从 120ms 降到 30ms,用 30 个实例平稳扛住峰值流量。

广告实时竞价特征服务

阶段:实时特征计算与召回

解决的问题:程序化广告的每次曝光竞价必须在 50ms 内返回,否则出价无效。特征源散落在多个团队维护的系统中,串行拉取根本来不及。

具体案例:某 DSP 平台将用户兴趣、上下文、广告主画像三类特征做成独立特征服务,本地缓存 + 分布式缓存双层兜底;特征缺失时异步回源并回填缓存;对下游画像服务设置熔断阈值,超时后返回默认画像参与竞价。优化后服务 QPS 从 3000 提升到 1.8 万,竞价超时率下降 95%。

金融风控实时特征服务

阶段:实时特征计算与决策

解决的问题:欺诈检测要求毫秒级响应且必须高可用,特征服务一旦抖动,会拖垮整条支付链路。

具体案例:某支付平台风控系统对每笔交易实时计算设备、IP、用户行为等特征。团队引入消息队列做异步特征补齐,避免同步等待拖慢主链路;用令牌桶限制对黑名单服务的调用速率,熔断时降级为静态规则。在双 11 峰值 30 万 QPS 下保持 99.99% 可用性。

Python 演示

python70 行
# -*- coding: utf-8 -*-"""特征服务 QPS 优化模拟器:对比缓存与批量合并的吞吐提升"""import timeimport randomfrom collections import deque  class FeatureService:    """模拟一个带缓存和批量合并的特征服务"""     def __init__(self, latency=0.002):        self.cache = {}        self.hit_count = 0        self.total_count = 0        self.latency = latency        self.batch_queue = deque()     def query_with_cache(self, key):        """缓存优先的单个特征查询"""        self.total_count += 1        if key in self.cache:            self.hit_count += 1            return self.cache[key]        time.sleep(self.latency)        value = random.randint(1, 100000)        self.cache[key] = value        return value     def query_batch(self, keys):        """批量合并查询:未命中特征只回源一次"""        result = {}        miss_keys = [k for k in keys if k not in self.cache]        for k in keys:            self.total_count += 1            if k in self.cache:                self.hit_count += 1                result[k] = self.cache[k]        if miss_keys:            time.sleep(self.latency)            for k in miss_keys:                value = random.randint(1, 100000)                self.cache[k] = value                result[k] = value        return result  def run_single(total=60):    svc = FeatureService(latency=0.002)    start = time.time()    for i in range(total):        svc.query_with_cache("user_%d" % (i % 20))    elapsed = time.time() - start    hit_rate = svc.hit_count / svc.total_count    print("单请求模式: QPS=%.0f 命中率=%.1f%%" % (total / elapsed, hit_rate * 100))  def run_batch(total=60, size=10):    svc = FeatureService(latency=0.002)    keys = ["user_%d" % (i % 20) for i in range(total)]    start = time.time()    for i in range(0, len(keys), size):        svc.query_batch(keys[i:i + size])    elapsed = time.time() - start    hit_rate = svc.hit_count / svc.total_count    print("批量合并模式: QPS=%.0f 命中率=%.1f%%" % (total / elapsed, hit_rate * 100))  if __name__ == "__main__":    run_single()    run_batch()
目的:用一个小型模拟器对比“逐请求回源”和“批量合并回源”的 QPS 差异,直观展示缓存与请求合并带来的吞吐提升。
  1. self.cache = {}用字典充当半成品库:key 是特征名,value 是算好的特征值。
  2. if key in self.cache:翻小料盒:命中直接返回,不回源。
  3. time.sleep(self.latency)模拟每次回源特征计算的耗时。
  4. miss_keys = [k for k in keys if k not in self.cache]批量模式先筛出没命中的 key。
  5. if miss_keys: time.sleep(self.latency)关键优化:一批缺失特征只回源一次,而不是每个都回源。
  6. run_single()单请求模式:每个请求独立走一遍“查缓存→miss→回源”的流程。
  7. run_batch()批量模式:攒满一批再查询,miss 特征合并回源,QPS 大幅提升。

常见误区

误区“QPS 上不去?把线程池从 200 开到 2000,反正线程不占多少内存,开得越多处理得越快!”

正解线程超过最优值后,CPU 大量时间花在线程上下文切换上,性能反而下降。就像厨房太小却硬塞二十口锅,厨师转身都困难,出菜反而更慢。线程数应该根据 CPU 核数和 IO 等待比计算,并压测验证拐点。

为何常见:直觉以为“资源越多越快”,忽略了调度开销这个隐藏成本。

误区“把特征全部缓存起来,命中率做到 100%,QPS 不就飞起了吗?”

正解缓存有内存成本和一致性成本。冷门特征一天被查不了几次,缓存占用内存还容易过期失效;热点特征才是缓存的重点。全量缓存就像把整本菜单的食材都备成半成品,库房爆炸、大量过期浪费,得不偿失。

为何常见:只看到命中率提升的单边收益,忽视了内存占用和特征过期带来的双重代价。

误区“云上资源便宜,QPS 不够就水平扩容,多部署几个实例不就行了?”

正解水平扩展确实有效,但如果瓶颈在下游数据库或特征源,加再多实例只会把下游打挂。而且新增实例冷启动时缓存是空的,流量分摊后整体命中率下降,可能越扩越慢。扩容之前,先想清楚瓶颈在计算、IO 还是下游。

为何常见:分布式时代“加机器”成了条件反射,跳过了最关键的瓶颈分析。

误区“监控面板上平均耗时 10ms,服务很健康,不用优化了。”

正解平均延迟会被大量快请求拉低,掩盖尾部慢请求。P99 才是用户体验的真相——最差的 1% 客人决定餐厅口碑。平均 10ms、P99 500ms 的服务,在大促时同样会超时雪崩。优化必须同时盯住平均和 P99 两个指标。

为何常见:平均值算起来直观,人类直觉偏爱“用一个数字总结一切”,却不知它最会骗人。

误区“限流会把请求拒之门外,影响用户体验,宁可让服务扛着也不要限流。”

正解服务崩溃后是 100% 请求失败,限流后只有超出的那部分请求被拒,剩余的还能正常服务。配合排队等待、降级返回默认特征、缓存副本兜底,可以把体验损失降到最低。限流不是赶客,是保住大部分客人的用餐体验。

为何常见:把限流等同于“拒绝”,没看到它保护长期可用性的价值——服务活着,才有机会服务下一位客人。

面试题

Q1. 特征服务中的 QPS 最接近以下哪种说法?

单选题 · easy

  • A.每秒能处理的特征请求数← 正确答案
  • B.每次请求的网络传输速度
  • C.特征服务的磁盘读写速度
  • D.每秒新增的特征数量

答案:A。QPS 即每秒查询数(Queries Per Second),衡量服务每秒能处理多少请求,相当于餐厅每秒端出几道菜。网络传输速度、磁盘速度和特征新增量描述的都不是“处理请求的速率”。

Q2. 关于 P99 延迟,下列说法正确的是?

单选题 · easy

  • A.99% 请求的平均耗时
  • B.排序后第 99% 位置的耗时值← 正确答案
  • C.最快 99 个请求的平均耗时
  • D.99 秒内的总请求耗时

答案:B。P99 是把所有请求的响应时间排序后,取第 99% 分位的值。它回答的是“最倒霉的 1% 客人等了多久”,而不是平均值或最快值。就像餐厅里 99% 的菜都能在 5 分钟内上桌,剩下 1% 可能要等半小时——P99 抓的就是那半小时。

Q3. 异步化改造的核心收益是?

单选题 · easy

  • A.减少单次请求的 CPU 计算量
  • B.释放线程等待时间以提升并发能力← 正确答案
  • C.降低特征服务的特征数量
  • D.显著降低缓存库存的占用

答案:B。异步化让请求线程发出任务后立刻释放去处理其他请求,而不是阻塞等待上游返回。收益是减少线程空等,让同样的线程数服务更多请求。它不改变 CPU 计算量,也不减少特征数量。

Q4. 以下哪些措施可以直接提升特征服务的 QPS?

多选题 · mid

  • A.提升本地缓存命中率← 正确答案
  • B.多个请求批量合并回源← 正确答案
  • C.删除所有异常日志输出
  • D.把超时时间调大一倍

答案:AB。缓存命中率提升直接降低平均响应时间,批量合并减少上游调用次数,两者都提高吞吐。删除日志可能掩盖问题,调大超时反而让请求挂更久,都不直接提升 QPS。

Q5. 缓存平均耗时 2ms,特征源耗时 20ms,命中率 95% 时平均响应时间约为?

单选题 · mid

  • A.2.9ms← 正确答案
  • B.5.0ms
  • C.19.0ms
  • D.20.0ms

答案:A。平均耗时 = 命中率×缓存耗时 + 未命中率×特征源耗时 = 0.95×2 + 0.05×20 = 2.9ms。95% 的请求走快路,只有 5% 走慢路,平均耗时接近快路。这也是为什么提高命中率对降低延迟立竿见影。

Q6. 关于令牌桶限流算法,下列说法正确的是?

单选题 · mid

  • A.桶容量决定突发流量上限← 正确答案
  • B.令牌速率决定长期均值
  • C.令牌桶只能限流不能应对突发
  • D.桶满后新令牌会加倍补充

答案:A。令牌桶里桶容量 b 决定可突发消耗的令牌总数,令牌补充速率 r 决定长期平均速率。令牌桶的设计目标恰恰是“允许突发但限制均值”。选项 C、D 描述与算法原理相悖。

Q7. 请求合并窗口设置过大的副作用是?

单选题 · hard

  • A.响应时间 P99 上升← 正确答案
  • B.服务 QPS 持续下降
  • C.缓存命中率显著下降
  • D.错误率必然下降

答案:A。合并窗口越大,请求在窗口内等待攒批的时间越长,尾部请求的延迟显著增加,P99 上升。批量合并提升的是吞吐而非降延迟,窗口过大会牺牲响应时间。QPS 不一定下降,命中率和错误率与窗口大小无必然关系。

Q8. 某特征服务 QPS 低但 CPU 利用率也低,最可能的瓶颈是?

单选题 · hard

  • A.下游 IO 等待或依赖瓶颈← 正确答案
  • B.本地 CPU 算力不足
  • C.内存溢出或 OOM 频繁
  • D.日志采集线程过多

答案:A。CPU 利用率低说明计算资源空闲,QPS 却上不去,大多是线程在等下游返回——网络 IO、数据库查询、外部服务调用。就像厨师闲着没菜可炒,问题出在供应链而不是灶台。CPU 不足、OOM、日志过多都会表现为 CPU 或内存异常,而非“低 CPU + 低 QPS”。

Q9. 生产环境监控特征服务,应重点关注哪些指标?

多选题 · mid

  • A.QPS 与错误率统计← 正确答案
  • B.P99 延迟与线程活跃数← 正确答案
  • C.缓存命中率与队列积压← 正确答案
  • D.代码注释行数统计

答案:ABC。QPS、错误率、P99、线程数、命中率、队列长度都是衡量服务健康度的核心指标,每一项都对应一个可能的瓶颈点。代码注释行数与运行性能完全无关,监控它是没有意义的。

Q10. 某特征服务目标 QPS 为 10 万,单实例压测上限 5000,要求 50% 冗余,最少需部署多少实例?

单选题 · extreme

  • A.20 个实例
  • B.30 个实例← 正确答案
  • C.40 个实例
  • D.50 个实例

答案:B。先用目标 QPS 除以单实例能力:100000 ÷ 5000 = 20;再乘以冗余系数 1.5,得到 30,向上取整正好是 30。冗余是为了应对流量尖峰和单实例故障,20 个是理论下限,没有任何余量。