手撕 MyBatis 可插拔式缓存设计(拓展)

手撕 MyBatis 可插拔式缓存设计(拓展)

1 缓存基础

1.1 是什么?

缓存就是内存中的一个对象,可以基于此对象存储其它对象。

1.2 为什么要应用缓存?

降低数据库的访问压力,提高数据的查询效率,改善用户体验。

1.3 你了解哪些缓存?

  1. 数据库内置的缓存?(例如 mysql 的查询缓存)

  2. 数据层缓存(一般由持久层框架提供,例如 MyBatis)

  3. 业务层缓存(基于 map 等实现的本缓存,分布式缓存-例如 redis)

  4. 浏览器内置缓存?

  5. CPU 缓存(高速缓冲区)

1.4 设计缓存时你应该考虑哪些问题?

  1. 存储结构(使用什么结构存储数据效率会更高?-散列表)

  2. 淘汰策略(缓存容量有限-LRU/FIFO/LFU,软应用、弱引用)

  3. 任务调度(定期刷新缓存,缓存失效时间)

  4. 并发安全(缓存并发访问时的线程安全)

  5. 日志记录(缓存是否命中,命中率是多少)

  6. 序列化(存对象时序列化、取对象时反序列化)

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);
 	}
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值