Jvm内部缓存选型?一篇文章为你解答疑惑
另一种可行方案来自于数据库理论,通过提交日志的方式来扩展写的性能。写入操作先记入日志中,随后异步的批量执行,而不是立即写入到数据结构中。这种思想可以应用到缓存中,执行哈希表的操作,将操作记录到缓冲区,然后在合适的时机执行缓冲区中的内容。这个策略依然需要同步锁或者tryLock,不同的是把对锁的竞争转移到对缓冲区的追加写上。 在Caffeine中,有一组缓冲区被用来记录读写。一次访问首先会被因线程而异的哈希到stripped ring buffer上,当检测到竞争时,缓冲区会自动扩容。一个ring buffer容量满载后,会触发异步的执行操作,而后续的对该ring buffer的写入会被丢弃,直到这个ring buffer可被使用。虽然因为ring buffer容量满而无法被记录该访问,但缓存值依然会返回给调用方。这种策略信息的丢失不会带来大的影响,因为W-TinyLFU能识别出我们希望保存的热点数据。通过使用因线程而异的哈希算法替代在数据项的键上做哈希,缓存避免了瞬时的热点key的竞争问题。 写数据时,采用更传统的并发队列,每次变更会引起一次立即的执行。虽然数据的损失是不可接受的,但我们仍然有很多方法可以来优化写缓冲区。所有类型的缓冲区都被多个的线程写入,但却通过单个线程来执行。这种多生产者/单个消费者的模式允许了更简单、高效的算法来实现。 缓冲区和细粒度的写带来了单个数据项的操作乱序的竞态条件。插入、读取、更新、删除都可能被各种顺序的重放,如果这个策略控制的不合适,则可能引起悬垂索引。解决方案是通过状态机来定义单个数据项的生命周期。 在基准测试中,缓冲区随着哈希表的增长而增长,它的的使用相对更节省资源。读的性能随着CPU的核数线性增长,是哈希表吞吐量的33%。写入有10%的性能损耗,这是因为更新哈希表时的竞争是最主要的开销。 Caffeine 举个例子 Mysql的缓存池,内部实现是一个LRU,但是其内部有个中间点,指向倒数3/8,一半是old区,另一半是young区,新数据插入是直接插入young区,这样就保护了真正的老数据不会被冲刷掉。 多级队列的形式 LFU结合频率这一属性给予更好的预测缓存数据是否在未来被使用。 但是传统LFU有其局限性: LFU实现需要维护大而复杂的元数据(频次统计数据等) 大多数实际工作负载中,访问频率随着时间的推移而发生根本变化,而传统LFU无法周期衰减频率 传统LFU的实现通过外接一个HashMap统计频率,但是HashMap存在Hash冲突,这会导致频率统计的不准确。 为了解决这些问题,Caffeine提出一种新的算法W-TinyLFU,它可以解决频率统计不准确以及访问频率衰减问题。这个方法让我们从空间、效率、以及适配矩阵的长宽引起的哈希碰撞的错误率上做权衡。 传统Hash存在Hash冲突的问题,使用LFU算法时候记录频率的话一旦发生hash冲突可能造成频率的统计错误。 W-TinyLFU算法使用一种Count-Min Sketch解决维护空间大的问题,类似布隆过滤器,降低冲突可能性,原理是多次hash分散开来,取最小值作为频率,一次Hash冲突的几率是1%的话,4次Hash的几率就是1%的4次方,大大降低的冲突可能性。 在Caffeine中为了实现Count-Min Sketch它在其中村政府,存放四个算法 其中randomSeed是一个随机数,sampleSize=开始设置的缓存最大树*10;table= 最大缓存数最接近的2的次方数(100的话是128,50是64);tableMask = table.length-1;size=0 在向缓存put数据的时候会调用 这个AddTask是一个Runnable,其中run方法会调用increment方法。 Caffeine比guava好在哪 W-TinyLFU (编辑:惠州站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |