业务场景是这样的,我要给前端提供一个接口,这个接口是从数据库查出数据根据业务逻辑拼装数据返回。这样是可以完成的,但是这个接口调用量比较大,于是就加了一个redis,把数据存到缓存中提高吞吐量。但是在系统压测的时候发现,没分钟访问量在3W左右总是上不去。后来查了资料才发现,redis有热点数据,因为我存redis中的数据就有5个key,这些key都是固定的,redis的存储机制是相同的key都会落到相同的分片,redis的每个分片就支持3W左右的吞吐量。
那么问题来了,怎样继续把吞吐量搞上去呢,于是就用到了本地缓存 guawa。下面先定义一个本地缓存的接口,然后再加实现类。guawa的get方法是先从本地缓存里面取,如果拿不到就去load方法里面取。想要深入了解可以看一下里面的源码。
在做本地缓存的时候有一个坑,就是spring和spring mvc初始化扫包的时候注意是不是只扫了一遍service,如果扫了两遍会初始化两个本地缓存对象,由于缓存不是static的类,会导致别人调用你的接口时拿不到最新的,而你本地跑的时候可以拿到最新的。
public interface CacheRpc {
void set(String key, String value) throws Exception;
/**
* @param key
* @param value
* @param timeout
* 单位毫秒
*/
void set(String key, String value, long timeout) throws Exception;
String get(String key) throws Exception;
void delete(String key) throws Exception;
}
public class R2MCacheRpcImpl implements CacheRpc {
@Resource(name = "cacheClusterClient")
private CacheClusterClient cacheClusterClient;
private static final Logger logger = LogManager.getLogger(R2MCacheRpcImpl.class);
private ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1));
//添加本地缓存,优先查询
private final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(new CacheLoader<String, String>() {
@Override
public String load(String accountsId) {
logger.debug("重新加载本地缓存");
return cacheClusterClient.get(accountsId) == null ? "":cacheClusterClient.get(accountsId);
}
@Override
public ListenableFuture<String> reload(final String accountsId, String oldValue) throws Exception {
return executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return cacheClusterClient.get(accountsId);
}
});
}
});
@Override
public void set(String key, String value) throws Exception {
try {
cacheClusterClient.set(key, value);
cache.put(key,value);
} catch (Exception e) {
logger.error("r2m set error,key=" + key + ",value=" + value, e);
throw e;
}
}
@Override
public void set(String key, String value, long timeout) throws Exception {
try {
cacheClusterClient.psetex(key, timeout, value);
cache.put(key,value);
} catch (Exception e) {
logger.error("r2m psetex error,key=" + key + ",value=" + value, e);
throw e;
}
}
@Override
public String get(String key) throws Exception {
try {
//优先从本地缓存取活动数据
return cache.getUnchecked(key);
} catch (Exception e) {
logger.error("r2m get error,key=" + key, e);
return null;
}
}
@Override
public void delete(String key) throws Exception {
try {
cache.invalidateAll();//清除所有key
//cache.refresh(key);//清除当前key
} catch (Exception e) {
logger.error("r2m del error,key=" + key, e);
throw e;
}
}
}
运行逻辑:spring初始化的时候会初始化一个本地缓存对象,调用本地缓存做set操作的时候,guawa会把数据放到本地缓存里面,同时放置两个时间,一个是过期时间,一个是刷新时间。用户在调本地缓存get的时候先看数据有没有过期,如果没有过期,就会取本地缓存里的,如果过期的话,调用load方法取数据放到本地缓存里面。 如果过期了,请求来的时候会触发reload方法,先返回旧数据,然后开启一个线程去取数据然后放到本地缓存。