手撕 MyBatis 可插拔式缓存设计(拓展)
1 缓存基础
1.1 是什么?
缓存就是内存中的一个对象,可以基于此对象存储其它对象。
1.2 为什么要应用缓存?
降低数据库的访问压力,提高数据的查询效率,改善用户体验。
1.3 你了解哪些缓存?
-
数据库内置的缓存?(例如 mysql 的查询缓存)
-
数据层缓存(一般由持久层框架提供,例如 MyBatis)
-
业务层缓存(基于 map 等实现的本缓存,分布式缓存-例如 redis)
-
浏览器内置缓存?
-
CPU 缓存(高速缓冲区)
1.4 设计缓存时你应该考虑哪些问题?
-
存储结构(使用什么结构存储数据效率会更高?-散列表)
-
淘汰策略(缓存容量有限-LRU/FIFO/LFU,软应用、弱引用)
-
任务调度(定期刷新缓存,缓存失效时间)
-
并发安全(缓存并发访问时的线程安全)
-
日志记录(缓存是否命中,命中率是多少)
-
序列化(存对象时序列化、取对象时反序列化)
2 本地缓存设计
2.1 缓存接口设计
基于接口定义规范
package com.java.cache;
public interface Cache {
void putObject(Object key,Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int size();
}
2.2 简易 Cache 设计
基于散列表存储对象,其设计如下:
package com.java.cache;
import java.util.HashMap;
import java.util.Map;
public class PerpetualCache implements Cache{
/**
* 特点:线程不安全,key 不允许重复,不能保证 key 的顺序
*/
private Map<Object,Object> cache=new HashMap<>();
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public int size() {
return cache.size();
}
@Override
public String toString() {
return cache.toString();
}
public static void main(String[] args) {
Cache cache=new PerpetualCache();
cache.putObject("A", 100);
cache.putObject("B", 200);
cache.putObject("C", 300);
System.out.println(cache);
cache.removeObject("D");
cache.clear();
System.out.println(cache.size());
}
}
2.3 线程安全的 Cache 设计
对 Cache 进行线程安全方面的扩展实现,例如:
package com.java.cache;
public class SynchronizedCache implements Cache{
private Cache cache;
public SynchronizedCache(Cache cache) {
this.cache=cache;
}
@Override
public synchronized void putObject(Object key, Object value) {
cache.putObject(key, value);
}
@Override
public synchronized Object getObject(Object key) {
// TODO Auto-generated method stub
return cache.getObject(key);
}
@Override
public synchronized Object removeObject(Object key) {
// TODO Auto-generated method stub
return cache.removeObject(key);
}
@Override
public synchronized void clear() {
cache.clear();
}
@Override
public synchronized int size() {
return cache.size();
}
@Override
public String toString() {
return cache.toString();
}
public static void main(String[] args) {
SynchronizedCache cache=
new SynchronizedCache(new PerpetualCache());
cache.putObject("A", 100);
cache.putObject("B", 200);
cache.putObject("C", 300);
System.out.println(cache);
}
}
说明,synchronized 关键描述方法时,这个方法上会自动应用一个排它锁(这里的排它锁
表示当前在访问此方法时,其它线程只能等待),
这样可能会带来一个性能上的问题,那如何解决呢?(可以考虑将读缓存数据的过程改成读
锁-ReadWriteLock)
2.4 命中率记录 Cache 设计
我们基于命中率判断,我们的数据是否来自缓存,并且可以基于命中率判断缓存设计是否合
理。
假如命中率比较低,说明我们的缓存需要重新进行设计。
package com.java.cache;
public class LoggingCache implements Cache{
private Cache cache;
/**记录请求次数*/
private int requests;
/**记录命中次数*/
private int hits;
public LoggingCache(Cache cache) {
this.cache=cache;
}
@Override
public void putObject(Object key, Object value) {
cache.putObject(key, value);
}
@Override
public Object getObject(Object key) {
requests++;
Object obj=cache.getObject(key);
if(obj!=null)hits++;
System.out.println("Cache hit Ratio : "+hits*1.0/requests);
return obj;
}
@Override
public Object removeObject(Object key) {
return cache.removeObject(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public int size() {
return cache.size();
}
@Override
public String toString() {
// TODO Auto-generated method stub
return cache.toString();
}
public static void main(String[] args) {
SynchronizedCache cache=
new SynchronizedCache(
new LoggingCache(
new PerpetualCache()));
cache.putObject("A", 100);
cache.putObject("B", 200);
cache.putObject("C", 300);
System.out.println(cache);
cache.getObject("D");
cache.getObject("A");
}
}
2.5 基于 FIFO 算法的 Cache 设计
基于 FIFO(先进先出算法)设计缓存对象的淘汰策略,例如:
package com.java.cache;
import java.util.Deque;
import java.util.LinkedList;
public class FifoCache implements Cache{
/**借助此对象存储数据*/
private Cache cache;
/**借助此队列记录 key 的顺序*/
private Deque<Object> keyOrders;
/**通过此变量记录 cache 可以存储的对象个数*/
private int maxCap;
public FifoCache(Cache cache,int maxCap) {
this.cache=cache;
keyOrders=new LinkedList<>();
this.maxCap=maxCap;
}
@Override
public void putObject(Object key, Object value) {
//1.记录 key 的顺序(起始就是存储 key,添加在队列最后位置)
keyOrders.addLast(key);
//2.检测 cache 中数据是否已满,满了则移除。
if(keyOrders.size()>maxCap) {
Object eldestKey=keyOrders.removeFirst();
cache.removeObject(eldestKey);
}
//3.放新的对象
cache.putObject(key, value);
}
@Override
public Object getObject(Object key) {
return cache.getObject(key);
}
@Override
public Object removeObject(Object key) {
Object obj=cache.removeObject(key);
keyOrders.remove(key);
return obj;
}
@Override
public void clear() {
cache.clear();
keyOrders.clear();
}
@Override
public int size() {
return cache.size();
}
@Override
public String toString() {
// TODO Auto-generated method stub
return cache.toString();
}
public static void main(String[] args) {
Cache cache=
new SynchronizedCache(
new LoggingCache(
new FifoCache(
new PerpetualCache(),3)));
cache.putObject("A",100);
cache.putObject("B",200);
cache.putObject("C",300);
cache.getObject("A");
cache.putObject("D",400);
cache.putObject("E",500);
System.out.println(cache);
}
}
2.6 基于 Lru 算法的 Cache 设计
Lru 算法是一个最近最少使用算法,通常应用于缓存淘汰策略,例如:
package com.java.cache;
import java.util.LinkedHashMap;
import java.util.Map;
public class LruCache implements Cache{
private Cache cache;
/**通过此属性记录要移除的数据对象*/
private Object eldestKey;
/**通过此 map 记录 key 的访问顺序*/
private Map<Object,Object> keyMap;
@SuppressWarnings("serial")
public LruCache(Cache cache,int maxCap) {
this.cache=cache;
//LinkedHashMap 可以记录 key 的添加顺序或者访问顺序
this.keyMap=
new LinkedHashMap<Object,Object>(maxCap, 0.75f, true)
{//accessOrder
//此方法每次执行 keyMap 的 put 操作时调用
@Override
protected boolean removeEldestEntry
(java.util.Map.Entry<Object, Object> eldest) {
boolean isFull=size()>maxCap;
if(isFull)eldestKey=eldest.getKey();
return isFull;
}
};
}
@Override
public void putObject(Object key, Object value) {
//存储数据对象
cache.putObject(key, value);
//记录 key 的访问顺序,假如已经满了,就要从 cache 中移除数据
keyMap.put(key, key);//此时会执行 keyMap 对象的 removeEldestEntry
if(eldestKey!=null) {
cache.removeObject(eldestKey);
eldestKey=null;
}
}
@Override
public Object getObject(Object key) {
keyMap.get(key);//记录 key 的访问顺序
return cache.getObject(key);
}
@Override
public Object removeObject(Object key) {
return cache.removeObject(key);
}
@Override
public void clear() {
cache.clear();
keyMap.clear();
}
@Override
public int size() {
return cache.size();
}
@Override
public String toString() {
return cache.toString();
}
public static void main(String[] args) {
SynchronizedCache cache=
new SynchronizedCache(
new LoggingCache(
new LruCache(new PerpetualCache(),3)));
cache.putObject("A", 100);
cache.putObject("B", 200);
cache.putObject("C", 300);
cache.getObject("A");
cache.getObject("C");
cache.putObject("D", 400);
cache.putObject("E", 500);
System.out.println(cache);
}
}
2.7 基于软引用实现的 Cache 设计
软引用(SoftReference)引用的对象会在内存不足时,清除软引用引用的对象,假如软引
用对象构建时关联了一个引用队列(ReferenceQueue),软引用引用的对象销毁后,会将
《软引用》放入到引用队列。
package com.java.cache;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
public class SoftCache implements Cache{
private Cache cache;
private ReferenceQueue<Object> garbageOfRequenceQueue=
new ReferenceQueue<>();
public SoftCache(Cache cache) {
this.cache=cache;
}
@Override
public void putObject(Object key, Object value) {
//1.移除一些垃圾对象(Soft 引用引用的已经被回收的对象)
removeGarbageObjects();
//2.将对象存储到 cache(key 不变,Value 为为 soft 引用对象)
cache.putObject(key,
new SoftEntry(key, value, garbageOfRequenceQueue));
}
@Override
public Object getObject(Object key) {
//1.基于 key 获取软引用对象并判断
SoftEntry softEntry=(SoftEntry)cache.getObject(key);
if(softEntry==null)return null;
//2.基于软引用对象获取它引用的对象并判断
Object target = softEntry.get();
if(target==null)cache.removeObject(key);
return target;
}
@Override
public Object removeObject(Object key) {
//1.移除一些垃圾对象(Soft 引用引用的已经被回收的对象)
removeGarbageObjects();
//2.从 cache 中移除对象
Object removedObj=cache.removeObject(key);
return removedObj;
}
@Override
public void clear() {
//1.移除一些垃圾对象(Soft 引用引用的已经被回收的对象)
removeGarbageObjects();
//2.清空 cache
cache.clear();
}
@Override
public int size() {
removeGarbageObjects();
return cache.size();
}
private void removeGarbageObjects() {
SoftEntry softEntry=null;
//1.从引用队列中获取已经被 GC 的一些对象的引用
while((softEntry=
(SoftEntry)garbageOfRequenceQueue.poll())!=null){
//softEntry 不为 null 表示 softEntry 引用的对象已经被移除
//2.从 cache 中将对象引用移除。
cache.removeObject(softEntry.key);
}
}
/**定义软引用类型*/
private static class SoftEntry extends SoftReference<Object> {
private final Object key;
public SoftEntry(Object key,
Object referent, ReferenceQueue<? super Object> rQueue) {
super(referent, rQueue);
this.key=key;
}
}
@Override
public String toString() {
// TODO Auto-generated method stub
return cache.toString();
}
public static void main(String[] args) {
Cache cache=new SoftCache(new PerpetualCache());
cache.putObject("A", new byte[1024*1024]);
cache.putObject("B", new byte[1024*1024]);
cache.putObject("C", new byte[1024*1024]);
cache.putObject("D", new byte[1024*1024]);
cache.putObject("E", new byte[1024*1024]);
System.out.println(cache.size());
System.out.println(cache);
}
}
问题,假如软引用对象构建时关联的队列中,有了软引用对象,说明什么?说明软引用引用
的对象被销毁了。
2.8 基于弱引用实现的 Cache 设计
弱引用(WeakReference)引用的对象可能会在 GC 时,清除弱引用引用的对象,假如弱引用
对象构建时关联了一个引用队列(ReferenceQueue),弱引用引用的对象销毁后,会将弱引
用放入到引用队列。
package com.java.cache;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class WeakCache implements Cache{
private Cache cache;
private ReferenceQueue<Object> garbageOfRequenceQueue=
new ReferenceQueue<>();
public WeakCache(Cache cache) {
this.cache=cache;
}
@Override
public void putObject(Object key, Object value) {
//1.移除一些垃圾对象(Soft 引用引用的已经被回收的对象)
removeGarbageObjects();
//2.将对象存储到 cache(key 不变,Value 为为 soft 引用对象)
cache.putObject(key,new WeakEntry(key, value, garbageOfRequenceQueue));
}
@Override
public Object getObject(Object key) {
//1.基于 key 获取软引用对象并判断
WeakEntry softEntry=(WeakEntry)cache.getObject(key);
if(softEntry==null)return null;
//2.基于软引用对象获取它引用的对象并判断
Object target = softEntry.get();
if(target==null)cache.removeObject(key);
return target;
}
@Override
public Object removeObject(Object key) {
//1.移除一些垃圾对象(Soft 引用引用的已经被回收的对象)
removeGarbageObjects();
//2.从 cache 中移除对象
Object removedObj=cache.removeObject(key);
return removedObj;
}
@Override
public void clear() {
//1.移除一些垃圾对象(Soft 引用引用的已经被回收的对象)
removeGarbageObjects();
//2.清空 cache
cache.clear();
}
@Override
public int size() {
removeGarbageObjects();
return cache.size();
}
private void removeGarbageObjects() {
WeakEntry softEntry=null;
//1.从引用队列中获取已经被 GC 的一些对象的引用
while((softEntry=(WeakEntry)garbageOfRequenceQueue.poll())!=null) {
//softEntry 不为 null 表示 softEntry 引用的对象已经被移除
//2.从 cache 中将对象引用移除。
cache.removeObject(softEntry.key);
}
}
/**定义软引用类型*/
private static class WeakEntry extends WeakReference<Object> {
private final Object key;
public WeakEntry(Object key,
Object referent, ReferenceQueue<? super Object> rQueue) {
super(referent, rQueue);
this.key=key;
}
}
@Override
public String toString() {
return cache.toString();
}
public static void main(String[] args) {
Cache cache=new WeakCache(new PerpetualCache());
cache.putObject("A", new byte[1024*1024]);
cache.putObject("B", new byte[1024*1024]);
cache.putObject("C", new byte[1024*1024]);
cache.putObject("D", new byte[1024*1024]);
cache.putObject("E", new byte[1024*1024]);
cache.putObject("F", new byte[1024*1024]);
cache.putObject("G", new byte[1024*1024]);
System.out.println(cache.size());
System.out.println(cache);
}
}
2.9 基于序列化实现的 Cache 设计
向缓存存储数据时,可以先将对象序列化(转换为字节)然后进行存储。当从缓存获取对象时
再进行反序列化(深拷贝)。深拷贝的对象,可以随便在外部进行修改,不影响缓存中原有数
据(深拷贝获取的对象是一个新的对象)。
package com.java.cache;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializedCache implements Cache{
private Cache cache;
public SerializedCache(Cache cache) {
this.cache=cache;
}
/**序列化*/
private byte[] serialize(Object value) {
//1.构建流对象
ByteArrayOutputStream bos=null;
ObjectOutputStream oos=null;
try {
//1.2 构建字节数组输出流,此流对象内置可扩容的数组。
bos=new ByteArrayOutputStream();
//1.3 构建对象输出流
oos=new ObjectOutputStream(bos);
//2.对象序列化
oos.writeObject(value);
//此时对象会以字节的方式写入到字节数组输出流
oos.flush();
return bos.toByteArray();
}catch (Exception e) {
throw new RuntimeException(e);
}finally {
//3.关闭流对象
if(bos!=null)
try{bos.close();bos=null;}catch(Exception e) {}
if(oos!=null)
try{oos.close();oos=null;}catch (Exception e2) {}
}
}
/**反序列化*/
public Object deserialize(byte[] value) {
//1.创建流对象
ByteArrayInputStream bis=null;
ObjectInputStream ois=null;
try {
//1.1 构建字节数组输入流,此对象可以直接读取数组中的字节信息
bis=new ByteArrayInputStream(value);
//1.2 构建对象输入流(对象反序列化)
ois=new ObjectInputStream(bis);
//2.反序列化对象
Object obj=ois.readObject();
return obj;
}catch(Exception e) {
throw new RuntimeException(e);
}finally {
//3.关闭流对象
if(bis!=null)
try{bis.close();bis=null;}catch(Exception e) {}
if(ois!=null)
try{ois.close();ois=null;}catch (Exception e2) {}
}
}
@Override
public void putObject(Object key, Object value) {
cache.putObject(key, serialize(value));
}
@Override
public Object getObject(Object key) {
return deserialize((byte[])cache.getObject(key));
}
@Override
public Object removeObject(Object key) {
return cache.removeObject(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public int size() {
return cache.size();
}
public static void main(String[] args) {
Cache cache=new SerializedCache(new PerpetualCache());
cache.putObject("A", 200);
cache.putObject("B", 300);
Object v1=cache.getObject("A");
Object v2=cache.getObject("A");
System.out.println(v1==v2);
System.out.println(v1);
System.out.println(v2);
}
}