缓存穿透及解决方法
访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时DB会挂掉。
解决方案通常有两种:
- 采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;
- 访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间。
缓存雪崩
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案:
可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
缓存击穿
一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。
解决方案:
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。
基于redisson实现自定义缓存map
基于redisson实现自定义缓存map可缓存空值和支持自定义TTL预防缓存穿透和雪崩。这个实现主要是基于redisson的RMap(不支持过期时间但效率更高)或RMapCache(支持过期时间但效率要差一些),结合RSetCache(支持过期时间的set)用于存储空值。可以进行批量缓存和获取提高性能,批量操作基于RBatch。
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.redisson.api.BatchOptions;
import org.redisson.api.RBatch;
import org.redisson.api.RMap;
import org.redisson.api.RMapAsync;
import org.redisson.api.RMapCache;
import org.redisson.api.RMapCacheAsync;
import org.redisson.api.RSetCache;
import org.redisson.api.RSetCacheAsync;
import org.redisson.api.RedissonClient;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
public class CacheMap {
/** 空值缓存时间10分钟 */
private final static int NULL_VALUE_KEY_CACHE_MILLISECONDS = 10 * 60 * 1000;
/** redisson map */
private RMap rMap;
/** 集合用于缓存空值 */
private RSetCache rSet;
/** redisson */
private RedissonClient redisson;
/** 批量操作选项 */
private BatchOptions options = BatchOptions.defaults();
/** 是否有ttl控制 */
private boolean ttlControl;
/** 缓存名称 */
private String name;
/** 集合名称 */
private String setName;
/** 是否缓存null */
private boolean cacheNull;
/**
* 静态构造方法
*
* @param redisson
* redisson客户端
* @param name
* 缓存名称
* @param ttlControl
* 是否有缓存时间控制
* @return
*/
public static CacheMap of(RedissonClient redisson, String name,
boolean ttlControl, boolean cacheNull) {
return new CacheMap(redisson, name, ttlControl, cacheNull);
}
/**
*
* @param redisson
* @param name
* @param ttlControl
*/
private CacheMap(RedissonClient redisson, String name,
boolean ttlControl, boolean cacheNull) {
this.name = name;
this.setName = name + "_null_value_key_set";
if (ttlControl) {
this.rMap = redisson.getMapCache(this.name);
} else {
this.rMap = redisson.getMap(this.name);
}
if (cacheNull) {
this.rSet = redisson.getSetCache(this.setName);
}
this.ttlControl = ttlControl;
// 批量操作
this.redisson = redisson;
this.cacheNull = cacheNull;
}
/**
* 判断是否存在key
*
* @param key
* @return
*/
public boolean containsKey(Object key) {
boolean r = rMap.containsKey(key);
if (!r) {
if(cacheNull){
r = rSet.contains(key);
}
}
return r;
}
/**
* 清除缓存
*/
public void clear() {
rMap.clear();
if (cacheNull) {
rSet.clear();
}
}
/**
* 获取数据
*
* @param key
* @return
*/
public Map get(Object key) {
Map map = Maps.newHashMapWithExpectedSize(1);
Object r = rMap.get(key);
if ((r == null && cacheNull && rSet.contains(key)) || r != null) {
map.put(key, r);
}
return map;
}
/**
* 存入map
*
* @param key
* @param value
* @return
*/
public Object put(Object key, Object value) {
if (value == null) {
if (cacheNull) {
rSet.add(key, NULL_VALUE_KEY_CACHE_MILLISECONDS,
TimeUnit.MILLISECONDS);
}
} else {
rMap.put(key, value);
}
return value;
}
/**
* 移除
*
* @param key
* @return
*/
public Object remove(Object key) {
Object o = rMap.remove(key);
if (cacheNull) {
rSet.remove(key);
}
return o;
}
/**
* 批量存储
*
* @param map
*/
public void putAll(Map map) {
RBatch batch = redisson.createBatch(options);
RMapAsync mapCache = ttlControl ? batch.getMapCache(this.name) : batch
.getMap(this.name);
RSetCacheAsync setCache = batch.getSetCache(this.setName);
Set<Map.Entry> entrySet = map.entrySet();
Iterator<Map.Entry> it = entrySet.iterator();
while (it.hasNext()) {
Map.Entry e = it.next();
if (e.getValue() == null) {
if (cacheNull) {// 缓存null
setCache.addAsync(e.getKey(),
NULL_VALUE_KEY_CACHE_MILLISECONDS,
TimeUnit.MILLISECONDS);
}
} else {
mapCache.putAsync(e.getKey(), e.getValue());
}
}
batch.execute();
}
/**
* 批量获取
*
* @param keys
* @return
*/
public Map getAll(Set keys) {
Map map = rMap.getAll(keys);
if (keys.size() == map.size()) {
return map;
} else if (cacheNull) {
Set set = rSet.readAll();
for (Object key : keys) {
if (!map.containsKey(key)) {
if (set.contains(key)) {
map.put(key, null);
}
}
}
}
return map;
}
/**
* 批量移除
*
* @param keys
* @return
*/
public long fastRemove(Object... keys) {
long r = rMap.fastRemove(keys);
if (cacheNull) {
rSet.removeAll(Sets.newHashSet(keys));
}
return r;
}
/**
* 快速存
*
* @param key
* @param value
* @return
*/
public boolean fastPut(Object key, Object value) {
boolean r = true;
if (value != null) {
r = rMap.fastPut(key, value);
} else if (cacheNull) {
rSet.add(key, NULL_VALUE_KEY_CACHE_MILLISECONDS,
TimeUnit.MILLISECONDS);
}
return false;
}
/**
* 带过期时间存
*
* @param key
* @param value
* @param ttl
* 单位毫秒
* @return
*/
public Object put(Object key, Object value, long ttl) {
if(ttl <= 0){
return put(key, value);
}
if (value != null) {
if (ttlControl) {
((RMapCache) rMap).put(key, value, ttl, TimeUnit.MILLISECONDS);
} else {
throw new IllegalArgumentException("can not support ttl");
}
} else if (cacheNull) {
rSet.add(key, NULL_VALUE_KEY_CACHE_MILLISECONDS,
TimeUnit.MILLISECONDS);
}
return value;
}
/**
* 带过期时间的putAll
*
* @param map
* @param ttl
*/
public void putAll(Map map, long ttl) {
if(ttl <= 0){
putAll(map);
return;
}
RBatch batch = redisson.createBatch(options);
RMapCacheAsync rMapCache = batch.getMapCache(this.name);
Set<Map.Entry> entrySet = map.entrySet();
Iterator<Map.Entry> it = entrySet.iterator();
while (it.hasNext()) {
Map.Entry e = it.next();
if (e.getValue() != null) {
if (ttlControl) {
rMapCache.putAsync(e.getKey(), e.getValue(), ttl,
TimeUnit.MILLISECONDS);
} else {
throw new IllegalArgumentException("can not support ttl");
}
} else if (cacheNull) {
rSet.add(e.getKey(), NULL_VALUE_KEY_CACHE_MILLISECONDS,
TimeUnit.MILLISECONDS);
}
}
// 批量操作
batch.execute();
}
/**
* 快速存
*
* @param key
* @param value
* @param ttl
* @param ttlUnit
* @return
*/
public boolean fastPut(Object key, Object value, long ttl) {
boolean r = true;
if (value != null) {
if (ttlControl) {
r = ((RMapCache) rMap).fastPut(key, value, ttl,
TimeUnit.MILLISECONDS);
} else {
throw new IllegalArgumentException("can not support ttl");
}
} else if (cacheNull) {
rSet.add(key, NULL_VALUE_KEY_CACHE_MILLISECONDS,
TimeUnit.MILLISECONDS);
}
return r;
}
}