缓存穿透:
所谓的缓存穿透,简单来讲就是查询某些不存在的key时,缓存和数据库查询结果都为空,而空的结果又不被缓存起来,而导致每次查询都去请求数据库层的情况。如果接口的并发足够大,那么同时有N多线程直接访问数据库的压力可想而知。
解决思路:
如果缓存未命中,那么只有一个线程访问数据库。示例代码如下:
package com.primer.demo.util;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.FutureTask;
public class CacheTask {
private static final ConcurrentMap<String, FutureTask<Object>> cache = new ConcurrentHashMap<String, FutureTask<Object>>();
public static Object getInTask(String cacheKey, Callable<Object> caller) {
System.out.println("1.缓存未命中,将查询数据库或者调用远程服务");
//未命中缓存,开始计算
FutureTask<Object> f = cache.get(cacheKey);
if (f == null) {
FutureTask<Object> ft = new FutureTask<Object>(caller);
f = cache.putIfAbsent(cacheKey, ft);
if (f == null) {
System.out.println("2.任务未命中,将查询数据库或者调用远程服务");
f = ft;
ft.run();
}
}
else {
System.out.println("2.任务命中,直接从缓存取结果");
}
try {
Object result = f.get();
System.out.println("取回的结果result:"+result);
return result;
} catch (Exception e) {
e.printStackTrace();
}
finally{
//最后将计算任务去掉,虽然已经移除任务对象,但其他线程
//仍然能够获取到计算的结果,直到所有引用都失效,被垃圾回收掉
boolean success = cache.remove(cacheKey,f);
System.out.println(success);
}
return null;
}
}
package com.primer.demo.util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import net.sf.ehcache.Cache;
public class CacheTest {
private Cache cache;
public CacheTest() {
this.cache = EHCacheUtil.getCacheManager().getCache(EHCacheUtil.EHCACHE_ONEM_CACHE);
}
public List<Long> concurrentService(int para1, int param2) {
long beginTime = System.nanoTime();
final String cacheKey = "IamKey";
List<Long> list = (List<Long>) EHCacheUtil.get(cacheKey);
if (list == null) {
Callable<Object> caller = new Callable<Object>() {
public Object call() throws InterruptedException {
System.out.println(" go to dao or rmi");
List<Long> list = new ArrayList<Long>();
list.add(1l);
list.add(2l);
// 将计算结果缓存
System.out.println("结果计算完毕,存入分布式缓存中");
EHCacheUtil.put(cacheKey, list);
Thread.sleep(500);
// 计算结果,通常是访问数据库或者远程服务
return list;
}
};
List<Long> result = (List<Long>) CacheTask.getInTask(cacheKey, caller);
long end = System.nanoTime();
// useTimes.add(end-beginTime);
return result;
} else {
System.out.println("1.缓存命中,直接返回");
long end = System.nanoTime();
// useTimes.add(end-beginTime);
return list;
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
new CacheTest().concurrentService(1, 1);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
new CacheTest().concurrentService(1, 1);
}
}
}).start();
}
}