栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > Java

多级缓存之Google Guava的实现方案

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多级缓存之Google Guava的实现方案

文章目录
  • 背景
  • 如何创建?
    • pom引入依赖
    • CacheLoader方式
      • 何时使用?
      • 案例
      • CacheBuilder 的详细方法
    • Callable方式
      • 何时使用?
      • 案例
  • 如何删除?
    • 被动
      • 基于数据大小的删除
      • 基于过期时间的删除
      • 基于引用的删除
    • 主动
      • 删除单条
      • 批量删除
      • 清空缓存
  • 总结

背景

数据库扛不住了可以使用Redis来分担读请求,在大访问量的系统中Redis集中式缓存方案,会成为大型系统的瓶颈。有什么方案解决呢?
可以在增加一层缓存层,即JVM进程的内存中进行本地缓存,分摊Redis的压力。Guava cache的设计来源于CurrentHashMap,可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读写性能。以下场景适合于做本地缓存:

  • 内存占用较小
  • 数据极少变化
  • 需要访问整个集合
  • 数据实时性要求不高
如何创建?

创建缓存方式有两种。

pom引入依赖

在项目中添加如下的依赖即可


   com.google.guava
   guava
   30.0-jre

CacheLoader方式 何时使用?

是否存在一个默认函数来加载或计算与键关联的值?如果是这样,则应使用CacheLoader。

案例
package com.linfanchen.springboot.lab.guava;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import static com.alibaba.druid.sql.ast.SQLPartitionValue.Operator.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class CacheTest {

    LoadingCache cache = CacheBuilder.newBuilder()
            .build(new CacheLoader() {
                
                @Override
                public String load(final String key) {
                    return getCar(key);
                }

                
                @Override
                public Map loadAll(final Iterable keys) throws Exception {
                    // 此包完整路径位于: com.google.common.collect.Lists
                    ArrayList keysList = Lists.newArrayList(keys);
                    return getCars(keysList);
                }

            });

    
    private static String getCar(String key) {
        return "Lexus";
    }

    
    private static Map getCars(List keys) {
        Map map = new HashMap<>();
        map.put("bmw", "BMW 530Li");
        map.put("benz", "E300L");
        map.put("audi", "Audi A6L");
        return map;
    }


    @Test
    public void firstTest() {
        java.util.List keys = new ArrayList<>();
        keys.add("bmw");
        keys.add("benz");

        // 从本地缓存读取数据
        try {
            System.out.println(cache.getAll(keys)); // 输出 {bmw=BMW 530Li, benz=E300L}

        } catch (ExecutionException e1) {
            e1.printStackTrace();
        }
    }



}


CacheBuilder 的详细方法
LoadingCache userCache = CacheBuilder.newBuilder()
         // 基于容量回收。缓存的最大数量。超过就取MAXIMUM_CAPACITY = 1 << 30。依靠LRU队列recencyQueue来进行容量淘汰
        .maximumSize(1000)
        
         // 基于容量回收。但这是统计占用内存大小,maximumWeight与maximumSize不能同时使用。设置最大总权重
        .maximumWeight(1000)
        
         // 设置权重(可当成每个缓存占用的大小)
        .weigher((o, o2) -> 5)
        
         // 软弱引用(引用强度顺序:强软弱虚)
         // -- 弱引用key
        .weakKeys()
        
         // -- 弱引用value
        .weakValues()
        
         // -- 软引用value
        .softValues()
        
         // 过期失效回收
         // -- 没读写访问下,超过5秒会失效(非自动失效,需有任意getput方法才会扫描过期失效数据)
        .expireAfterAccess(5L, TimeUnit.SECONDS)
        
         // -- 没写访问下,超过5秒会失效(非自动失效,需有任意putget方法才会扫描过期失效数据)
        .expireAfterWrite(5L, TimeUnit.SECONDS)
        
         // 没写访问下,超过5秒会失效(非自动失效,需有任意putget方法才会扫描过期失效数据。但区别是会开一个异步线程进行刷新,刷新过程中访问返回旧数据)
        .refreshAfterWrite(5L, TimeUnit.SECONDS)
        
         // 移除监听事件
        .removalListener(removal -> {
             // 可做一些删除后动作,比如上报删除数据用于统计
             System.out.printf("触发删除动作,删除的key=%s%n", removal);
        })
        
         // 并行等级。决定segment数量的参数,concurrencyLevel与maxWeight共同决定
        .concurrencyLevel(16)
        
         // 开启缓存统计。比如命中次数、未命中次数等
        .recordStats()
        
         // 所有segment的初始总容量大小
        .initialCapacity(512)
        
         // 用于测试,可任意改变当前时间。
        .ticker(new Ticker() {
             @Override
             public long read() {
                     return 0;
                    }
        })
        // 开始构造
        .build(new CacheLoader() {
             @Override
             public Object load(Object name) {
                     // 在cache找不到就取数据
                     return String.format("重新load(%s):%s", System.currentTimeMillis(), name);
                    }
        });
        
 // 简单使用
 userCache.put("car", "BMW 530Li 行政版");
 System.out.println(userCache.get("car"));
 
Callable方式 何时使用?

希望使用原子的“ get-if-absent-compute”语义,则应将Callable传递给get调用。

案例
package com.linfanchen.springboot.lab.guava;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

@RunWith(SpringRunner.class)
@SpringBootTest
public class CacheCallableTest {

    LoadingCache cache = CacheBuilder.newBuilder()
            .build(new CacheLoader() {
                
                @Override
                public String load(final String key) {
                    return getCar(key);
                }

            });

    
    private static String getCar(String key) {
        return "Lexus" + key;
    }

    
    private static Map getCars(List keys) {
        Map map = new HashMap<>();
        map.put("bmw", "BMW 530Li");
        map.put("benz", "E300L");
        map.put("audi", "Audi A6L");
        return map;
    }


    @Test
    public void secondTest() {

        try {
            // 从本地缓存读取数据
            String value = cache.get("es300h", new Callable() {
                @Override
                public String call() throws Exception {
                    System.out.println("Now invoking callable code...");
                    return "callable code...";
                }
            });

            System.out.println(value);  // 输出: callable code...

        } catch (ExecutionException e1) {
            e1.printStackTrace();
        }
    }



}

如何删除? 被动 基于数据大小的删除

当数据的个数多于 maximumSize所设置的值时候,会根据 LRU+FIFO 策略进行淘汰。

基于过期时间的删除

隔多长时间后没有被访问过的key被删除,时间标准参照 expireAfterAccess所设置的时间。

基于引用的删除

可以通过weakKeys和weakValues方法指定Cache只保存对缓存记录key和value的弱引用。这样当没有其他强引用指向key和value时,key和value对象就会被垃圾回收器回收。

主动 删除单条

cache.invalidate("bmw");

批量删除

cache.invalidateAll(Arrays.asList("bmw","benz"));

清空缓存

cache.invalidateAll();

总结

guava cache 的异步 reload 策略可以有效实现容错、节约调用耗时的目的,但有一个致命的缺陷:主线程返回的数据有可能是已过期的。
通常,我们对于缓存中数据的实际失效时间并不敏感,在这样的情况下,即使 guava cache 返回了已失效数据,也并不会造成任何业务问题,而由此带来的性能提升与容错的好处是显而易见的。

参考文档:

官方网址

官方使用案例

Google Guava Cache高效本地缓存

Guava Cache简介、应用场景分析、代码实现以及核心的原理

guava cache详细介绍

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1039797.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号