数据回源与回填策略
前面我们介绍过,Guava Cache提供的是一种穿透型缓存模式,当缓存中没有获取到对应记录的时候,支持自动去源端获取数据并回填到缓存中。这里回源获取数据的策略有两种,即CacheLoader方式与Callable方式,两种方式适用于不同的场景,实际使用中可以按需选择。
下面一起看下这两种方式。
CacheLoader
CacheLoader适用于数据加载方式比较固定且统一的场景,在缓存容器创建的时候就需要指定此具体的加载逻辑。常见的使用方式如下:
public LoadingCache<String, User> createUserCache() {
return CacheBuilder.newBuilder()
.build(new CacheLoader<String, User>() {
@Override
public User load(String key) throws Exception {
System.out.println(key + "用户缓存不存在,尝试CacheLoader回源查找并回填...");
return userDao.getUser(key);
}
});
}
上述代码中,在使用CacheBuilder创建缓存容器的时候,如果在build()方法中传入一个CacheLoader实现类的方式,则最终创建出来的是一个LoadingCache具体类型的Cache容器:
默认情况下,我们需要继承CacheLoader类并实现其load抽象方法即可。
当然,CacheLoader类中还有一些其它的方法,我们也可以选择性的进行覆写来实现自己的自定义诉求。比如我们需要设定refreshAfterWrite来支持定时刷新的时候,就推荐覆写reload方法,提供一个异步数据加载能力,避免数据刷新操作对业务请求造成阻塞。
另外,有一点需要注意下,如果创建缓存的时候使用refreshAfterWrite指定了需要定时更新缓存数据内容,则必须在创建的时候指定CacheLoader实例,否则执行的时候会报错。因为在执行refresh操作的时候,必须调用CacheLoader对象的reload方法去执行数据的回源操作。
Callable
与CacheLoader不同,Callable的方式在每次数据获取请求中进行指定,可以在不同的调用场景中,分别指定并使用不同的数据获取策略,更加的灵活。
public static void main(String[] args) {
try {
GuavaCacheService cacheService = new GuavaCacheService();
Cache<String, User> cache = cacheService.createCache();
String userId = "123";
// 获取userId, 获取不到的时候执行Callable进行回源
User user = cache.get(userId, () -> cacheService.queryUserFromDb(userId));
System.out.println("get对应用户信息:" + user);
} catch (Exception e) {
e.printStackTrace();
}
}
通过提供Callable实现函数并作为参数传递的方式,可以根据业务的需要,在不同业务调用场景下,指定使用不同的Callable回源策略。
回源查询
前面介绍了基于CacheLoader方式自动回源,或者基于Callable方式显式回源的两种策略。但是实际使用缓存的时候,并非是缓存中获取不到数据时就一定需要去执行回源操作。
比如下面这个场景:
用户论坛中维护了个黑名单列表,每次用户登录的时候,需要先判断下是否在黑名单中,如果在则禁止登录。
因为论坛中黑名单用户占整体用户量的比重是极少的,也就是几乎绝大部分登录的时候去查询缓存都是无法命中黑名单缓存的。这种时候如果每次查询缓存中没有的情况下都去执行回源操作,那几乎等同于每次请求都需要去访问一次DB了,这显然是不合理的。
所以,为了支持这种场景的访问,Guava cache也提供了一种不会触发回源操作的访问方式。如下: