JAVA本地缓存选型(Guava cache/Caffeine/Ehcache)

目录

1 概述

缓存是什么?

为什么需要缓存?

有哪些常见缓存方案?

一些相关术语和概念

2 Guava Cache

简单示例

3 Caffeine

3.1 使用接口

3.2 实现架构

3.3 关于性能

读 (100%)

读 (75%) / 写 (25%)

写 (100%)

3.4 关于命中率

CountMin Sketch --减小计数器空间占用

保鲜机制

TinyLFU

W-TinyLFU

4 Ehcache

简单示例

5 对比总结


1 概述

缓存是什么?

        维基词典将缓存定义为未来需要的、可快速检索的内容的存储。缓存是临时数据的集合,这些数据要么是位于其他地方的数据的副本,要么是计算的结果。缓存中已有的数据可以重复访问,且时间和资源成本极低。

为什么需要缓存?

        - 减少重复计算

        - 提升服务吞吐

        - 降低底层数据系统负载

有哪些常见缓存方案?

        - 分布式 (Memcache/Redis/Tair)

        - 本地 (纯内存/内存+磁盘)

一些相关术语和概念

        - 缓存命中,即对于给定key,从缓存中能取到合法数据条目,衡量缓存是否高效的最重要指标就是缓存命中率;

        - 热数据,对于大多数程序,数据访问往往都呈现2/8规则,总有少部分数据会被频繁访问,这部分数据成为热数据;

        - 驱逐,缓存中能存放的元素数量总是有限的,当容量满时,新元素的加入意味着老元素需要被驱逐。驱逐策略很大程度上影响着缓存命中率,常见的策略有FIFO/LRU/LFU;

2 Guava Cache

        Guava Cache的设计类似于ConcurrentHashMap,功能全面,使用简单。常见的使用场景是作为ConcurrentHashMap的替代品,即 当你需要一个支持淘汰能力的ConcurrentHashMap时,可以简单的使用Guava Cache来代替。

        - 支持LRU的淘汰策略,但不支持自定义策略;

        - 按元素插入时间或访问时间做淘汰;

        - 指定最大容量;

        - 移除事件监听;

        - LoadingCache,即指定元素加载逻辑;

简单示例

    Cache<String, Object> cache = CacheBuilder.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(60, TimeUnit.SECONDS)
            .build();

3 Caffeine

        Caffeine可以被看做Guava的升级版,它实现了非常高性能的读写接口,且提供的W-TinyLFU淘汰算法可以带来更高的缓存命中率。Spring5 开始Caffeine代替了Guava作为默认Cache实现。

3.1 使用接口

        在接口层面,Caffeine基本兼容了Guava的用法。

    Cache<String, Object> cache = Caffeine.newBuilder()
            .maximumSize(10_000)
            .expireAfterWrite(60, TimeUnit.SECONDS)
            .build();

3.2 实现架构

3.3 关于性能

性能上,关键的设计是顺序访问队列、异步化读写,分层时间轮、代码生成等。具体见:Design zh CN · ben-manes/caffeine Wiki · GitHub

以下是Caffeine官方给出的,与ConcurrentHashMap/Guava Cache的性能对比。Caffeine的读写性能要远好于Guava,甚至超过不带缓存特性的ConcurrentHashMap。

读 (100%)

在这个基准测试中, 8 线程对一个配置了最大容量的缓存进行并发读。

读 (75%) / 写 (25%)

在这个基准测试 中,对一个配置了最大容量的缓存,6 线程 进行并发读,2 线程进行并发写。

写 (100%)

在这个基准测试 中,8 线程对一个配置了最大容量的缓存进行并发写。

3.4 关于命中率

Caffeine 使用l Window TinyLfu策略因为其高命中率和更低的内存开销。W-TinyLFU是基于LFU演进的算法,解决了LFU无法处理历史高频数据可能占据缓存坑位的问题。

CountMin Sketch --减小计数器空间占用

CountMin Sketch使用了一个二维数组(实现上是个long一维数组)和多个hash函数,来近似的记录每个key的访问频率,思想类似布隆过滤器。

如图所示,当某个key被访问时,会根据预先定义的4个hash函数,找到数组中的四个位置,并更新其中的值。这4个值中的最小值,即被视为此key的访问频率。

之所以使用4个hash方法、并取最小值作为频率,是为了减少hash冲突导致某些实际访问频率低,但和某个热点key hash冲突 的key会被误当做热点key的情况。

保鲜机制

如果计数器中的记录的访问次数永远只增不减,会使得老数据永远被保留,而新的热点数据还没等到频率增加就被淘汰。TinyLFU采用的机制是,当CountMin Sketch中记录的频率总和大于缓存最大数量的10倍时,对整个Sketch中的所有值做重置。所谓重置,具体是对所有记录的频率除2。

TinyLFU

TinyLFU作为其他缓存(如LRU)的前置,当新数据进入缓存前,会使用其CountMin Sketch中记录的频率数据,和原有缓存中被淘汰的数据进行PK,胜利者写入缓存。

W-TinyLFU

W-TinyLFU在TinyLFU的基础上,增加了前置的Window Cache,目的是解决突发性热点数据的保留问题。Window Cache本身基于LRU,新数据首先会写入它,LRU的结构使得哪怕是新出现的热点数据,也不会被淘汰。另外一方面,Window Cache的初始大小仅为总缓存大小的1%,也不破坏整体的LFU设计。

从Window cache淘汰的数据,会再进入到TinyLFU中进行判断,和从MainCache淘汰下来的数据进行PK,胜利者写回MainCache,失败者则被彻底淘汰。

MainCache,还分为Probation segment和protected segment,设计类似JVM的新生代和老年代,新数据会被首先写入Probation segment,当Probation segment的数据被再次访问时,会被移到protected segment中。

4 Ehcache

Ehcache 是一个开源的、基于标准的,健壮、可靠、快速、简单、轻量级的java分布式缓存,支持与其他框架的集成,是Hibernate默认的CacheProvider。同时也实现了JSR107的规范,是Jcache的一种实现。
不同于Guava和Caffeine的纯内存设计,Ehcache支持多种介质:堆、堆外、磁盘、分布式,且支持多层组合。

4.1 简单示例

            // CacheManager
            CacheManager cacheManager =     CacheManagerBuilder.newCacheManagerBuilder()
                    .with(CacheManagerBuilder.persistence(new File(getPath())))
                    .build();

            cacheManager.init();
            
            // ResourcePools
            ResourcePools resourcePools = ResourcePoolsBuilder.newResourcePoolsBuilder()
                    .heap(5000, EntryUnit.ENTRIES)
                    .offheap(40, MemoryUnit.MB)
                    .disk(10, MemoryUnit.GB)
                    .build();
            
            CacheConfiguration<String, Object> cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Object.class, resourcePools)
                    .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofHours(1)))
                    .build();

            // 创建Cache
            Ehcache<String, Object> myCache = (Ehcache<String, Object>) cacheManager.createCache(physicalTblName, cacheConfiguration);
            

4.2 读写链路

多层架构下,put时,数据首先写入权威层(即磁盘),并且将其他层的缓存值失效;get时,会从上而下依次尝试取数,直到下降至权威层,从下层取到数据后,再将数据放入上层。

由于每次put都会访问磁盘,设计一个高效的序列化器是比较重要的,否则性能会比较差。

5 对比总结

三种缓存特性对比

Guava CacheCaffeineEhcache
读写性能一般很好
命中率 中
驱逐策略LRUW-TinyLFULRU/LFU/FIFO
分布式不支持不支持支持
磁盘不支持不支持支持
易用性较好

参考

Home zh CN · ben-manes/caffeine Wiki · GitHub

缓存那些事 - 美团技术团队

https://www.ehcache.org/documentation/3.10/index.html

https://arxiv.org/pdf/1512.00727

  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值