本文由 Kevin Lin 发表在 medium.com,经原作者授权由 InfoQ 中文站翻译并分享
原文链接:https://medium.com/pinterest-engineering/scaling-cache-infrastructure-at-pinterest-422d6d294ece
随着越来越多的用户到 Pinterest 寻求灵感,Pinterest 核心基础设施系统的需求增长的比以往任何时候都快。我们的核心存储系统之一是位于许多微服务和数据库前面的分布式缓存层。它处于 Pinterest 基础架构技术栈的底部,负责吸收由用户增长驱动的绝大多数后端流量。
Pinterest 的分布式缓存集群建立在 AWS 的 EC2 实例上,由数千台机器组成,缓存了数百 TB 的数据,高峰时,每秒可处理 1.5 亿个请求。该缓存层通过降低整个后端技术栈的延迟,来优化顶层性能,并通过减少昂贵的后端所需的容量来提供显著的成本效率。
本文中,我们将对支持 Pinterest 的大规模缓存集群的架构进行深入的技术研究。
每个对 Pinterest 的 API 请求都会在内部根据技术栈分发到复杂的 RPC 树,并在完成其关键路径前会涉及数十个服务。这可能包括查询关键数据(例如 Pinterest 的图片和收藏板)的服务,推送相关图片的推荐系统和垃圾内容检测系统。在这些服务中,只要其输入数据可以被唯一键值表示,就可以将该离散的查询单元的结果缓存在临时存储系统中,以便将来重用。
在 Pinterest,分布式缓存层的最常见用途是通过后备语义(look-aside semantics)来存储这类中间计算的结果。这使得缓存层能吸收一大部分流量。如果没有缓存层,这些流量会流向涉及复杂计算和昂贵存储的服务和数据库。凭借着毫秒级的尾延迟(tail latency),以及极低的单位请求基础架构成本,这个分布式缓存层提供了一个高性能低成本的后端扩展机制,以满足 Pinterest 不断增长的需求。
简化版的 Pinterest 的 API 请求生命周期:经过主要 API 服务,其依赖项后端以及分布式缓存层
通过提供分布式缓存层即服务,应用开发人员可以专注于实现业务逻辑,而不必担心分布式数据的一致性、高可用性或者内存容量。缓存层用户使用通用的路由抽象层,以确保应用程序具有容错性和一致的数据视图。此外,缓存服务端集群可以独立于应用层横向扩展,从而透明地调整内存或吞吐量,以适应资源使用情况的变化。
Memcached 和 mcrouter 构成了 Pinterest 分布式缓存基础架构的骨干,并且在 Pinterest 的存储基础架构中起着至关重要的作用。 Memcached 是由纯 C 语言编写的开源且高效的内存键值存储。Mcrouter 是应用层的 Memcached 协议代理,位于 Memcached 集群的前面,并提供强大的高可用性和路由功能。
Memcached 是缓存解决方案中非常有吸引力的选择:
Mcrouter 在 2014 年由 Facebook 开源,在扩展其 Memcached 部署方面发挥了关键作用。Mcrouter 也非常适合 Pinterest 的架构,原因如下:
从 mcrouter 到 Memcached 的请求路由总览。每个键前缀都与一个路由策略相关联,图中展示了两个例子。
在实践中,mcrouter 作为边车代理(proxy sidecar)被部署在和服务同一机器的单独进程。如图 2 所示,应用程序(可以由任何语言编写)在回送时将 Memcached 协议请求发送给 mcrouter,然后 mcrouter 作为代理将这些请求发送到数千个上游 memcached 服务器。这种架构能使我们在完全托管的缓存服务器集群中构建强大功能的同时,对客户端服务保持完全透明。
尽管从 Pinterest 早期开始,memcached 一直就是 Pinterest 基础架构的一部分,我们对其客户端的拓展策略在这些年来也在不断进化。具体来说,路由和服务发现在最开始是通过客户端库完成的(这其实很脆弱,而且它还与二进制部署紧密耦合)。然后,该方法被内部构建的一个路由代理取代(该路由代理没有提供用于高可用性的基础功能),最终被 mcrouter 取代。
Memcached 的效率很高:单个 r5.2xlarge EC2 实例每秒能支持超过 10 万个请求和数以万计的并发 TCP 连接,同时不会显着地增加客户端的延迟。这使 Memcached 成为 Pinterest 吞吐效率最高的生产服务。这部分归功于编写良好的 C 语言代码以及其体系结构。该体系结构利用了多个工作线程,每个工作线程独立地运行由”libevent“驱动的事件循环,来支持传入的连接。
在 Pinterest,Memcached 的 extstore 在存储效率方面取得了巨大的成功,具体的用例包括可视搜索以及个性化搜索推荐引擎。extstore 扩展了缓存数据容量,在 DRAM 之外增加了挂载在本地的 NVMe 闪存盘,从而将每个实例的可用存储容量从约 55 GB(r5.2xlarge)增加到将近 1.7 TB(i3.2xlarge),而实例成本只是略有增长。
在实践中,extstore 大大优化了数据用量受限的用例,尽管 DRAM 和 SSD 响应时间之间有几个数量级的差异,extstore 却没有牺牲端到端延迟。extstore 的内置调整工具使我们能找到一个平衡了磁盘 I/O、磁盘到内存的重新缓存速率、压缩频率和压缩程度以及客户端尾部响应时间的最佳平衡点。
Pinterest 的所有基础架构系统都是高可用的,我们的缓存系统也不例外。 通过利用 mcrouter 提供的丰富的路由功能,我们的 memcached 集群有着一系列的容错功能:
分布式系统的关键功能之一是水平可伸缩性,这是一种可以横向扩展而不是纵向扩展以适应额外的流量增长的能力。在 Pinterest,我们绝大多数的缓存工作量都是受吞吐量限制的,这需要集群中实例的数量与请求的数量大致呈线性比例关系。然而,memcached 本身是一个非常简单的键值存储,它本身并不会知道集群中的其他节点。那么每秒数亿个请求是如果通过网络发送到正确的服务器上的呢?
Mcrouter 通过对每个请求的缓存键运用哈希算法,来将请求确定性地发送到池中的某一个主机。这对于在服务器之间平均分配流量非常有帮助,但是 memcached 有一个独特的要求,即它的集群需要任意可伸缩性,也就是说运维人员要能够自由地根据不断变化的流量需求,来调整集群容量,同时最大程度地减少客户端的影响。
一致性哈希确保了在合格分片的总数增加或减少时,大多数键空间分区也可以映射到同一服务器。高度集中和可预测的命中率影响,允许系统在扩展时对客户端透明,从而防止容量的小范围变化导致集群命中率出现灾难性下降。
一致性哈希算法保证了当单一节点加入现有集群时,大多数键值空间所分配的服务器不变
客户端路由层将单个键值前缀映射到一个或多个这样的一致哈希池,这些一致哈希池位于某个路由策略之后,包括跨可用区复制集群的可用区亲和性偏好路由,针对位于基于闪存的容量集群后方的基于内存集群的 L1L2 路由(具有穿透)等。这样可以隔离流量,从而按客户端的用例情况来分配容量,并且可以确保来自 Pinterest 集群中任何客户端机器的一致缓存路由行为。
所有足够复杂的基础架构系统都具有一个共同特点:充满了(往往非常细微的)优劣权衡。在构建和扩展我们的缓存系统的过程中,我们权衡了许多方案的成本和收益。如下是最重要的几点:
展望未来,我们希望继续提高 Pinterest 缓存基础架构的效率、可靠性和性能。我们的努力包括当前的一些实验性项目,例如将 memcached 核心直接嵌入到主机应用程序进程中,以处理性能关键的用例(这能使 memcached 与服务流程共享内存空间,并消除网络和 I/O 开销)。此外还有可靠性项目,例如设计一个稳健的多区域冗余解决方案。
领取专属 10元无门槛券
私享最新 技术干货