优步凭借 CacheFront 的改进实现每秒 1.5 亿次的读取操作

作者 | Leela Kumili
译者 | 张卫滨

优步的工程师更新了 CacheFront 的架构,以满足每秒钟 1.5 亿次的读取操作,同时确保了更强的一致性。这次更新解决了延迟敏感的服务中陈旧数据的读取问题,并通过引入新的写入一致性协议、与 Docstore 的更紧密协作以及对优步 Flux 流系统进行改进,支持了不断增长的需求。

在早期的 CacheFront 设计中,通过消除请求重复并将频繁访问的键缓存在靠近应用服务的地方,实现了每秒 4000 万次读取的高吞吐量。虽然这种模型在可扩展性方面非常有效,但它缺乏强大的端到端一致性,这对于需要最新数据的工作负载来说是无法满足需求的。缓存失效依赖于生存时间(time-to-live,TTL)和变更数据捕获(change data capture,CDC),这实现了最终一致性并延迟了更新的可见性。这会造成特定的问题:在读取 - 拥有 - 写入(read-own-write)不一致性中,一条被读取、缓存然后更新的数据行可能会继续提供陈旧的值,直到它无效或过期。负缓存(存储“未找到”的结果)即使在行被插入后也可能返回错误的未命中结果,这可能会破坏读取 - 拥有 - 插入(read-own-insert)不一致性中的服务逻辑。

图片

CacheFront 以前的读写失效路径(图片来源:Uber Engineering 的博客文章)

新的实现引入了写入一致性协议以及位于查询引擎和 Flux(优步的流更新系统)之间的去重层。现在每个 CacheFront 节点在提供响应之前都会与 Docstore 验证数据的新鲜度。存储引擎层包括用于已删除行的墓碑标记和用于 MySQL 会话分配的严格单调时间戳。这些机制允许系统在提交前高效地识别和读取所有修改过的键,包括删除操作,确保即使在高负载下也不会提供陈旧的数据。

图片

改进后的 CacheFront 写路径与失效(图片来源:Uber Engineering 的博客文章)

当事务完成时,存储引擎会返回提交时间戳和受影响行键的集合。在这些响应上注册的回调会立即通过写入失效标记来使 Redis 中先前缓存的条目失效。Flux 继续尾随 MySQL binlogs 并执行异步缓存填充。这三种缓存填充机制:直接查询引擎更新、失效标记和 TTL 过期,结合 Flux 尾随,协同工作以便在支持极高读取吞吐量的同时保持强一致性。

优步的工程师 Preetham Narayanareddy 和 Eli Pozniansky 解释了改进背后的动机:

对更高的缓存命中率和更强的一致性保证的需求在不断增加。使用 TTL 和 CDC 进行缓存失效的最终一致性在某些用例中成为了限制因素。

通过这次集成,优步的工程师还能够弃用并移除之前引入的专用 API,减少了运维复杂性并简化了系统。

优步工程师增强了遥测和可观测性仪表板,以监控缓存健康和实时 binlog 尾随。缓存分片被重新组织以均匀分配负载。Cache Inspector 工具构建在与 Flux 相同的 CDC 管道上,它会将 binlog 事件与缓存中存储的条目进行比较。这些更新允许将表的 TTL 延长至 24 小时,将缓存命中率提高到 99.9% 以上,同时保持低延迟。

查看英文原文:

Uber Achieves 150M Reads per Second with CacheFront Improvements(https://www.infoq.com/news/2025/10/uber-cachefront-150m-reads/)

今日好文推荐