目录
- 缓存设计需要考虑的地方
- 项目代码编写
- mybaits缓存设计原理
- guava缓存设计原理
本地缓存设计需要考虑的地方
- 数据结构使用
- 对象上限
- 清除策略
- 过期时间
- 线程安全
- 是否持久化
本地缓存项目代码编写
- 提供公共的泛型抽象类
public interface Cache<K, V> {
V get(K key);
Map<K, V> getAll(List<K> keys);
void put(K key, V value);
void refresh(K key);
void refreshAll(List<K> keys);
}
public abstract class WrapLoadingCache<K, V> implements Cache<K, V>{
private static final Logger log = LoggerFactory.getLogger(WrapLoadingCache.class);
protected LoadingCache<K, Optional<V>> loaderCache;
protected abstract Optional<V> load(K key);
protected Map<K, Optional<V>> loadAll(List<K> keys){
throw new UnsupportedOperationException();
}
protected void init(int time, TimeUnit unit, int maxSize) {
loaderCache = CacheBuilder.newBuilder()
.expireAfterWrite(time, unit)
.maximumSize(maxSize)
.build(new CacheLoader<K, Optional<V>>() {
@Override
public Optional<V> load(K key) {
return LoaderCache.this.load(key);
}
@Override
public Map<K, Optional<V>> loadAll(Iterable<? extends K> keys) throws Exception {
return LoaderCache.this.loadAll(Lists.newArrayList(keys));
}
});
}
@Override
public V get(K key) {
Optional<V> opt = null;
try {
opt = loaderCache.get(key);
} catch (ExecutionException e) {
log.warn("WrapLoadingCacheget throw exception", e);
}
return opt != null && opt.isPresent() ? opt.get() : null;
}
@Override
public Map<K, V> getAll(List<K> keys) {
try {
Map<K, V> result = new HashMap<>();
ImmutableMap<K, Optional<V>> map = loaderCache.getAll(keys);
map.forEach((key, opt) -> {
V v = opt != null && opt.isPresent() ? opt.get() : null;
result.put(key, v);
});
return result;
} catch (ExecutionException e) {
log.warn("WrapLoadingCache get throw exception", e);
}
return null;
}
@Override
public void put(K key, V value) {
loaderCache.put(key, Optional.ofNullable(value));
}
@Override
public void refresh(K key) {
loaderCache.refresh(key);
}
@Override
public void refreshAll(List<K> keys) {
loaderCache.putAll(loadAll(keys));
}
}
- 各个功能如果要使用则,继承WrapLoadingCache,并实现InitializingBean以便spring启动时调用init方法,init方法可以传入时间之类的过期设置参数。
- load方法各自子类自个设计, 本地缓存后可跟redis缓存redis总结中redis实践有需要注意的redis缓存实践参考。
mybaits缓存设计原理
缓存图
-
其中Cache是接口,定义了一些公共方法,PerpetualCache是一级缓存,LruCache,SynchronizedCache, BlockingCache等类都是二级缓存,采用装饰器模式,装饰一级缓存。一级缓存是SqlSession级别的,二级缓存是Mapping级别的存在线程安全问题,当然mybatis提供线程安全的Cache,但是mybatis处理二级缓存虽然解决了线程安全问题,并没有提供RR的隔离级别,只提供RC。
LruCache
- LruCache 的 keyMap 属性是实现 LRU 策略的关键,该属性类型继承自 LinkedHashMap,并覆盖了 removeEldestEntry 方法。LinkedHashMap 内部的 tail 节点会指向最新插入的节点。head 节点则指向第一个被插入的键值对,也就是最久未被访问的那个键值对。默认情况下,LinkedHashMap 仅维护键值对的插入顺序。LinkedHashMap具体实现Lru细节可参考LinkedHashMap 源码详细分析
BlockingCache
- getObject 方法会先获取与 key 对应的锁,并加锁。若缓存命中,getObject 方法会释放锁,否则将一直锁定。getObject 方法若返回 null,表示缓存未命中。
- 使用分段锁的思想,一个key对应一个ReentrantLock
private final ConcurrentHashMap<Object, ReentrantLock> locks;
mybatis二级缓存导致的不可重复读问题
- 可参考MyBatis 源码分析 - 缓存原理5.二级缓存
guava缓存设计原理
- 并发方面的实现类似ConcurrentHashMap1.7的分段锁(分段Segment),LoadingCache写法的实现在get时无数据会自动调用load方法。LoadingCache支持自动刷新。具体细节可参考Guava LocalCache 缓存介绍及实现源码深入剖析
// CacheLoader形式的Cache
private static final LoadingCache<String, String> LOADER_CACHE = CacheBuilder.newBuilder()
.expireAfterAccess(1, TimeUnit.SECONDS)
.maximumSize(1000)
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return key + new Date();
}
});