JMeta如何用极致技术实现接近完美的缓存一致性?

举报
bug菌 发表于 2024/12/31 09:51:23 2024/12/31
【摘要】 🏆本文收录于「滚雪球学SpringBoot」专栏,专业攻坚指数级提升持续更新中,up!up!up!!欢迎点赞&&收藏&&订阅。@TOC ✨ 前言在大规模分布式系统中,缓存一致性一直是个令人头疼的问题。特别是像 Meta(前身 Facebook)这样每天处理数十亿用户请求的互联网巨头,缓存一致性的设计至关重要。Meta 不仅要在超大规模的全球分布式架构中实现高可用性和低延迟,还要解决缓存和后...

🏆本文收录于「滚雪球学SpringBoot」专栏,专业攻坚指数级提升持续更新中,up!up!up!!欢迎点赞&&收藏&&订阅。

@TOC

前言

在大规模分布式系统中,缓存一致性一直是个令人头疼的问题。特别是像 Meta(前身 Facebook)这样每天处理数十亿用户请求的互联网巨头,缓存一致性的设计至关重要。

Meta 不仅要在超大规模的全球分布式架构中实现高可用性低延迟,还要解决缓存和后端数据库之间的数据一致性问题。而 “接近完美” 的缓存一致性,已经成为 Meta 的核心竞争力之一。

接下来,我们将深入探讨 Meta 在缓存架构设计中如何实现“接近完美”的一致性,并以技术视角解读其关键实践。


🧠 缓存一致性是什么?

缓存一致性指的是缓存中的数据与后端数据库的数据保持一致的程度。分布式系统中缓存的引入虽然提升了性能,但也带来了以下典型挑战:

  1. 读写延迟:写操作直接更新数据库,但缓存中的旧值可能还未失效,导致脏读。
  2. 缓存失效:缓存中的数据失效时如何触发更新。
  3. 并发冲突:多个节点同时读写同一数据时,如何保证一致性。

Meta 的挑战更为复杂:

  • 每天有数十亿用户访问。
  • 数据分布在多个全球数据中心,跨区域访问时延显著。
  • 一致性要求极高,比如好友列表、消息推送等。

🌟 Meta缓存一致性实现的关键技术

1. 缓存+数据库的双写一致性

Meta 的系统需要处理缓存和数据库的双写问题:当更新写入数据库后,如何同时更新缓存,避免脏数据。常规的做法有:

  • 写后删除(Write-Through): 每次写数据库后,删除缓存中的对应数据,让下一次读取直接从数据库拉取最新值。
  • 写后更新(Write-Around): 更新数据库的同时,将缓存的数据也更新。

Meta 使用的是写后删除的策略,但做了以下优化:

  1. 异步批量失效缓存:在写入数据库时,Meta 会将受影响的缓存键记录到一个消息队列中,然后异步清除。
  2. 高优先级缓存更新:某些关键数据(如用户好友列表)会直接同步到缓存,减少数据库负载。

2. 基于分布式日志的强一致性设计

Meta 为了解决分布式系统中缓存同步的延迟问题,采用了类似于分布式日志的架构,例如 Kafka 或自研的消息系统。

核心思路

  • 日志式传播更新:数据库的每一次更新都会写入到一个分布式日志系统中,日志消息会广播给所有缓存服务。
  • 订阅更新机制:各缓存节点会订阅对应的数据更新日志,并快速同步。

关键技术细节

  • 去重与乱序处理:日志系统会记录全局的序列号,确保缓存节点按顺序处理更新。
  • 数据分片的动态同步:每个缓存节点仅监听它所负责的分片数据。

3. 区域性缓存架构与跨数据中心同步

Meta 的数据分布在多个全球数据中心,为了降低跨区域访问延迟,Meta 构建了区域性缓存层

  • 区域性缓存层:每个区域的数据中心有本地缓存,用户请求优先命中本地缓存。
  • 跨数据中心同步
    • 区域缓存中的数据通过分布式一致性协议(如 Paxos/Raft)与主数据中心同步。
    • Meta 的跨区域缓存同步采用的是多主架构,即多个数据中心之间同步写入,而非单点主节点。

4. 一致性哈希算法

为了支持超大规模的缓存,Meta 使用了一种优化的一致性哈希算法来分布缓存的负载。

关键技术

  • 每个缓存节点根据哈希值确定它负责的键值范围。
  • 节点故障或扩容时,数据分布最小化重新分配,减少了缓存失效问题。

应用场景

  • 在热点数据(如用户点赞、好友关系)中,使用一致性哈希将数据均匀分布到不同节点上,避免单点过载。

5. 软失效与缓存刷新策略

Meta 采用了一种**软失效(Soft Expiry)**的缓存策略:

  • 缓存数据到期后,不会立即删除,而是标记为“过期”,并允许继续使用一段时间。
  • 后台进程会在标记“过期”后主动刷新数据。

优势

  • 缓解了数据库的压力。
  • 提高了用户请求的命中率,即使在高并发情况下也能稳定运行。

代码示例:软失效策略

public class CacheWithSoftExpiry {
    private final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();

    public String get(String key) {
        CacheEntry entry = cache.get(key);
        if (entry == null || isExpired(entry)) {
            // 数据过期或不存在,触发后台刷新
            refreshDataInBackground(key);
            return entry != null ? entry.value : null; // 继续返回旧值
        }
        return entry.value;
    }

    private boolean isExpired(CacheEntry entry) {
        return System.currentTimeMillis() > entry.expiryTime;
    }

    private void refreshDataInBackground(String key) {
        // 模拟后台异步刷新
        new Thread(() -> {
            String newValue = fetchFromDatabase(key); // 拉取最新值
            cache.put(key, new CacheEntry(newValue, System.currentTimeMillis() + 60000));
        }).start();
    }
}

代码解析:

  在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

类定义与成员变量
CacheWithSoftExpiry 类使用了一个 ConcurrentHashMap 来存储缓存条目,确保并发访问的安全性。cache 是一个缓存的核心容器,保存了键值对,其中键是字符串类型,值是 CacheEntry 对象。

get 方法
get 方法用于根据给定的键从缓存中获取值。如果缓存中找不到该键对应的条目,或者条目已过期,方法会触发后台刷新数据的操作。若缓存存在并且没有过期,直接返回缓存中的值。

  • 首先,通过 cache.get(key) 查找缓存中的 CacheEntry 对象。
  • 如果 entry == null(缓存中没有数据)或者数据已过期(通过 isExpired(entry) 判断),则调用 refreshDataInBackground(key) 方法异步刷新数据,并继续返回当前缓存中的值(即使数据已经过期)。
  • 如果数据未过期,则直接返回缓存的值。

isExpired 方法
isExpired 方法判断缓存条目是否过期。通过比较当前时间和条目的过期时间 entry.expiryTime 来确定。System.currentTimeMillis() 返回当前的系统时间(毫秒级别),如果超过了 expiryTime,则认为该条目已过期。

refreshDataInBackground 方法
refreshDataInBackground 方法在一个新的线程中异步执行,模拟从数据库拉取最新数据并更新缓存。

  • new Thread(...) 启动了一个新的线程,线程中的代码通过 fetchFromDatabase(key) 拉取最新的值,并用新的值和新的过期时间(当前时间 + 60秒)更新缓存。

CacheEntry 类
尽管 CacheEntry 类没有提供具体实现,但从代码可以推测,CacheEntry 可能包含两个字段:

  1. value:存储缓存的实际数据。
  2. expiryTime:记录数据的过期时间。

总结
这个类实现了一个带有软过期机制的缓存系统,当数据过期时会异步刷新数据,并且始终返回当前缓存的值(即使它已经过期)。这种设计可以减少因频繁访问数据库带来的性能压力,同时保证缓存中的数据尽可能保持更新。

6. 热点数据的多级缓存

为了解决热点数据(如明星动态、热门帖子)引起的缓存过载问题,Meta 设计了多级缓存架构

  • 本地缓存层(L1): 缓存用户请求的热点数据,响应最快。
  • 分布式缓存层(L2): 使用类似 Memcached 或自研缓存服务,存储全局数据。
  • 数据库后备(L3): 当缓存均未命中时,才请求数据库。

多级缓存的优点

  • 低延迟: L1 缓存直接命中,速度最快。
  • 高可用: L2 缓存分布式存储,故障隔离。

🌟 Meta接近完美的一致性:关键实践总结

  1. 双写一致性优化: 通过写后删除策略和异步更新机制,减少缓存中的脏数据。
  2. 分布式日志同步: 使用分布式日志系统保证缓存更新的顺序和一致性。
  3. 区域缓存与同步: 利用区域性缓存架构降低跨区域延迟,并通过分布式协议保证跨数据中心的强一致性。
  4. 一致性哈希: 确保缓存节点的负载均衡和动态扩容。
  5. 软失效策略: 缓解缓存更新带来的数据库压力,提升用户体验。
  6. 多级缓存架构: 提高缓存的命中率和高并发能力。

🔮 未来展望:缓存一致性的演进方向

虽然 Meta 已经实现了接近完美的缓存一致性,但仍有进一步优化的方向:

  1. 结合机器学习预测失效:通过用户行为数据预测数据的缓存需求,提前更新热点数据。
  2. 数据分片与流控优化:进一步优化热点分片和缓存流量控制,避免瞬时高峰导致缓存抖动。
  3. 与云原生结合:探索在云原生环境下的缓存一致性设计,例如利用 Kubernetes 动态扩展缓存服务。

结语

在 Meta 的超大规模分布式架构中,缓存一致性是一个动态权衡的过程。通过一系列先进的技术手段和策略,Meta 成功地实现了“接近完美”的缓存一致性。这些技术实践不仅适用于像 Meta 这样的大型企业,也为中小型企业提供了许多宝贵的借鉴。

缓存一致性没有终点,但技术创新从不止步。你是否也有关于缓存的一些独特见解?欢迎分享你的思考!🚀

🧧福利赠与你🧧

  无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。

最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。

同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。

✨️ Who am I?

我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。