热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Caffeine如何变热?

本文字数:4066字预计阅读时间:10分钟一、何谓热点数据?在某段时间范围内,经常被访问的数据可以称为热点数据。以视频网站为

  

本文字数:4066

预计阅读时间:10分钟

一、何谓热点数据?

在某段时间范围内,经常被访问的数据可以称为热点数据。

以视频网站为例,热点视频主要是来自于观众的观看,那么热点视频可以分为如下几类:

  1. 永久性热点数据

    比如某部热门电视剧,在其播出期间,观看频次非常高,那么在此期间,该电视剧可以算作永久性热点数据。

  2. 阶段性热点数据

    1. 周期性热点

      例如最近的欧洲杯,比赛视频观看频次都会随着比赛场次呈现出周期性的波动。

    2. 突发性热点

      主要来源于突发事件:例如地震报道,明星直播等等。

不同类别的热点视频区分主要依据是在某段时间范围内访问量的变化,也就是访问频率。

将热点数据缓存起来,将会明显提升应用的性能。

二、权衡

针对上面提到的三种热点数据,可以制定不同的缓存策略:

  1. 永久性热点:其访问频率随时间变化波动基本不大,这种数据尽量全部缓存起来。

  2. 突发性热点:其访问频率由于突发性而无法预估,故只能制定有效的检测手段。

  3. 周期性热点:虽然其访问频率随时间呈现周期性的波动,但是在每个周期内,都可以看做突发性热点。

如果所有的数据都能存储到进程内存中,岂不是能大大提升应用性能?

但是,实际情况却不是这样的。

针对java应用来说GC,会暂停整个应用。内存中缓存的对象越多,GC也会越频繁,GC总体时间也会越长。

而且针对动辄数千万的视频数据,其大小可能占到数百G的级别,也不可能全部放到内存中。

缓存有项重要的指标:命中率,就是访问某个数据时,该数据正好在缓存中,即为命中。

  • 内存越大,缓存的数据就越多,理论上命中率就会越高。

  • 内存越小,缓存的数据就越少,理论上命中率就会越低。

所以命中率本质上跟内存大小有直接的联系。

在命中率和内存大小之间权衡,以便获得最好的性能,是项技术活,于是各种各样的淘汰算法各显神通。

三、最佳命中率之Caffeine

当数据访问的概率随时间的变化是恒定的时候,LFU(Least Frequently Used)算法:淘汰使用频率最低的,缓存命中率可以达到最高。

因为它统计了缓存数据的访问频率(甚至包括历史数据),所以可以判断出那些是热的数据。

LFU的特点也给它带来了两个致命的缺点:

  1. 在大多数的实际场景中,访问频率会随着时间发生变化,而LFU无法自适应。

  2. 维护大量统计数据,内存消耗极大。

而另一种流行的淘汰机制LRU, 淘汰最久未被使用的数据,其能自适应时间变化,但命中率无法达到最高。

Caffeine采用了一种改进算法TinyLFU,解决了LFU的这两个缺点。下面来看一下TinyLFU的作用:

上图流程说明:

当有新数据需要放到缓存时,Cache将需要淘汰的数据,通过TinyLFU来决定是否用新数据替代被淘汰的数据,来提升命中率。

TinyLFU本质是布隆过滤器的变种,其使用Count–Min Sketch算法,可以用极小的内存,来实现大量数据统计:

这里存在三个问题,顺便说一下:

  1. 如果存在大量只访问一次的数据(长尾流量),那么TinyLFU中的计数器将被无效占用,从而影响频率判断。

  2. TinyLFU如何反映时间变化影响的频率变化?

  3. 突发的数据可能还没有累计够足够的频率就被淘汰了,导致命中率下降。

一、长尾流量问题

Caffeine在TinyLFU之前加一个Dookeeper(BloomFilter),过滤这种低频数据:

如果一个元素,在Doorkeeper中,则直接插入TinyLFU的主结构,否则先插入Doorkeeper

对于数据查询,会将Doorkeeper中的那一个计数值也加到计数值上去

这样DoorKeeper就可以将低频数据拦截住,降低了计数器数量。

二、时间变化影响的频率问题

如果整体计数超过某个阈值,会对TinyLFU所有的统计计数进行衰减。

保障剔除历史频率很高但之后不经常使用的数据,另外也降低了内存消耗。

三、突发性的稀疏访问问题

Caffeine采用了Window TinyLFU机制来解决这个问题:

即,增加一个只占总体缓存1%大小的Window缓存(内部采用LRU算法),所有的新数据都先进入Window缓存。

当Window缓存满了,淘汰的数据进入TinyLFU流程,这个被称为W-TinyLFU算法,它保证了突发的数据有机会被保留下来。

另外W-TinyLFU的Main Cache淘汰算法采用了分段LRU算法,进而保障突发热数据有机会被保留下来。

W-TinyLFU算法已经被证明可以适应时间变化进而提供近似最佳的命中率。

四、热点探测

Caffeine很优秀,可是这里还有几个问题:

  1. 在本地内存中做统计,而对于互联网应用来说,通常会部署多个实例,流量均分到每个实例上,

    这样,单个实例的数据情况无法代表整个应用的情况。所以从应用的整体角度看,可能某个数据是热的,但是在单个实例上并不明显。

  2. 当发现某个数据变热时,Caffeine只能在本地内存中生效,无法通知到其他实例。

如果在Caffeine之上,支持集中式热点数据探测,并且各个实例能够自动感知,岂不是更好?

我们已经知道,命中率跟数据的访问频率有很大关系,那么基于数据的访问频率做下筛选,即高于某个频率的数据才放到缓存中,那么就能保证缓存中的数据尽可能都是高频的。

热点探测原型如下:

主要分为两部分:

  1. 在客户端埋点,采用环形计数器统计数据的访问量,并定时上报统计数据。

  2. 探测器用于汇总客户端的统计数据,采用滑动窗口来做集中式探测,发现高频数据及时通知客户端缓存起来。

流程简单释义:

  • ① 客户端key计数统计。

  • ② 客户端统计轮切换。

  • ③ 客户端推送统计数据到探测器的缓冲队列。

  • ④ 探测器并行消费统计数据。

  • ⑤ 探测器采用滑动窗口统计key的访问频率。

  • ⑥ 探测器通知客户端Caffeine缓存热点数据。

上面的图示为了展示热点探测系统简化版流程,实际客户端实例有多个,探测器也是集群结构。

来看下采用热点探测系统如何来解决一、何谓热点数据中的三种热点数据:

  1. 永久性热点

    该类数据的特点就是访问频率较为稳定,可以针对此类数据设置固定的频率阈值。

    当此类数据在客户端快过期时,立马统计并上报访问情况,就可以保证该类数据一直缓存在客户端中。

  2. 突发性热点

    该类数据特点就是突发性的高频访问,随着时间进行衰减,探测系统采用滑动窗口计数,

    既能够保证达到阈值时及时探测到,又不会因为时间衰减而处于阈值边缘漏掉的情况。

  3. 周期性热点

    周期性热点在每个周期内都可以看做是突发性热点,故不再赘述。

五、热咖啡

我们基于京东的hotkey项目,做了大量优化和改进,衍生出高性能,高可靠的热点数据探测系统 - HotCaffeine。

架构如下:

组件介绍:

  1. etcd:配置中心,用于服务发现和配置等。

  2. client:客户端SDK,用于数据收集上报和接收热key。

  3. worker:用于接收客户端数据上报和热key计算。

  4. dashboard:管理后台和数据展示。

etcd安全性介绍:

etcd的安全模型如下:

HotCaffeine采用如下鉴权保障安全性:

  1. 针对客户端,需要单独的用户名和密码,但是他们共同拥有同样的角色,就是client,而client对资源/hotcaffeine/只有只读权限

  2. 针对worker,由于是内部应用,单独建立一个worker的角色,对资源/hotcaffeine/具有读写权限。

  3. 针对dashboard,由于是内部应用,需要给各个用户赋权,故其拥有root用户和权限。

HotCaffeine功能介绍:

  1. 多缓存配置:

    HotCaffeine客户端支持配置多个Caffeine缓存,并且支持动态调整缓存大小和过期时间。

  2. 灵活的热点规则配置:

    所谓热点规则就是针对热点数据的滑窗统计数据进行配置,例如在3秒内达到5次访问即认为变为热点数据。

    不同的热点规则支持对应到不同的缓存中,即热点规则和缓存缓存是多对多的。

    而且支持前缀规则匹配和动态修改,业务端可以实时修改规则实时生效。

  3. 热点数据实时查看:

    点击热key名可以查看热key在各个客户的实例对应的值:

  4. 调用量分布:

    业务端很少知道自己缓存的数据分布情况,使用调用量分布功能业务端就能大概知道key的分布情况,用于设置缓存大小和过期时间等有很大帮助。

    下面举个具体的例子来说明一下,假设业务系统在某段时间内共有如下6个key访问,访问总量为100:

    那么,如果这段时间内,key6在缓存中的话,命中率就能达到50%。而key1和key2在缓存中的话,命中率仅为10%。

    根据这个简单的例子,来看下调用量分布:

  • 调用量=2的key有944个,那么调用量为2的key的总调用量为1888,它占本次统计的总调用量的比例为17.95%。

  • 也就是说如果把调用量>=2的key都缓存下来,命中率为(1-43.86%)=56.14%。

  • 左边第一列是调用量,也就是上面那个例子中说的访问量。

  • 第二列是key的数量,根据这列可以大概知道key的多少(非重复),进而可以设置缓存大小。

  • 第三列是调用量占比,比如 以第二行的数据为例来说明一下:

  • 第四列是key的生存时间数据,可以参照个这个数据设置缓存的过期时间。

  • TopK热键:

    Topk作为阈值规则的补充,从访问量的维度来选择热键,它的度量指标是,即访问量最高的k个键作为热键

    此曲线图纵坐标轴含义是topk的访问量与总量的比例,即可以认为,此段时间内,如果将这些topk的键缓存下来,命中率可以达到此比例。

    鼠标放到曲线点上展示的是具体的数据情况。当点击该曲线点时,可以看到具体的topk数据,如下:

    这里的调用量是指从key上报开始,如果后续持续的有数据上报会持续统计。

    存活时间是指从key上报开始,至统计时的时间

    点击key的名字即可看到该key实时的滑动窗口数据,如下:

  • 吞吐优化:

    起初,在16核32G内存的docker配置下,HotCaffeine最高只能支持35万key/秒的探测:

    瓶颈主要体现在GC层面:

    由于大量对象在内存中无法及时回收导致频繁GC,而且GC时间较长等问题。针对这个问题,采取降低探测热点数据缓存时间的办法,即仅满足客户端时间范围要求即可,使吞吐量提升至稳定支撑40万key/秒:

    此时的瓶颈已经由GC转换到了CPU上,毕竟HotCaffeine是个CPU密集型应用,故尝试对锁竞争进行优化。

    优化主要包括如下两个方面:

    经过这些优化,HotCaffeine吞吐最终提升至59万key/秒:

    1. 将数据缓冲队列拆分为多个小队列,数据均匀hash到不同的队列,保障同样hash的数据肯定只在一个队列中,将并发变为并行处理。

    2. 针对某个待检测数据来说,后续的滑窗计数等不存在并发问题,故消除全部锁竞争代码。

  • 接入效果:

    下图为某个核心业务接入HotCaffeine后,客户端的响应情况:

    响应由42ms降低至36ms,降低14%。

  • 回馈社区:

    目前HotCaffeine已经在github开源,地址:https://github.com/sohutv/hotcaffeine。

    六、参考文献

    1. Caffeine

    2. 万字详解本地缓存之王-Caffeine

    3. W-TinyLFU论文

    4. Segment LUR

    5. 对象存储系统中热点数据的研究

    6. hotkey

    也许你还想看

    (▼点击文章标题或封面查看)

    RocketMQ中台化建设

    2021-01-14

    iOS:制作简易的 AAC 播放器 —— 了解音频的播放流程

    2021-08-26

    iOS的CoreData技术笔记

    2021-08-19

    MotionLayout动画从未如此简单!

    2021-07-15

    【文末有惊喜!】详解:mach-o文件如何分析多余的类和方法

    2021-07-08



推荐阅读
  • 本文深入解析了JDK 8中HashMap的源代码,重点探讨了put方法的工作机制及其内部参数的设定原理。HashMap允许键和值为null,但键为null的情况只能出现一次,因为null键在内部通过索引0进行存储。文章详细分析了capacity(容量)、size(大小)、loadFactor(加载因子)以及红黑树转换阈值的设定原则,帮助读者更好地理解HashMap的高效实现和性能优化策略。 ... [详细]
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 手指触控|Android电容屏幕驱动调试指南
    手指触控|Android电容屏幕驱动调试指南 ... [详细]
  • 本文深入解析了Python在处理HTML过滤时的实现方法及其应用场景。通过具体实例,详细介绍了如何利用Python代码去除HTML字符串中的标签和其他无关信息,确保内容的纯净与安全。此外,文章还探讨了该技术在网页抓取、数据清洗等领域的实际应用,为开发者提供了宝贵的参考。 ... [详细]
  • Java集合框架特性详解与开发实践笔记
    Java集合框架特性详解与开发实践笔记 ... [详细]
  • 构建基础的字符串队列实现方法
    在探讨如何构建基础的字符串队列实现方法时,我们发现许多开发者在面对这一问题时常常感到困惑。实际上,队列的基本原理非常简单,即遵循先进先出的原则。然而,在具体实现过程中,需要注意的是Java语言中并没有指针的概念,因此需要通过嵌套类来模拟指针,进而构建链表结构。这种实现方式不仅能够有效地管理字符串数据,还能提升代码的可读性和维护性。 ... [详细]
  • Python多线程编程技巧与实战应用详解 ... [详细]
  • 本文详细介绍了 Java 中遍历 Map 对象的几种常见方法及其应用场景。首先,通过 `entrySet` 方法结合增强型 for 循环进行遍历是最常用的方式,适用于需要同时访问键和值的场景。此外,还探讨了使用 `keySet` 和 `values` 方法分别遍历键和值的技巧,以及使用迭代器(Iterator)进行更灵活的遍历操作。每种方法都附有示例代码和具体的应用实例,帮助开发者更好地理解和选择合适的遍历策略。 ... [详细]
  • Android中将独立SO库封装进JAR包并实现SO库的加载与调用
    在Android开发中,将独立的SO库封装进JAR包并实现其加载与调用是一个常见的需求。本文详细介绍了如何将SO库嵌入到JAR包中,并确保在外部应用调用该JAR包时能够正确加载和使用这些SO库。通过这种方式,开发者可以更方便地管理和分发包含原生代码的库文件,提高开发效率和代码复用性。文章还探讨了常见的问题及其解决方案,帮助开发者避免在实际应用中遇到的坑。 ... [详细]
  • 初探性能优化:入门指南与实践技巧
    在编程领域,常有“尚未精通编码便急于优化”的声音。为了从性能优化的角度提升代码质量,本文将带领读者初步探索性能优化的基本概念与实践技巧。即使程序看似运行良好,数据处理效率仍有待提高,通过系统学习性能优化,能够帮助开发者编写更加高效、稳定的代码。文章不仅介绍了性能优化的基础知识,还提供了实用的调优方法和工具,帮助读者在实际项目中应用这些技术。 ... [详细]
  • 本文深入解析了Java 8并发编程中的`AtomicInteger`类,详细探讨了其源码实现和应用场景。`AtomicInteger`通过硬件级别的原子操作,确保了整型变量在多线程环境下的安全性和高效性,避免了传统加锁方式带来的性能开销。文章不仅剖析了`AtomicInteger`的内部机制,还结合实际案例展示了其在并发编程中的优势和使用技巧。 ... [详细]
  • 深入探索 JavaScript 中 Array 数组对象的基本操作与应用
    深入探索 JavaScript 中 Array 数组对象的基本操作与应用 ... [详细]
  • 分布式开源任务调度框架 TBSchedule 深度解析与应用实践
    本文深入解析了分布式开源任务调度框架 TBSchedule 的核心原理与应用场景,并通过实际案例详细介绍了其部署与使用方法。首先,从源码下载开始,详细阐述了 TBSchedule 的安装步骤和配置要点。接着,探讨了该框架在大规模分布式环境中的性能优化策略,以及如何通过灵活的任务调度机制提升系统效率。最后,结合具体实例,展示了 TBSchedule 在实际项目中的应用效果,为开发者提供了宝贵的实践经验。 ... [详细]
  • 本书详细介绍了在最新Linux 4.0内核环境下进行Java与Linux设备驱动开发的全面指南。内容涵盖设备驱动的基本概念、开发环境的搭建、操作系统对设备驱动的影响以及具体开发步骤和技巧。通过丰富的实例和深入的技术解析,帮助读者掌握设备驱动开发的核心技术和最佳实践。 ... [详细]
author-avatar
章胜一首简单的歌_192
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有