利用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缓存的功能。

上一篇下一篇

猜你喜欢

热点阅读