为什么应用缓存?
- 升高数据库的拜访压力。
- 进步查问效率。
- 改善用户体验。
你都理解哪些缓存?
- 数据库内置缓存(DBA批改)。
- 数据层缓存(由长久层框架决定,例如mybatis)
- 业务层缓存(由业务层框架以及第三缓存产品决定:本地缓存+分布式缓存)
- 浏览器缓存(Cache-Control)
设计缓存都应该思考什么问题?
- 存储构造:应用什么构造存储数据?(数组,链表,散列存储-哈希存储)
- 淘汰算法:无限容量(LRU,FIFO,.....),不限容量(GC)
- 并发平安:保障线程平安。
- 任务调度:每隔多长时间清理一下缓存。
- 日志记录:是否命中?(命中率)
缓存零碎设计根底
缓存规范定义
package com.cy.java.cache; /** Cache接口设计*/ public interface Cache { public void putObject(Object key,Object value); public Object getObject(Object key); public Object removeObject(Object key); public void clear(); public int size(); }
繁难Cache实现
场景利用:
- 存储数据量比拟小(因为没有思考淘汰机制)
- 没有线程共享(一个线程的外部缓存)
- 缓存对象生命周期比拟短
package com.cy.java.cache; import java.util.HashMap; import java.util.Map; /**负责真正存储数据的一个对象,将数据存储到一个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()); } }
构建线程平安Cache对象
场景利用:并发环境
package com.cy.java.cache; /**线程平安的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); } }
思考:对于SynchronizedCache 有什么劣势,劣势?
反对日志记录的Cache实现
package com.cy.java.cache; /** 用于记录命中率的日志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"); } }
思考:你感觉LoggingCache记录日志的形式有什么不好的中央?(信息的完整性,同步问题)
LruCache实现
利用场景:基于LRU算法的的根本实现
package com.cy.java.cache; import java.util.LinkedHashMap; import java.util.Map; /** 缓存淘汰策略:LRU(最近起码应用算法)*/ 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); } }
设置Cache淘汰算法:FIFO算法
package com.cy.java.cache; import java.util.Deque; import java.util.LinkedList; /** * FifoCache :基于FIFO算法(对象满了要按先进先出算法移除对象)实现cache对象 */ 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); } }
序列化Cache的实现
场景:存储到cache的是对象的字节
package com.cy.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); } }
软件援用Cache实现
利用场景:内存不足时淘汰缓存中数据
package com.cy.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); } }
弱Cache对象实现
利用场景:GC触发革除缓存对象
package com.cy.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); } }
缓存零碎设计进阶
缓存利用需要降级
- 缓存零碎既要保障线程平安又要保障性能。
- 缓存日志的记录要写到文件,而且是异步写
- 向缓存中写数据时要进步序列化性能。
缓存对象读写锁利用
package com.cy.java.cache; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 构建线程平安对象,基于ReentrantReadWriteLock对象实现读写锁利用。 * @author qilei */ public class ReentrantLockCache implements Cache { private Cache cache; /** * 此对象提供了读锁,写锁利用形式. * 1)写锁:排他锁 * 2)读锁:共享锁 * 阐明:读写不能同时执行。 */ private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public ReentrantLockCache(Cache cache) { this.cache=cache; // TODO Auto-generated constructor stub } @Override public void putObject(Object key, Object value) { readWriteLock.writeLock().lock(); try { cache.putObject(key, value); }finally { readWriteLock.writeLock().unlock(); } } @Override public Object getObject(Object key) { readWriteLock.readLock().lock(); try { Object object=cache.getObject(key); return object; }finally{ readWriteLock.readLock().unlock(); } } @Override public Object removeObject(Object key) { readWriteLock.writeLock().lock(); try { Object object=cache.removeObject(key); return object; }finally{ readWriteLock.writeLock().unlock(); } } @Override public void clear() { readWriteLock.writeLock().lock(); try { cache.clear(); }finally{ readWriteLock.writeLock().unlock(); } } @Override public int size() { readWriteLock.readLock().lock(); try { int size=cache.size(); return size; }finally{ readWriteLock.readLock().unlock(); } } }
异步日志Cache实现
第一步:增加依赖
` <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> `
第二步:增加配置文件logback.xml (参考我的项目代码)
<?xml version="1.0" encoding="UTF-8"?> <configuration> <logger name="com.cy" level="TRACE" /> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--文件门路,定义了日志的切分形式----把每一天的日志归档到一个文件中,以避免日志填满整个磁盘空间 --> <fileNamePattern>logs/context-log.%d{yyyy-MM-dd}.log </fileNamePattern> <!--只保留最近30天的日志 --> <maxHistory>30</maxHistory> </rollingPolicy> <encoder charset="UTF-8"> <pattern>[%-5level] %date --%thread-- [%logger] %msg %n</pattern> </encoder> </appender> <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender"> <discardingThreshold>0</discardingThreshold> <queueSize>256</queueSize> <appender-ref ref="FILE" /> </appender> <root level="debug"> <appender-ref ref="ASYNC_FILE" /> </root> </configuration>
第三步:构建AsyncLoggingCache
package com.cy.java.cache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 通过此对象异步记录查问操作的命中率 * 1)抉择日志库 * 2)执行异步写操作。 */ public class AsyncLoggingCache implements Cache { //日志门面利用 private static Logger log=LoggerFactory.getLogger(LoggingCache.class); private Cache cache; /**示意申请次数*/ private int requests; /**命中次数(命中示意从缓存中取到数据了)*/ private int hits; public AsyncLoggingCache(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++; //记录日志耗时 log.info("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(); } public static void main(String[] args) { Cache cache= new AsyncLoggingCache(new PerpetualCache()); cache.putObject("A", 100); cache.putObject("B", 200); cache.putObject("C", 300); cache.putObject("D", 400); //System.out.println(cache); cache.getObject("E"); cache.getObject("A"); cache.getObject("B"); } }
Kryo构建序列化Cache
第一步:增加依赖
<dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>5.0.0-RC5</version> </dependency>
第二步:构建我的项目工具类
public class KryoUtils { /** * 多线程并发执行时,可能会呈现线程不平安,具体起因是什么? * 1)多个线程的并发 * 2)多个线程有数据共享 * 3)多个线程在共享数据集上的操作不是原子操作 * * 剖析:当呈现了线程不平安,如何进行批改来保障线程平安 * 1)将多线程改为单线程。 * 2)勾销共享 (例如在以后利用中咱们一个线程一个Kryo对象) * 3)加锁+CAS * * ThreadLocal提供了这样的一种机制: * 1)能够将对象绑定到以后线程(其实是将对象存储到以后线程的map中) * 2)能够从以后线程获取绑定的对象(从以后线程的map中获取对象) */ static private final ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() { protected Kryo initialValue() { Kryo kryo = new Kryo(); // Configure the Kryo instance. kryo.setRegistrationRequired(false); //.... return kryo; }; }; public static Object deserialize(byte[] array){ Kryo kryo=kryos.get(); Input input = new Input(new ByteArrayInputStream(array)); Object obj=kryo.readClassAndObject(input); return obj; } public static byte[] serialize(Object object){ //从以后线程获取kryo对象,以后线程没有会调用ThreadLocal的initialValue办法创建对象并绑定线程 Kryo kryo=kryos.get(); ByteArrayOutputStream bos=new ByteArrayOutputStream(); Output output = new Output(bos); kryo.writeClassAndObject(output, object); output.close(); return bos.toByteArray(); } }
> 构建高性能序列化Cache
public class KryoSerializedCache implements Cache { private Cache cache; public KryoSerializedCache(Cache cache) { this.cache=cache; } @Override public void putObject(Object key, Object value) { //1.将对象序列化 byte[] array=KryoUtils.serialize(value); //2.将序列化后的字节数组援用存储到cache cache.putObject(key,array); } @Override public Object getObject(Object key) { //1.基于key获取缓存中的字节数组援用 byte[] array=(byte[])cache.getObject(key); //2.将字节数组反序列化为对象 return KryoUtils.deserialize(array); } @Override public Object removeObject(Object key) { return KryoUtils.deserialize((byte[])cache.removeObject(key));