利用ConcurrentHashMap和newScheduled
2021-08-21 本文已影响0人
haiyong6
背景
面临一个需求,开发了一个加密接口,秘钥存储在vault里,并发压测时吞吐量达不到1000,查了下原因,除了java加密本身比较耗时外,http去vault请求秘钥也受点影响,所以,打算做一个java缓存来增加并发吞吐量。
一般情况下,这种需求可以直接用redis来做,第一次去vault取秘钥,放到redis里,设10s左右的过期时间,10s内的大批量请求全部从redis里取秘钥,只要保证并发时能达到那个吞吐量就可以了。但是,毕竟是秘钥,存到redis里不够安全,所以pass掉了这个方案。
于是,就需要在java缓存里实现一下这个类redis缓存的功能。
思路
参考nacos-client源码里ClientWorker类的线程定时维护nacos属性更新的功能,如下图:
nacos-client截图
利用ConcurrentHashMap维护一个静态的可适应并发情况下的键值对,把秘钥值、系统当前timestamp维护进一个MutableTriple对象,把此对象put进这个map里,这样所有线程都能拿到这个map里的值,利用newScheduledThreadPool开一个线程定时,每隔10s钟扫描这个map里的所有值,和当前时间做比对,如果超过10s的移除该值。
代码
实现代码如下:
private final static ConcurrentHashMap<String, Object> cacheKeyMap = new ConcurrentHashMap<String, Object>();
final static ScheduledExecutorService executor;
static {
executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("vaultScheduledTread");
t.setDaemon(true);
return t;
}
});
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
checkCacheMapInfo();
} catch (Throwable e) {
LogUtils.error("vaultScheduledTread deal cacheMap failed,{}" + e.getMessage());
}
}
public void checkCacheMapInfo() {
//检查map里所有键值对的有效期,过期的删除
if(!cacheKeyMap.isEmpty()) {
List<String> removeKeyList = new ArrayList<String>();
Set<Entry<String, Object>> entries = cacheKeyMap.entrySet();
for (Entry<String, Object> entry:entries){
@SuppressWarnings("unchecked")
MutableTriple<String, Long, ?> valueTriple = (MutableTriple<String, Long, ?>) entry.getValue();
//LogUtils.info("key:"+ entry.getKey() + " value:" + valueTriple.getLeft());
Long timestamp = valueTriple.getMiddle();
if(System.currentTimeMillis() > (timestamp + 1000*10)) {
removeKeyList.add(entry.getKey() + "");
}
}
for(String key : removeKeyList) {
cacheKeyMap.remove(key);
}
}
}
}, 1L, 10L, TimeUnit.SECONDS);
}
业务调用方法如下,注意要加同步关键字synchronized:
private String getCacheValue(EncryptParam params) throws Exception{
String mapKey = params.getKeyName() + ":" + params.getKeyType();
if (!cacheKeyMap.containsKey(mapKey)) {
synchronized (cacheKeyMap) {
if (!cacheKeyMap.containsKey(mapKey)) {
String value = vaultClient.getValueByPath(params.getVaultToken(), params.getKeyName(), params.getKeyType());
MutableTriple<String, Long, ?> valueTriple = MutableTriple.of(value, System.currentTimeMillis(), null);
cacheKeyMap.put(mapKey, valueTriple);
}
}
}
@SuppressWarnings("unchecked")
MutableTriple<String, Long, ?> valueTriple = (MutableTriple<String, Long, ?>) cacheKeyMap.get(mapKey);
return (String) valueTriple.getLeft();
}
这样就实现了第一次没有的时候去vault查秘钥,查出来之后放入cacheKeyMap里做缓存,用定时任务每个10s钟扫描键值对里的值是否已过期,过期了就remove掉,一个类redis缓存的功能。