欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何安全的更新java本地缓存

程序员文章站 2022-04-21 13:32:06
...

对于某类数据,如果读的频率远远大于写的频率,数据不会经常被修改,则最适合采用本地缓存。但使用缓存,不可避免的就需要对缓存进行更新。

最近在做一个项目的时候,发现多个老系统里采用了一种不安全的更新方案,该方案的主要思路如下:

/** 本地缓存  */
private List<InterfaceConfig>   configs            = null;
/** 本地缓存的上次更新时间 */
private long                    lastUpdateTime     = 0;
public List<InterfaceConfig> queryInterfaceList() {
        long currentTime = System.currentTimeMillis();
        //判断本次缓存是否过期,过期则重新调用webservice查询数据,并更新缓存
        if (currentTime - lastUpdateTime > 60000) {

            InterfaceManageResult result = interfaceManageFacade.queryAllInterfaceList();
            if (null != result && result.isSuccess()) {
                configs = result.getInterfaceConfigList();
            }
            lastUpdateTime = currentTime;
        }
        if (!CollectionUtils.isEmpty(configs)) {
            return configs;
        }
        
        //本地缓存为空,则重新调用webservice查询数据,并更新缓存
        InterfaceManageResult result = interfaceManageFacade.queryAllInterfaceList();
        if (null == result || !result.isSuccess()) {
            return null;
        }
        configs = result.getInterfaceConfigList();
        return configs;
    }

  当外部请求访问缓存数据时:

  1. 如果缓存已经过期(当前时间-缓存的上次更新时间超过缓存的有效期),则重新调用webservice访问服务端查询数据,然后更新缓存。
  2. 如果缓存未过期,但缓存为空,则重新调用webservice访问服务端查询数据,然后更新缓存。

仔细分析一下,该方案存在以下几处安全隐患:

  1. 如果某一时刻缓存过期,此时刚好有大量的请求并发访问缓存数据,则会给服务端造成很大的压力,有多少个并发请求,就会并发向服务端发起多少次webservice请求
  2. 缓存第一次初始化前,如果有大量的请求并发访问缓存数据,同样会给服务端早晨很大的压力。
       即在某些时间点,会给服务端带来峰值抖动,其实,造成该隐患的根本原因在于:缓存的更新时机是由外部请求直接触发的。因此,消除该隐患就应该从这个点入手,改变缓存更新的触发点,消除服务端的峰值。
       改进后的方案:
  1. 系统启动时,调用webservice访问服务端查询数据,并初始化缓存
  2. 采用定时任务,定时触发缓存更新。

       这样就将本地缓存的更新与外部请求的访问完全隔离了,外部请求不再直接触发对服务端的webservice请求,因为缓存的更新导致的服务端的峰值消除了。

 

       其他可以优化的点:

       目前服务端查询数据时,会查询DB中的所有数据,但实际上在大多数定时任务执行中,这些数据是没有发生任何变化的,即使有变化,也可能只有1%甚至更小比例的数据发生变化,缓存不需要对没有变化的数据进行更新,因此会造成一定的时间浪费。可以在缓存更新时,记录本次更新时间,然后下次更新时,只查询DB中最近修改时间在上次更新时间之后的数据,然后只对缓存中这部分数据进行更新,这样就会大大增加每次缓存更新完成的速度,也会降低对DB的压力。 既然现在只更新本地缓存中的部分数据,就需要考虑并发读写的问题,可以采用ConcurrentHashMap或者CopyOnWriteArrayList解决

 

本文为原创,转载请注明出处