当前位置:   article > 正文

Java程序中的常见的四种缓存类型及代码实现_cache java类型

cache java类型

 在Java程序中,有的时候需要根据不同的场景来使用不同的缓存类型。在Java中主要分别有堆缓存、堆外缓存、磁盘缓存、分布式缓存等。

堆缓存

 使用Java堆内存来存储缓存对象。使用堆缓存的好处是没有序列化/反序列化,是最快的缓存。缺点也很明显,当缓存的数据量很大时,GC(垃圾回收)暂停时间会变长,存储容量受限于堆空间大小。一般通过软引用/弱引用来存储缓存对象,即当堆内存不足时,可以强制回收这部分内存释放堆内存空间。一般使用堆缓存存储较热的数据。可以使用Guava Cache、Ehcache 3.x、MapDB实现。

1.Gauva Cache实现

  1. Cache<String,String> cache = CacheBuilder.newBuilder()
  2. .concurrencyLevel(4)
  3. .expireAfterWrite(10,TimeUnit.SECONDS)
  4. .maximumSize(10000)
  5. .build();

然后可以通过put、getIfPresent来读写缓存。CacheBuilder有几类参数:缓存回收策略、并发设置、统计命中率等。

maximumSize 设置缓存的容量,当超出maximumSize时,按照LRU进行缓存回收。

expireAfterWrite 设置TTL,缓存数据在给定的时间内没有写(创建/覆盖)时,则被回收,即定期会回收缓存数据。

expireAfterAccess 设置TTI,缓存数据在给定的时间内没有被读/写时,则被回收。每次访问时,都会更新它的TTI,从而如果该缓存是非常热的数据,则将一直不过期,可能会导致脏数据存在很长时间(因此,建议设置expireAfterWrite)。

weakKeys/weakValues 设置弱引用缓存。

softValues 设置软引用缓存。

invalidate(Object key)/ invalidateAll(Iterable<?> keys)/invalidateAll() 主动失效某些缓存数据。

什么时候触发失效呢?Guava Cache不会在缓存数据失效时立即触发回收操作,而在PUT时会主动进行一次缓存清理,当然读者也可以根据实际业务通过自己设计线程来调用cleanUp方法进行清理。

concurrencyLevel Guava Cache重写了ConcurrentHashMap,concurrencyLevel用来设置Segment数量,concurrencyLevel越大并发能力越强。

recordStats: 启动记录统计信息,比如命中率等。

2.Ehcache 3.x实现

  1. CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true);
  2. CacheConfigurationBuilder<String, String> cacheConfig = CacheConfigurationBuilder.newCacheConfigurationBuilder(
  3. String.class,String.class,
  4. ResourcePoolsBuilder.newResourcePoolsBuilder().heap(100,EntryUnit.ENTRIES))
  5. .withDispatcherConcurrency(4)
  6. .withExpiry(Expirations.timeToLiveExpiration(Duration.of(10,TimeUnit.SECONDS)));
  7. Cache<String,String> cache = cacheManager.createCache("cache",concheConfig);

CacheManager在JVM关闭时调用CacheManager.close()方法,可以通过PUT、GET来读写缓存。CacheConfigurationBuilder 也有几类参数:缓存回收策略、并发设置、统计命中率等。

heap(100, EntryUnit.ENTRIES) :设置缓存的条目数量,当超出此数量时按照LRU进行缓存回收。

heap(100, MemoryUnit.MB) 设置缓存的内存空间,当超出此空间时按照LRU进行缓存回收。另外,应该设置withSizeOfMaxObjectGraph(2)统计对象大小时对象图遍历深度和withSizeOfMaxObjectSize(1, MemoryUnit.KB )可缓存的最大对象大小。

withExpiry(Expirations. timeToLiveExpiration (Duration.of (10, TimeUnit. SECONDS ))) 设置TTL,没有TTI。

withExpiry(Expirations. timeToIdleExpiration (Duration.of (10, TimeUnit. SECONDS ))) 同时设置TTL和TTI,且TTL和TTI值一样。

remove(K key)/ removeAll(Set<? extends K> keys)/clear() 主动失效某些缓存数据。

什么时候触发失效呢?Ehcache使用了类似于Guava Cache的机制。

withDispatcherConcurrency:是用来设置事件分发时的并发级别。

 

3.MapDB 3.x实现

  1. HTreeMap cache = DBMark.heapDB()
  2. .concurrencyScale(16)
  3. .make()
  4. .hashMap("cache")
  5. .expireMaxSize(1000)
  6. .expireAfterCreate(10,TimeUnit.SECONDS)
  7. .expireAfterUpdate(10,TimeUnit.SECONDS)
  8. .expireAfterGet(10,TimeUnit.SECONDS)
  9. .create();

然后可以通过PUT、GET来读写缓存。其有几类参数:缓存回收策略、并发设置、统计命中率等。

expireMaxSize 设置缓存的容量,当超出expireMaxSize时,按照LRU进行缓存回收。

expireAfterCreate/expireAfterUpdate 设置TTL,缓存数据在给定的时间内没有写(创建/覆盖)时,则被回收,即定期地会回收缓存数据。

expireAfterGet 设置TTI,缓存数据在给定的时间内没有被读/写时,则被回收。每次访问时都会更新它的TTI,从而如果该缓存是非常热的数据,则将一直不过期,可能会导致脏数据存在很长的时间(因此,建议要设置expireAfterCreate/expireAfterUpdate)。

remove(Object key) /clear() 主动失效某些缓存数据。

什么时候触发失效呢?MapDB默认使用类似于Guava Cache的机制。不过,也支持通过如下配置使用线程池定期进行缓存失效。

.expireExecutor(scheduledExecutorService )

.expireExecutorPeriod(3000)

concurrencyScale 类似于Guava Cache的配置。

堆外缓存

即缓存数据存储在堆外内存,可以减少GC暂停时间(堆对象转移到堆外,GC扫描和移动的对象变少了),可以支持更大的缓存空间(只受机器内存大小限制,不受堆空间的影响)。但是,读取数据时需要序列化/反序列化,因此会比堆缓存慢很多。可以使用Ehcache 3.x、MapDB实现。

1.EhCache 3.x实现

  1. CacheConfigurationBuilder<String, String> cacheConfig = CacheConfigurationBuilder.newCacheConfigurationBuilder(
  2. String.class,String.class,ResourcePoolsBuilder.newResourcePoolsBuilder().offheap(100,MemoryUnit.MB))
  3. .withDispatcherConcurrency(4)
  4. .withExpiry(Expirations.timeToLiveExpiration(Duration.of(10,TimeUnit.SECONDS)))
  5. .withSizeOfMaxObjectGraph(3
  6. .withSizeOfMaxObjectSize(1,MemoryUnit.KB);

堆外缓存不支持基于容量的缓存过期策略。

2.MapDB 3.x实现

  1. HTreeMap cache = DBMark.memoryDirectDB().concurrencyScale(16).make().hashMap("cache")
  2. .expireStoreSize(64*1024*1024)
  3. .expireMaxSize(1000)
  4. .expireAfterCreate(10,TimeUnit.SECONDS)
  5. .expireAfterUpdate(10,TimeUnit.SECONDS)
  6. .expireAfterGet(10,TimeUnit.SECONDS)
  7. .create();

在使用堆外缓存时,请记得添加JVM启动参数,如-XX:MaxDirectMemorySize=6G。

磁盘缓存

 即缓存数据存储在磁盘上,在JVM重启时数据还是存在的,而堆缓存/堆外缓存数据会丢失,需要重新加载。可以使用Ehcache 3.x、MapDB实现。

1.EhCache 3.x实现

  1. CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
  2. .using(PoolExecutionServiceConfigurationBuilder
  3. .newPooledExecutionServiceCoonfigurationBuilder()
  4. .defaultPool("default",1,10)
  5. .build())
  6. .with(new CacheManagerPersistenceConfiguration(new File("home\back")))
  7. .build(true);
  8. CacheConfigurationBuilder<String,String> cacheConfig =
  9. CacheConfigurationBuilder.newCacheConfigurationBuilder(
  10. String.class,
  11. String.calss,
  12. ResourcePoolsBuilder.newResourcePoolsBuilder()
  13. .disk(100,MemoryUnit.MB,true))
  14. .withDiskStoreThreadPool("default",5)
  15. .withExpiry(Expirations.timeToLiveExpiration(Duration.of(30,TimeUnit.SECONDS)))
  16. .withSizeOfMaxObjectGraph(3
  17. .withSizeOfMaxObjectSize(1,MemoryUnit.KB);

在JVM停止时,记得调用cacheManager .close(),从而保证内存数据能dump到磁盘。

2.MapDB 3.x实现

  1. DB db = DBMark.fileDB("home\back\db.data")
  2. //启用mmap
  3. .fileMmapEnable()
  4. .fileMmapEnableIfSupported()
  5. .fileMmapPreclearDisable()
  6. .cleanerHackEnable()
  7. //启用事务
  8. .transactionEnable() 
  9. .closeOnJvmShutdown()
  10. .concurrencyScale(16)
  11. .make();
  12. HTreeMap cache = db.hashMap("cache")
  13. .expireMaxSize(1000)
  14. .expireAfterCreate(10,TimeUnit.SECONDS)
  15. .expireAfterUpdate(10,TimeUnit.SECONDS)
  16. .expireAfterGet(10,TimeUnit.SECONDS)
  17. .createOrOpen();

因为开启了事务,MapDB则开启了WAL。另外,操作完缓存后记得调用db.commit方法提交事务。

  1. cache.put("key" + counterWriter, "value" + counterWriter);
  2. db .commit();

分布式缓存: 

上文提到的缓存是进程内缓存和磁盘缓存,在多JVM实例的情况下,会存在两个问题:1.单机容量问题;2.数据一致性问题(多台JVM实例的缓存数据不一致怎么办?),不过,这个问题不用太纠结,既然数据允许缓存,则表示允许一定时间内的不一致,因此可以设置缓存数据的过期时间来定期更新数据;3.缓存不命中时,需要回源到DB/服务请求多变问题:每个实例在缓存不命中的情况下都会回源到DB加载数据,因此,多实例后DB整体的访问量就变多了,解决办法是可以使用如一致性哈希分片算法。因此,这些情况可以考虑使用分布式缓存来解决。可以使用ehcache-clustered(配合Terracotta server)实现Java进程间分布式缓存。当然也可以使用如Redis实现分布式缓存。

两种模式如下。

· 单机时: 存储最热的数据到堆缓存,相对热的数据到堆外缓存,不热的数据到磁盘缓存。

· 集群时: 存储最热的数据到堆缓存,相对热的数据到堆外缓存,全量数据到分布式缓存。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/392422
推荐阅读
相关标签
  

闽ICP备14008679号