1、本地缓存GuavaCache的使用

学习目标:

1、了解缓存的作用

2、了解Guava对缓存的操作方法

学习过程:

一、为什么需要缓存

    缓存需要消耗一些内存空间达到提升速度的功能。如果一些数据需要多次的访问,缓存起来效率会高很多,但是也要注意所以如果一条数据不需要多次的访问,也就没有缓存起来,因为这样会消耗内容,还有就是缓存存放的数据总量不会超出内存容量,如果大量占用了内存也会导致系统变慢的。

   还需要考虑的就是缓存放到哪里?一般可以分为本地缓存和分布式缓存两种方式。本地缓存就只能在本地访问,在需要缓存量不是很大的时候,同时对缓存的数据也不是很重要的情况下,本地缓存操作方便,但是如果缓存量很大,为了提供内存的利用率,提高缓存的稳定性,可以采用分布式缓存的方案。

   本地缓存,如果是web服务呢,我们经常使用的session,session当然也是一种非常好用的缓存,但是只针对某个用户的,今天我们将会学习google的Guava的包封装的一个缓存工具类。

二、Guava的缓存封装工具

平时我们查询数据库时,并没有把数据缓存起来,如果需要缓存,我们一般的代码思路

(1)、先判断缓存是否有此数据

(2)、如果有直接从缓存取值

(3)、如果没有就查询数据,并把数据放到缓存中

这样模板式的处理比较死板,Guava帮我们封装一下,就不需要写得这么复杂了。还需要考虑的就是缓存时效、最大限制、缓存的清空等。Guava比较都封装得比较好了。

1、基本使用

导入包

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

使用代码:

@Service
public class GoodService {
    @Autowired
    private GoodsDao goodsDao;
    private final static String PREFIX="good:";
    LoadingCache<String, Goods> goodsCache = CacheBuilder.newBuilder().maximumSize(1000)
            .expireAfterAccess(20, TimeUnit.SECONDS).build(new CacheLoader<String, Goods>() {
                @Override
                public Goods load(String goodId) throws Exception {
                    Goods good = goodsDao.queryByid(Integer.valueOf(goodId.split(":")[1]));
                    if (good == null) {
                        good = new Goods(); 
                    }
                    return good;
                }
            });

    public Goods getGoodCache(int goodId) {
        Goods goods=null;
        try {
            goods= goodsCache.get(PREFIX+goodId);
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return goods;
    }

    /**
     * 手动清除
     * @param goodId
     * @return
     */
    public void clearGoodCache(int goodId) {
        goodsCache.invalidate(PREFIX+goodId);
    }

    public void clearAllGoodCache() {
        goodsCache.invalidateAll();
    }
}

测试代码,你可以再中间清空缓存得操作注释和不注释得时候得区别。

@RunWith(SpringJUnit4ClassRunner.class) // 使用junit4进行测试
@ContextConfiguration(locations = { "classpath:applicationContext.xml" }) // 加载配置文件
public class GoodServiceTest {

    @Autowired
    private GoodService goodService;
    @Test
    public void testGetUserCache() {
        Goods good=goodService.getGoodCache(9);
        //goodService.clearGoodCache(good.getGoodsId());
        Goods good1=goodService.getGoodCache(9);
        System.out.println(good.getGoodsName());
        System.out.println(good1.getGoodsName());
    }
}

 

三、缓存回收

  什么时候某个缓存项就不值得保留了?Guava Cache提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。

1、基于容量的回收(size-based eviction)

    如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。缓存将尝试回收最近没有使用或总体上很少使用的缓存项。——警告:在缓存项的数目达到限定值之前,缓存就可能进行回收操作——通常来说,这种情况发生在缓存项的数目逼近限定值时。

   另外,不同的缓存项有不同的“权重”(weights)——例如,如果你的缓存值,占据完全不同的内存空间,你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。在权重限定场景中,除了要注意回收也是在重量逼近限定值时就进行了,还要知道重量是在缓存创建时计算的,因此要考虑重量计算的复杂度。比如我可以设置价钱越高权重越高。

2、定时回收(Timed Eviction)

    CacheBuilder提供两种定时回收的方法:

    expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。

    expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。

定时回收周期性地在写操作中执行,偶尔在读操作中执行。代码修改如下:

LoadingCache<String, Goods> goodsCache = CacheBuilder.newBuilder().maximumSize(1000)
        .expireAfterAccess(20, TimeUnit.SECONDS).weigher(new Weigher<String, Goods>() {
            public int weigh(String key, Goods value) {
                return ((Double)value.getGoodsCash()).intValue();
            }
        }).build(new CacheLoader<String, Goods>() {
            @Override
            public Goods load(String goodId) throws Exception {
                Goods good = goodsDao.queryByid(Integer.valueOf(goodId.split(":")[1]));
                if (good == null) {
                    good = new Goods();
                }
                return good;
            }
        });

public Goods getGoodCache(int goodId) {
    Goods goods = null;
    try {
        goods = goodsCache.get(PREFIX + goodId);
    } catch (ExecutionException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return goods;
}

 

3、基于引用的回收(Reference-based Eviction)

    通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:

    CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用键的缓存用==而不是equals比较键。

    CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用值的缓存用==而不是equals比较值。

    CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是equals比较值。

4、显式清除

    任何时候,你都可以显式地清除缓存项,而不是等到它被回收:

    个别清除:Cache.invalidate(key) 

    批量清除:Cache.invalidateAll(keys) 

    清除所有缓存项:Cache.invalidateAll()

四、清除什么时候发生

    也许这个问题有点奇怪,如果设置的存活时间为一分钟,难道不是一分钟后这个key就会立即清除掉吗?我们来分析一下如果要实现这个功能,那Cache中就必须存在线程来进行周期性地检查、清除等工作,很多cache如redis、ehcache都是这样实现的。

    但在GuavaCache中,并不存在任何线程!它实现机制是在写操作时顺带做少量的维护工作(如清除),偶尔在读操作时做(如果写操作实在太少的话),也就是说在使用的是调用线程,参考如下示例:

    这在GuavaCache被称为“延迟删除”,即删除总是发生得比较“晚”,这也是GuavaCache不同于其他Cache的地方!这种实现方式的问题:缓存会可能会存活比较长的时间,一直占用着内存。如果使用了复杂的清除策略如基于容量的清除,还可能会占用着线程而导致响应时间变长。但优点也是显而易见的,没有启动线程,不管是实现,还是使用起来都让人觉得简单(轻量)。

    如果你还是希望尽可能的降低延迟,可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp(),ScheduledExecutorService可以帮助你很好地实现这样的定时调度。不过这种方式依然没办法百分百的确定一定是自己的维护线程“命中”了维护的工作。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Guava Cache是Google Guava库中提供的一种本地缓存解决方案。它是一个基于内存的缓存,可以在应用程序内部存储数据,提高应用程序性能。 Guava Cache提供了以下特性: 1. 自动加载:当缓存中不存在某个键的值时,可以自动加载生成该值。 2. 自动移除:缓存中的某些条目可以在一定时间内自动过期,或者可以使用大小限制来限制缓存中的条目数。 3. 针对不同的缓存数据设置不同的过期时间、存活时间、最大值、最小值等。 4. 支持同步和异步缓存使用Guava Cache非常简单,只需要按以下步骤操作: 1. 引入Guava库。 2. 创建一个CacheBuilder对象,用于配置缓存。 3. 调用build()方法创建一个Cache对象。 4. 使用put()方法向缓存中添加数据。 5. 使用get()方法从缓存中读取数据,如果缓存中不存在该键对应的值,则可以自动加载。 6. 使用invalidate()方法从缓存中移除数据。 下面是一个简单的示例: ```java import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class GuavaCacheExample { public static void main(String[] args) throws ExecutionException { // 创建一个CacheBuilder对象 CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder() .maximumSize(100) // 设置缓存最大条目数 .expireAfterWrite(10, TimeUnit.MINUTES); // 设置缓存过期时间 // 创建一个Cache对象 LoadingCache<String, String> cache = cacheBuilder.build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { System.out.println("loading " + key); // 自动加载数据 return "value-" + key; } }); // 添加数据到缓存cache.put("key1", "value1"); cache.put("key2", "value2"); // 从缓存中读取数据 System.out.println(cache.get("key1")); // 输出"value1" System.out.println(cache.get("key3")); // 输出"loading key3"和"value-key3" // 移除缓存中的数据 cache.invalidate("key1"); System.out.println(cache.get("key1", () -> "default")); // 输出"default" } } ``` 在这个示例中,我们使用CacheBuilder对象配置了缓存的最大条目数和过期时间。我们还使用CacheLoader对象创建了一个自动加载的缓存,当缓存中不存在某个键的值时,可以自动加载生成该值。我们使用put()方法向缓存中添加了两个数据,使用get()方法从缓存中读取了两个数据,并使用invalidate()方法从缓存中移除了一个数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值