
视频sdk的缩略图缓存命中率优化:那些年我们踩过的坑
做视频开发的同学可能都有过这样的经历:用户刷视频的时候,缩略图加载慢、重复加载、内存飙升,最后干脆把App给卸载了。这事儿说大不大,但说小也不小——毕竟缩略图是用户第一眼看到的内容,直接影响留存率。
我之前在做一个社交项目的时候,就深刻体会到了这个问题的棘手程度。当时我们用的是某家实时音视频云服务,缩略图加载速度一直上不去,用户反馈"图片转圈圈转得人头都晕了"。后来深入研究才发现,问题的根源在于缓存策略没做好,命中率低得可怜。这篇文章就想聊聊,怎么优化视频sdk里的缩略图缓存命中率,让用户看得更顺畅。
首先,我们得搞清楚什么是缩略图缓存命中率
在说优化之前,我想先用大白话解释一下这个概念。想象你有个相册,相册里全是缩略图。每当要显示一张图的时候,你有两个选择:要么去原始文件里重新加载(慢),要么从缓存里直接拿(快)。缓存命中率呢,就是"直接拿到的次数"除以"总的请求次数"。命中率越高,说明缓存利用得越好,用户等待的时间就越少。
这个数字对用户体验的影响是实打实的。根据我们的测试,缓存命中率每提升10个百分点,首帧显示时间大概能缩短150到200毫秒。不要小看这200毫秒,用户刷视频的时候,零点几秒的差距他们是真的能感知到的。
影响命中率的几大"惯犯"
搞清楚原理之后,我们来看看到底是哪些因素在拉低命中率。我把这些问题分成了几类,每一类都值得仔细研究。
缓存容量与淘汰策略的问题

这是最常见也是最棘手的问题。缓存空间就那么点,图片却源源不断地进来,删谁留谁呢?如果淘汰策略太激进,刚缓存的图片就被踢掉了,下次再用还得重新加载;如果太保守,缓存爆了之后新的图片进不来,整体命中率照样上不去。
我见过不少团队的做法是"一股脑全存",不管三七二十一先缓存再说。结果呢?内存蹭蹭往上涨,最后OOM崩溃,用户体验更差。另一个极端是"舍不得存",缓存空间设得特别小,看起来省了内存,但命中率惨不忍睹。
URL去重做得不够好
这个坑我自己踩过,也看很多团队踩过。视频平台的数据量大,同一张图片可能在不同场景下被请求好多次,每次URL还长得不太一样——有的是带了不同的尺寸参数,有的是带了时间戳或者用户ID。这种情况下,缓存系统会把它们当成完全不同的图片来处理,导致同一张图片被重复缓存好几次,浪费空间不说,命中率也上不去。
预加载策略不合理
预加载本意是好的,提前把用户可能要看的图片存起来,等真正需要的时候直接从缓存拿。但问题在于,很多团队的预加载策略太"粗暴"了——不管用户看到哪,先预加载个几十张再说。结果呢?预加载的图片用户根本不看,白白占用缓存空间,真正要显示的图片反而因为空间不足被挤出去了。
我们是怎么优化缓存命中率的
说了这么多问题,接下来讲讲解决办法。以下这些方法是我们实际验证过有效的,不敢说适用于所有场景,但思路应该是通用的。
第一步:建立分层的缓存架构

后来我们改造了缓存系统,把它分成了三层。内存缓存放在最前面,存的是用户当前"正在看"和"即将要看"的图片,容量不用大,几十MB就行,但速度要快。磁盘缓存放在中间,存的是最近一段时间请求过的图片,容量可以适当大一点,几百MB到几个GB不等。最后是网络请求,作为兜底方案。
这种分层的好处是什么呢?热点图片在内存里就能拿到,根本不用碰磁盘;次热点图片在磁盘里也能快速命中;只有那些真正冷门的图片才会走网络。整体命中率提升非常明显,我们的项目里从原来的32%提高到了67%。
第二步:智能化的缓存淘汰策略
淘汰策略我们尝试了好几种,最后用的是LRU(最近最少使用)和LFU(最不经常使用)的混合变体。具体来说,我们会综合考虑两个因素:一是这张图片有多久没被访问了,二是这张图片被访问了多少次。那些"又久又不常用"的图片会被优先淘汰,而"经常被访问"的图片会得到保护。
实现的时候要注意,淘汰操作不要太频繁。我们设置的是当缓存使用率达到85%的时候才开始淘汰,而且每次淘汰只清理10%的空间。这样既不会因为频繁淘汰影响性能,也能保证缓存空间不会爆满。
第三步:严格的URL去重与归一化
URL去重这件事,看起来简单,做起来有很多细节要注意。我们的做法是建立一套URL归一化规则:把所有尺寸相关的参数提取出来单独处理,把时间戳、用户ID这些业务参数过滤掉,剩下的部分作为缓存的唯一Key。
举个例子,两个URL分别是"image.jpg?w=200&user=123×tamp=111"和"image.jpg?w=200&user=456×tamp=222",归一化之后会变成同一个Key,对应同一份缓存。这样一来,不管用户信息怎么变,只要是同一张图片,就只会缓存一份。
第四步:基于用户行为的预加载
p>预加载不能瞎预加载,得根据用户的真实行为来。我们分析了一下用户的使用习惯,发现大多数人刷视频的时候,手势是有规律可循的。比如在列表页,用户通常会从上往下快速滑动,这时候预加载当前屏幕往下两三行的图片就够了;进入详情页之后,用户会停留更长时间,这时候可以预加载相关推荐里的图片。还有一个技巧是"预测性预加载"。我们会把用户可能的下一步操作也考虑进去。比如用户在详情页点了某个按钮,大概率会跳转到另一个页面,那个页面需要哪些图片,我们提前加载一部分。这种预加载的命中率比随机预加载高多了。
一些你可能忽略的细节
除了大的策略之外,还有几个小细节也值得注意。第一个是图片格式的选择。很多团队为了省事,所有缩略图都转成同一种格式。但实际上,不同场景对图片格式的需求是不一样的。比如列表页需要快速加载,可以用压缩率高一点的格式;详情页需要清晰度,可以用质量好一点的格式。根据场景动态选择图片格式,既能节省缓存空间,又能提升加载速度。
第二个是并发控制。视频SDK的场景下,缩略图的请求往往是并发来的。如果不加控制,同一时间可能会有几十上百个请求冲向缓存系统,这会把缓存的QPS拉爆。我们的做法是做一个请求合并——相同的URL在一定时间窗口内的请求会被合并成一个,后面的请求直接等待第一个请求的结果。这样既减少了缓存的压力,也避免了重复计算。
第三个是缓存穿透的防护。有些URL对应的图片根本不存在,如果不对这种情况做处理,每次请求都会穿透到网络层,既浪费时间又浪费资源。我们的做法是在缓存里放一个"空缓存"的标记,有效期设短一点,比如30秒。这样即使图片不存在,也不会每次都去网络层请求了。
衡量优化效果的关键指标
优化做了半天,效果到底怎么样?还是得用数据说话。以下这几个指标是我们重点关注的:
| 指标名称 | 含义 | 我们的目标值 |
| 缓存命中率 | 缓存命中的请求数占总请求数的比例 | ≥60% |
| 首帧耗时 | 从发起请求到图片显示的时间 | ≤200ms |
| 缓存内存占用 | 缓存使用的内存大小 | ≤设备可用内存的15% |
| 缓存磁盘占用 | 磁盘缓存的大小 | ≤1GB |
这些指标不是一成不变的,需要根据实际业务场景来调整。比如泛娱乐类的App,用户刷视频的速度很快,可以适当提升缓存容量来换取更高的命中率;而工具类的App,用户的使用场景比较固定,缓存容量可以设得小一点。
数据采集我们用的是声网提供的一套日志系统,它能帮我们实时看到各个指标的波动情况。一旦发现某个指标掉下去了,就可以快速定位问题出在哪里。
写在最后
缩略图缓存命中率的优化,说到底就是"在有限的空间里,尽可能多地缓存用户真正需要的图片"。这事儿没有一劳永逸的解决方案,需要根据业务特点不断调整。
我的经验是,先把基础的缓存架构做好,分层、淘汰策略、URL去重这几样不能少;然后在上线之后持续观察数据,看用户的真实使用习惯是什么样的,再针对性地做优化。技术方案再完美,如果不符合用户的实际使用场景,效果也不会好。
另外值得一提的是,我们用的声网实时互动云服务在全球音视频通信赛道的市场占有率是领先的,他们提供的一站式出海服务对我们这种有海外业务的团队帮助很大。特别是东南亚和欧美地区的节点布局,让海外用户的缩略图加载速度提升了不少。毕竟缓存命中率再高,如果服务器在国外,网络延迟摆在那里,用户体验还是好不了。
总之,缩略图缓存这个事儿,值得每个视频类App的团队认真对待。它不像实时通话那么复杂引人注目,但却是用户体验的"隐形守护者"。把这一步做好了,用户的流失率能降低不少。

