赞
踩
之前在做项目时,需要在项目启动时将一些数据加载到内存使用,当时的做法是存在了一个map中,每个不同的缓存单独使用一个map,在使用时会在固定的时间间隔重新加载数据,这个时间的设置,是通过一个volatile类型的时间变量作为判断依据,每次在请求数据时会先判断时间是否超时,是则重新加载数据并更新这个变量。但是对这个变量是否需要设置volatile这个关键字很是纠结,通过发帖,查资料,了解了在并发修改变量时,应该要保证变量的安全性和可见性,所以使用该关键字。但是这样的做法始终觉得有问题,首先在并发请求时,会不会造成重复更新,还有在修改map时还会有请求数据的操作,这样也会造成很大的问题,这里有个严重的错误是在存在并发更新的情况下居然没有使用CouncurrentHashMap。基于以上种种问题,寻求一种良好的解决方案,于是学习了guava缓存的使用,通过spring的缓存支持,可以做到异步刷新,过期时间的设置等。在这里记录总结下guava的配置及使用方式。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>26.0-jre</version>
</dependency>
在学习时通过创建guava配置属性类来对缓存属性进行设置,没有通过spring提供的自动配置。
创建GuavaProperties类,通过该类接收属性配置值。
@ConfigurationProperties(prefix = "guava.cache.config") public class GuavaProperties { private long maximumSize; private long maximumWeight; private long expireAfterAccessDuration; private long expireAfterWriteDuration; private long refreshDuration; private int initialCapacity; private int concurrencyLevel; ....省略getter setter方法 }
同时希望通过cacheLoader进行数据的加载及在数据更新时使用线程池异步重新加载,这里创建GuavaCacheLoader类。
public class GuavaCacheLoader extends CacheLoader<String,String> { private final ExecutorService executorService = Executors.newFixedThreadPool(4); @Override public String load(String s) throws Exception { if (s.equals("hello")) { Thread.sleep(3000); return "world"; } else if (s.equals("world")) { Thread.sleep(5000); return "hello"; } return "no value"; } @Override public ListenableFuture<String> reload(String key, String oldValue) throws Exception { ListenableFutureTask<String> task = ListenableFutureTask.create(new Callable<String>() { @Override public String call() throws Exception { if (key.equals("hello")) { return "nihao"; } else if (key.equals("world")) { return "shijie"; } return "no value"; } }); executorService.submit(task); return task; } }
这里用线程睡眠来模拟真实加载数据时的耗时。在重新加载数据时对数据的返回值进行了变更。
创建配置类GuavaCacheConfig,通过创建CacheManager来管理不同的cache。
@EnableConfigurationProperties(GuavaProperties.class) @Configuration @EnableCaching public class GuavaCacheConfig { @Autowired private GuavaProperties guavaProperties; @Bean public CacheBuilder<Object,Object> cacheBuilder(){ long maximumSize = guavaProperties.getMaximumSize(); long expireAfterWrite = guavaProperties.getExpireAfterWriteDuration(); long expireAfterAccess = guavaProperties.getExpireAfterAccessDuration(); long refreshDuration = guavaProperties.getRefreshDuration(); if(maximumSize <= 0){ maximumSize = 1024; } if(expireAfterAccess <= 0){ expireAfterAccess = 3600; } if(expireAfterWrite <= 0){ expireAfterWrite = 3600; } if(refreshDuration <= 0){ refreshDuration = 1800; } return CacheBuilder.newBuilder().maximumSize(maximumSize) .expireAfterWrite(expireAfterWrite,TimeUnit.SECONDS) .refreshAfterWrite(refreshDuration,TimeUnit.SECONDS); } @Bean(name = "guavaCacheLoader") public CacheLoader cacheLoader(){ return new GuavaCacheLoader(); } @Bean public CacheManager cacheManager(@Qualifier("cacheBuilder")CacheBuilder cacheBuilder, @Qualifier("guavaCacheLoader")CacheLoader cacheLoader){ GuavaCacheManager cacheManager = new GuavaCacheManager(); cacheManager.setCacheBuilder(cacheBuilder); cacheManager.setCacheLoader(cacheLoader); return cacheManager; } }
在application.propertiesp配置文件设置配置属性值,这里为了演示,去掉了最后操作数据过期的时长设置。
设置的属性为写入后10s数据过期进行刷新,20s后数据过期重新写入
guava.cache.config.expire-after-write-duration=20
#更新间隔时长
guava.cache.config.refresh-duration=10
guava.cache.config.maximumSize=1024
编写测试controller
@RestController public class HelloController { @Autowired private CacheManager cacheManager; @RequestMapping("hello") public String hello(){ long start = System.currentTimeMillis(); String value = cacheManager.getCache("hello").get("hello").get().toString(); long end = System.currentTimeMillis(); return String.format("value=[%s], wait time : [%d]",value,(end - start)); } @RequestMapping("world") public String world(){ long start = System.currentTimeMillis(); String value = cacheManager.getCache("world").get("world").get().toString(); long end = System.currentTimeMillis(); return String.format("value=[%s], wait time : [%d]",value,(end - start)); } }
初次请求
再次请求
过期刷新
到这里Guava缓存的使用学习总结已经结束了,这里我只根据之前的项目需要来使用guava,有很多特性,如缓存注释的使用在这里并没有体现。需要的同学可以查询资料进行学习。
在下篇博文中,会针对springboot2所支持的缓存caffeine进行总结学习。spring5后spring官方放弃了guava而使用了更优秀的caffeine缓存,同时spring的缓存支持也有较大的变动,在下篇博文中将会利用caffeine来学习缓存的使用。
参考资料:
http://ifeve.com/google-guava-cachesexplained/
https://blog.csdn.net/a67474506/article/details/52608855
源码地址:
https://github.com/Edenwds/springboot_study/tree/master/guava
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。