Java本地缓存

引言

缓存是存储在内存中的KV数据结构,分为分布式缓存和本地缓存。

分布式缓存方案中,一般应用进程和缓存进程不在同一台服务器,通过RPC或HTTP进行通信,可以实现应用服务和缓存的完全解耦,支持大量的数据存储,
分布式缓存常见有redis,memcache等。

本地缓存方案中的应用进程和缓存进程在同一个进程,没有网络开销,访问速度快,但受限于内存,不适合存储大量数据。本地缓存主要有Guava cache,Caffeine,Encache等,还可以通过HashMap自实现一套本地缓存机制。

今天重点来聊一聊本地缓存的应用。

Guava Cache

使用Guava Cache缓存本地数据,首先需要引入对应的工具包

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>      

本地缓存数据,并支持强制刷新。

@Component
public class MyGuavaCache {
    private LoadingCache<String, Field> fieldCache = CacheBuilder.newBuilder()
            .maximumSize(1000) // 设置最大容量
            .refreshAfterWrite(24, TimeUnit.HOURS) //设置过期刷新间隔
            .build(
                    new CacheLoader<String, Field>() { // 这是自动刷新
                        @Override
                        public Field load(String key) throws Exception {
                            return getFromDb(key);
                        }
                    }
            );

    /**
    * 从db取数
    */
    private Field getFromDb(String key) {
        return dbRepositiory.getByKey(key);
    }

    /**
     * 提供给外部调用获取结果
     */
    public Map<String, Field> get(String key) {

       try {
            return fieldCache.get(key); // 过期会自动执行load获取数据
        } catch (ExecutionException e) {
            throw new UncheckedExecutionException(e);
        }
    }

    /**
     * 手动强制缓存失效
     * @param key
     */
    public void invalidate(String key) {
        fieldCache.invalidate(key);
    }
}

Guava cache缓存支持最大容量限制,可以指定插入时间和访问时间的过期删除策略,且是线程安全,是基于LRU算法实现。在文章的结尾,会简单介绍一下LRU算法。

Caffeine

Caffeine内部采用了一种结合LRU、LFU优点的算法:W-TinyLFU,在性能上比Guava cache更加优秀,Caffeine的API基本和Guava cache一样。下面是Caffeine的三种加载方式:

/**
 * 手动加载
 */
Cache<String, String> cache = Caffeine.newBuilder()
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .maximumSize(1000)
    .build();
cache.get("key", k -> createData("key"));

/**
 * 同步加载
 */
LoadingCache<String, String> cache = Caffeine.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(this::createData);

/**
 * 异步加载
 */
AsyncCache<String,String> cache = Caffeine.newBuilder()
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .maximumSize(1000)
    .buildAsync();

CompletableFuture<String> data = cache.get(key , k -> createData(k));

Caffeine的过期设置也几乎和Guava cache一致

LoadingCache<String,String> cache = Caffeine.newBuilder()
    //限制最大数量
    .maximumSize(1000)
    //基于权重
    .maximumWeight(1000)
    //指定计算权重的方式
    .weigher(this::caculateWeight)
    //缓存在写入多久后失效
    .expireAfterWrite(1000,TimeUnit.SECONDS)
    //缓存在访问多久后失效
    .expireAfterAccess(1000,TimeUnit.SECONDS)
    //多种时间过期策略组合使用
    .expireAfter(new Expiry<String, String>() {
                public long expireAfterCreate(Key key, Graph graph, long currentTime) {
                    long seconds = graph.creationDate().plusHours(5)
                            .minus(System.currentTimeMillis(), ChronoUnit.MILLIS).getSeconds();
                    return TimeUnit.SECONDS.toNanos(seconds);
                }
                public long expireAfterUpdate(Key key, Graph graph,long currentTime, long currentDuration) {
                    return currentDuration;
                }
                public long expireAfterRead(Key key, Graph graph,long currentTime, long currentDuration) {
                    return currentDuration;
                }
            })
    .build(this::load);

Encache

Encache是一个纯Java的进程内缓存框架,是Hibernate中默认de CacheProvider。同Caffeine和Guava Cache相比,Encache的功能更加丰富,扩展性更强,支持多种缓存淘汰算法,包括LRU、LFU和FIFO,缓存支持堆内存储、堆外存储、磁盘存储(支持持久化)三种,支持多种集群方案,解决数据共享问题。

Encache在性能上不及Caffeine和Guava Cache,其中Caffeine性能最优,在本地缓存方案中,比较推荐Caffeine作为本地缓存工具,另外使用redis或者memcache作为分布式缓存,构造多级缓存体系,保证性能和可靠性。

缓存算法

缓存算法有FIFO先进先出、LRU最近最久未使用、LFU最近最少使用

FIFO

FIFO先进先出,是最简单的一种算法,最先进入的数据,将最先被淘汰,同队列的机制一样。

LRU

LRU 最近最久未使用,在很多分布式缓存系统(如Redis, Memcached)中都有广泛使用。LRU(Least recently used,最近最久未使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是如果数据最近时间被访问过,那么将来被访问的几率也更高。常用链表保存缓存数据,算法详细实现:
1)新数据会被插入到链表头部
2)缓存命中时,会将数据移到链表头部
3)当链表满的时候,链表尾部数据将会被丢弃

缓存污染问题
当存在热点数据时,LRU算法很好,但偶发性的、周期性的批量操作,会导致LRU命中率急剧下降,缓存污染情况也比较严重。
实现较简单,命中时需要遍历链表,找到命中的数据块索引,然后将数据移动到头部。

备注:【缓存污染指将不常用的数据移动到缓存中,降低缓存效率的现象】

LRU-K算法

K代表最近使用的次数,为解决LRU算法中缓存污染问题,将判断标准改为最近使用过K次(LRU相当于LRU-1),需要多维护一个队列,记录所有缓存被访问的历史,当访问次数达到K的时候,才将数据放到缓存;有限淘汰第K次访问时间距离当前时间最大的数据

算法实现:
1)数据第一次被访问时,加入到访问历史列表
2)数据在历史列表里没有达到K次访问,按一定规则淘汰,比如FIFO(先进先出),LRU(最近最少使用)
3)当历史队列数据访问达到K次后,将数据迁移到缓存队列,并从历史队列移除,
4)缓存数据被再次访问后,重新排序
5)缓存队列,优先淘汰末尾的数据,即倒数第K次访问距离限制最久的数据,

LRU-K具有LRU的优点,同时避免了LRU的缺点:缓存污染。
实际应用中LRU-2是综合各种因素后的最优选择,更大K值命中率虽然高,但适应性差,需要大量数据访问才能清楚历史访问记录。

LRU-K 降低了缓存污染的问题,命中率比LRU高;
LRU-K队列是一个优先级队列,算法复杂度和代价比较高;
需要额外记录被访问过还没缓存的数据,内存消耗要高一些,尤其数据量较大的情况;另外LRU-K需要基于时间进行排序,CPU消耗也会更高一些。

LFU

Least Frequently Used 最近最少使用

与LRU算法相比,从时间上来说,首先淘汰最长时间未被使用的数据
LFU则是淘汰近一段时间内被访问次数最少的数据

例如:一段时间T,主存块为3,若数据走向为1 2 1 2 1 3 4,当数据4过来时,主存块满触发淘汰策略,
按LRU算法,数据2被淘汰(数据2最久未被使用)
按LFU算法应换数据3(十分钟内,数据3只访问了一次)

所以LRU关键是看最后一次被使用到发生调度的时间长短,
而LFU关键是看一定时间段被使用的频率

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容