Redian新闻
>
Meta 如何将缓存一致性提高到 99.99999999

Meta 如何将缓存一致性提高到 99.99999999

公众号新闻

作者 | Mayank Sharma
译者 | 平川
策划 | 凌敏

本文最初发布于 Mayank Sharma 的个人博客。

缓存是一种很强大的技术,广泛应用于计算机系统的各个方面,包括高速缓存硬件、操作系统、Web 浏览器,特别是后端开发。对于像 Meta 这样的公司来说,缓存非常重要,因为它可以帮助他们减少延迟,扩展繁重的工作负载,并节省资金。由于他们的场景中大量使用了缓存,所以他们遇到了另一个问题:缓存失效。

过去这些年,Meta 已经将他们的缓存一致性从 99.9999(6 个 9)提高到了 99.99999999(10 个 9)。也就是说,在他们的缓存集群中,每 100 亿次缓存写入操作中只有不到 1 次不一致。

本文主要包含以下内容:

  1. 什么是缓存失效和缓存一致性?

  2. 为什么 Meta 如何重视缓存一致性,甚至 6 个 9 都无法满足他们?

  3. Meta 的监控系统如何帮助他们改进缓存失效和缓存一致性并修复 Bug?

缓存失效和缓存一致性

根据定义,缓存并不是真实的数据源。因此,当真实数据源中的数据发生变化时,应该有一个主动失效过期缓存项的过程。在这个过程中,如果处理不当,则缓存中可能会无限期地保留与真实数据源不一致的值。

那么我们该如何失效缓存?

我们可以使用 TTL 来保持缓存的新鲜度,这样任何其他系统都不会引发缓存失效。但是,在本文中,我们将讨论 Meta 的缓存一致性。我们假设,失效操作是由缓存之外的其他东西执行的。

首先,我们看下缓存不一致是如何产生的:

假设 1、2、3、4 是一个递增的时间序列:

  1. 首先,缓存填入来自数据库的值。

  2. 但是,在值 x =42 到达缓存之前,某个操作在数据库中将该值更新为 x=43。

  3. 为此,数据库发送了 x=43 的缓存失效事件,而且该事件在 x=42 之前到达,那么缓存值将设为 43。

  4. 现在,x =42 事件到达,缓存被设置成 42,于是不一致就产生了。

为了解决这个问题,我们可以使用一个 version 字段来执行冲突解决,使旧版本永远都不会覆盖当前版本。这种解决方案适用于几乎 99% 的互联网公司,但对于 Meta 这么复杂的系统,这可能还不够。

为什么 Meta 如此重视缓存一致性?

从 Meta 的角度来看,缓存不一致几乎和数据库中丢失数据一样糟糕。从用户的角度来看,那可能会导致非常糟糕的用户体验。

当你在 Instagram 上向一个用户发送私信时,在后台,这些消息会存储在主存中,并且会生成用户到主存的映射。

假如有三个用户:Bob、Mary 和 Alice。Bob 和 Mary 都向 Alice 发送消息。Bob 在美国,Alice 在欧洲,而 Mary 在日本。因此,系统会查询离用户居住地最近的区域,并将消息发送到 Alice 数据存储。在这种情况下,当 TAO 副本查询 BOB 和 Mary 所在的区域(都包含不一致的数据)时,它就会将消息发送到没有 Alice 消息的区域。

上述情况会导致信息丢失和糟糕的用户体验。因此,这是 Meta 需要首先解决的问题之一。

监    控

要解决缓存失效和缓存一致性问题,第一步是度量。要能够准确地度量缓存一致性,并在缓存中出现不一致条目时发出预警。而且,还要确保度量结果中不包含任何误报,因为如果值班工程师学会了忽略它,度量将失去信任并变得毫无价值。

抛开 Meta 的实际解决方案,最简单的解决方案是通过状态记录和跟踪每次缓存更改。在工作负载比较小的情况下,这种解决方案是可行的,但 Meta 的系统每天要进行超过 10 万亿次的缓存填充。记录和跟踪所有缓存的状态会把本已繁重的缓存负载变成异常繁重的工作负载,甚至都不用考虑还要对其进行调试。

Polaris

Polaris 是在一个非常高的层次上作为客户端与一个有状态的服务进行交互,它并不了解服务的内部机制。Polaris 遵循的基本原则是“缓存最终应该与数据库保持一致”。在接收到失效事件时,Polaris 会查询所有副本以验证是否有任何其他违规操作发生。例如:如果 Polaris 收到一个失效事件(x=4 @ version 4),那么它将作为客户端检查所有缓存副本以验证是否有违规的情况。如果有一个副本返回(x=3 @ version 3),那么 Polaris 会将其标记为不一致,并将其放入队列,以便稍后对照相同的目标缓存主机进行检查。Polaris 会报告特定时间范围内的不一致,如 1 分钟、5 分钟或 10 分钟。

这种多个时间范围的设计不仅让 Polaris 可以使用多个队列来有效地实现回退和重试,而且对于防止误报也是必不可少的。

为了加深理解,我们再看个例子。

假如 Polaris 接收到(x = 4 @ version 4)的失效消息。但是当 Polaris 检查缓存时,却找不到 x 的数据条目,它应该将此标记为不一致。这种情况下有两种可能。

版本 3 的 x 不可见,而版本 4 是对该键的最新写入,这确实是一个缓存不一致。可能是版本 5 的写入操作删除了键 x,而 Polaris 也许只看到了比失效事件中的数据更新的视图。

我们怎么才能确切地知道这两种情况中哪一种是正确的?

对于这两种情况,Polaris 需要通过查询数据库进行查验。绕过缓存的查询可能是计算密集型的,并且还可能使数据库暴露于风险中,因为保护数据库和扩展读取量大的工作负载是缓存最常见的两个用例。所以,我们不能向系统发送太多的查询。

为了解决这个问题,Polaris 会延迟执行此类检查,并在不一致的样本超过设置的阈值(比如 1 分钟或 5 分钟)时才发起数据库调用。Polaris 给出的指标是“在 M 分钟内 N 个 9 的缓存写入是一致的”。所以现在,Polaris 提供了一个指标:在 5 分钟内 99.99999999 的缓存是一致的。

现在,让我们通过一个代码示例来看下 Polaris 如何帮助 Meta 解决了一个 Bug。这个例子是关于缓存不一致是如何产生的。

让我们通过以下的示例代码来看下这个过程。

假设缓存维护了一个键到元数据的映射和一个键到版本的映射。

cache_data = {}cache_version = {}meta_data_table = {"1": 42}version_table = {"1": 4}

当接收到读取请求时,会首先检查缓存中的值,如果值不在缓存中,就从数据库返回这个值。

def read_value(key):    value = read_value_from_cache(key)    if value is not None:        return value    else:        return meta_data_table[key]def read_value_from_cache(key):    if key in cache_data:        return cache_data[key]    else:        fill_cache_thread = threading.Thread(target=fill_cache(key))        fill_cache_thread.start()        return None

缓存返回结果 None,并利用数据库返回的值填充缓存。我这里利用线程异步实现了这个过程。

def fill_cache(key):    fill_cache_metadata(key)    fill_cache_version(key)
def fill_cache_metadata(key): meta_data = meta_data_table[key] print("Filling cache meta data for", meta_data) cache_data[key] = meta_data
def fill_cache_version(key): time.sleep(2) version = version_table[key] print("Filling cache version data for", version) cache_version[key] = version
def write_value(key, value): version = 1 if key in version_table: version = version_table[key] version = version + 1
write_in_databse_transactionally(key, value, version) time.sleep(3) invalidate_cache(key, value, version)
def write_in_databse_transactionally(key, data, version): meta_data_table[key] = data version_table[key] = version

与此同时,当版本数据被填充到缓存中时,数据库又有新的写入请求更新了元数据值和版本值。这看起来像是一个 Bug,但它不是,因为缓存失效应该把缓存带回到与数据库一致的状态。(注意:为了重现这个问题,我在缓存和数据库写入函数中加了 time.sleep)。

def invalidate_cache(key, metadata, version):    try:        cache_data = cache_data[key][value] ## To produce error    except:        drop_cache(key, version)
def drop_cache(key, version): cache_version_value = cache_version[key] if version > cache_version_value: cache_data.pop(key) cache_version.pop(key)read_thread = threading.Thread(target=read_value, args=("1"))write_thread = threading.Thread(target=write_value, args=("1",43))print_thread = threading.Thread(target=print_values)

然后,在缓存失效期间,由于某种原因,失效失败,在这种情况下,异常处理程序将删除缓存。

删除缓存函数的逻辑是最新版本大于 cache_version_value 则删除键,但我们不是这样做的。因此,这会导致过时的元数据无限期地驻留在缓存中。

还请注意,这个例子只是简单地说明下 Bug 可能如何发生,实际的 Bug 会复杂得多,会涉及数据库复制和跨区域通信。只有当上述所有步骤都发生,并且按照这个特定的顺序发生时,才会触发 Bug。不一致的情况很少出现。Bug 隐藏在交错操作和瞬态错误后的错误处理代码中

一致性跟踪

假如你在值班,你收到了 Polaris 报告的缓存不一致信息,你首先要做的是检查日志,看看问题可能出在哪里。正如我们前面所讨论过的,记录缓存数据的每个更改几乎是不可能的,但是如果我们只记录可能导致更改的更改呢?

在上面的代码中,如果缓存没有接收到失效事件或失效失败,就会出现问题。作为值班人员,我们需要检查以下内容:

  • 缓存服务器接收到失效事件了吗?

  • 服务器正确处理失效了吗?

  • 该数据项后来不一致了吗?

Meta 已经构建了一个有状态的跟踪库,在这个紫色的小窗口中记录和跟踪缓存变化,其中包含所有触发 Bug 导致缓存不一致的奇怪而复杂的交互。

小    结

对于任何分布式系统,可靠的监控和日志系统都是必不可少的,那可以确保我们捕获错误并快速找到根本原因,从而缓解问题。在 Meta 的例子中,Polaris 发现了异常并立即发出了警报。借助一致性追踪信息,值班工程师只用了不到 30 分钟就定位了问题。

原文链接:

https://medium.com/@mayank.sharma2796/how-meta-improved-their-cache-consistency-to-99-99999999-58d79674a806

声明:本文为 InfoQ 翻译,未经许可禁止转载。

点击底部阅读原文访问 InfoQ 官网,获取更多精彩内容!

今日好文推荐

德国再次拥抱Linux:数万系统从windows迁出,能否避开二十年前的“坑”?

谷歌大裁员引发元老集体抗议:领导脑袋空空,无能的中层管理团队不断扩大

系统 bug 致百人入狱,砸了 2.8 亿元仍上云失败!二十年了,这家大企业被日本软件坑惨了

钉钉 AI Agent Store 上线了!软件竞争格局重构:Agent 掀起新风暴,App 何去何从?

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
长期手机辐射,或致性功能异常、影响发育与脑活动?施一公院士:电磁辐射对生物系统的影响,这次说清楚了!每人每月$500!就业率翻一番, 多人建议将每月发钱提高到1000美元免费无门槛!Wetzel’s Pretzels免费送Original Pretzels啦!Transformer解码真实场景!Meta推出70M参数SceneScript模型Meta革命新架构掀翻Transformer!无限上下文处理!纯加法Transformer!结合脉冲神经网络和Transformer的脉冲Transformer | NeurIPS 2023聊一台性价比高到变态的游戏本在大温买房,如何将资产传承下一代?必备干货早知道!统一角色、百变场景,视频生成神器PixVerse被网友玩出了花,超强一致性成「杀招」联合国大会一致通过美国主导的人工智能决议“不想过度依赖中国”,美日达成一致上岸笔记 | 恭喜 美本 Aggie同学 成功斩获 Bellwether Asset Management,全职offer最新 Amex Offers 汇总【Secrets & Impression by Secrets 度假村 $750返$200】加拿大狂发签证后果严重!近2万游客、探亲者留下当难民! 比例高到惊人!《那是故乡》&《浴火重生》英国永居这么容易保值?离开英国超15年,申returning签证1周获签!如何提高成功率?卫生巾的苦恼;因打包饭菜毁了女儿的情感;当当俞渝的建议与感悟【惊爆】加拿大狂发签证后果严重:近2万游客、探亲者留下当难民 ! 比例高到惊人 !下个月,加州快餐业的工资将提高到每小时20美元,已有餐厅提前裁员!如何购买适合自己家庭的人寿保险?联合国大会一致通过,有关人工智能!ByteHouse 如何将 OLAP 性能提升百倍?每人每月$500!这州“发钱”计划见成效!受益者希望每月能提高到$1000.....王健林:“宗馥莉是我最心仪的儿媳,可惜犬子不争气 ”Stability AI开源上新:3D生成引入视频扩散模型,质量一致性up,4090可玩中年以后,学会清理“人生缓存”Looking Good: China’s Cosmetics Appeal to Overseas Markets纽约华人团结一致!万人百车游行, 抗议布鲁克林86街游民所建案关注|加拿大资本利得税征税比例从50%提高到67%!背刺炒房客,对大温地产市场影响深远惊爆!加拿大狂发签证后果严重!近2万游客、探亲者留下当难民! 比例高到惊人!拜登承诺将富豪最低税率提高到25%Meta is working on an AI wristband that can read brain signa纽约华人团结一致!万人百车游行,抗议布鲁克林86街游民所建案英雄“照”過美人關真有其人以色列一意孤行,执意进攻拉法,美国德国态度一致坚决反对不舒服的恋情,如何将人一点点耗尽
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。