1、本地缓存GuavaCache的使用

本文深入探讨Guava缓存的使用与管理,包括缓存的必要性、Guava缓存工具的封装与使用,以及缓存回收策略,如基于容量、定时和基于引用的回收。同时介绍了缓存的显式清除机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习目标:

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可以帮助你很好地实现这样的定时调度。不过这种方式依然没办法百分百的确定一定是自己的维护线程“命中”了维护的工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值