guava实现多级缓存(1)

1. 为何要使用多级缓存

日常工作中,时常要面对抽奖活动,奖励发放,商品秒杀等大流量高并发的场景。
高并发场景面对的第一个问题是DB的IO瓶颈。 这时比较通用缓存方式是加一层redis,用内存的性能来解决IO的瓶颈。
但是引入Redis就一劳永逸了嘛?不是的,相对应的高并发场景又会引发Redis热KEY和大KEY的问题,造成带宽拉胯。
这时可以在做一层本地缓存。那么问题又来了,本地缓存没有过期时间啊,因此就可以用GUAVA CACHE来解决,本质上他就是封装了一个过期时间,至于过期策略,一般有三种,定时删除,定期删除,惰性删除。guava本地缓存就是采用惰性删除的方式就很合理

2.GuavaCache简介

GuavaCache是google开源java类库Guava的其中一个模块,在maven工程下使用可在pom文件加入如下依赖:

<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>19.0</version>
        </dependency>
  • Cache接口及其实现

构建一个key为Integer、value为String的Cache实例:

	final static Cache<Integer, String> cache = CacheBuilder.newBuilder()
			//设置cache的初始大小为10,要合理设置该值
			.initialCapacity(10)
			//设置并发数为5,即同一时间最多只能有5个线程往cache执行写入操作
			.concurrencyLevel(5)
			//设置cache中的数据在写入之后的存活时间为10秒
			.expireAfterWrite(10, TimeUnit.SECONDS)
			//构建cache实例
			.build();

据说GuavaCache的实现是基于ConcurrentHashMap的,因此上面的构造过程所调用的方法,通过查看其官方文档也能看到一些类似的原理。比如通过initialCapacity(5)定义初始值大小,要是定义太大就好浪费内存空间,要是太小,需要扩容的时候就会像map一样需要resize,这个过程会产生大量需要gc的对象,还有比如通过concurrencyLevel(5)来限制写入操作的并发数,这和ConcurrentHashMap的锁机制也是类似的(ConcurrentHashMap读不需要加锁,写入需要加锁,每个segment都有一个锁)。

  • 常用方法
/**
 * 该接口的实现被认为是线程安全的,即可在多线程中调用
 * 通过被定义单例使用
 */
public interface Cache<K, V> {
 
  /**
   * 通过key获取缓存中的value,若不存在直接返回null
   */
  V getIfPresent(Object key);
 
  /**
   * 通过key获取缓存中的value,若不存在就通过valueLoader来加载该value
   * 整个过程为 "if cached, return; otherwise create, cache and return"
   * 注意valueLoader要么返回非null值,要么抛出异常,绝对不能返回null
   */
  V get(K key, Callable<? extends V> valueLoader) throws ExecutionException;
 
  /**
   * 添加缓存,若key存在,就覆盖旧值
   */
  void put(K key, V value);
 
  /**
   * 删除该key关联的缓存
   */
  void invalidate(Object key);
 
  /**
   * 删除所有缓存
   */
  void invalidateAll();
 
  /**
   * 执行一些维护操作,包括清理缓存
   */
  void cleanUp();
}
  • 过期策略
    • 基于存活时间的清除(Timed Eviction)
      这应该是最常用的清除策略,在构建Cache实例的时候,CacheBuilder提供两种基于存活时间的构建方法:
      (1)expireAfterAccess(long, TimeUnit):缓存项在创建后,在给定时间内没有被读/写访问,则清除。
      (2)expireAfterWrite(long, TimeUnit):缓存项在创建后,在给定时间内没有被写访问(创建或覆盖),则清除。
      expireAfterWrite()方法有些类似于redis中的expire命令,但显然它只能设置所有缓存都具有相同的存活时间。若遇到一些缓存数据的存活时间为1分钟,一些为5分钟,那只能构建两个Cache实例了。

    • 基于容量的清除(size-based eviction)
      在构建Cache实例的时候,通过CacheBuilder.maximumSize(long)方法可以设置Cache的最大容量数,当缓存数量达到或接近该最大值时,Cache将清除掉那些最近最少使用的缓存。
      以上是这种方式是以缓存的“数量”作为容量的计算方式,还有另外一种基于“权重”的计算方式。比如每一项缓存所占据的内存空间大小都不一样,可以看作它们有不同的“权重”(weights)。你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。

    • 显式清除
      任何时候,你都可以显式地清除缓存项,而不是等到它被回收,Cache接口提供了如下API:
      (1)个别清除:Cache.invalidate(key)
      (2)批量清除:Cache.invalidateAll(keys)
      (3)清除所有缓存项:Cache.invalidateAll()

    • 基于引用的清除(Reference-based Eviction)
      在构建Cache实例过程中,通过设置使用弱引用的键、或弱引用的值、或软引用的值,从而使JVM在GC时顺带实现缓存的清除,不过一般不轻易使用这个特性。
      (1)CacheBuilder.weakKeys():使用弱引用存储键
      (2)CacheBuilder.weakValues():使用弱引用存储值
      (3)CacheBuilder.softValues():使用软引用存储值

3.实践

初始化缓存

@Service
public class CacheServiceImpl implements CacheService {
 
    private Cache<String, Object> commonCache = null;
 
    @PostConstruct
    public void init() {
        commonCache = CacheBuilder.newBuilder()
                .initialCapacity(10)
                .maximumSize(100)
                .expireAfterWrite(60, TimeUnit.SECONDS).build();
    }
 
    @Override
    public void setCommonCache(String key, Object value) {
        commonCache.put(key, value);
    }
 
    @Override
    public Object getFromCommonCache(String key) {
        return commonCache.getIfPresent(key);
    }
 
}

使用

ItemModel itemModel = null;
 
    // 先从本地缓存中找
    itemModel = (ItemModel)cacheService.getFromCommonCache("item_" + id);
    if (itemModel == null) {
        // 再从 Redis 中找
        itemModel = (ItemModel)redisTemplate.opsForValue().get("item_" + id);
        if (itemModel == null) {
            // 最后从 MySQL 中找
            itemModel = itemService.getItemById(id);
            redisTemplate.opsForValue().set("item_" + id, itemModel);
            redisTemplate.expire("item_" + id, 10, TimeUnit.MINUTES);
        }
        cacheService.setCommonCache("item_" + id, itemModel);
    }
 
    ItemVO itemVO = this.convertFromItemModel(itemModel);

参考:
https://www.cnblogs.com/xxmyz/p/10119230.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值