Java 使用MemoryCache缓存,缓存数据库主数据,提高数据读写性能

基于数据库的应用系统中,经常有必要根据 ID 获取编号或者名称,这是因为我们设计数据库,一般按照“三范式”来设计数据库,业务数据表中只存放主数据的 ID。而根据 ID 获取编号或者名称,通常是使用 SQL 实时查询。每次都查询数据库,数据库负荷不小,这部分其实可以优化。
  常见的 cache , 包括 ehcache/oscache/apache jcs, 只适合于 cache 业务数据(transaction data),而不适合于 cache 主数据(master data)。还动不动就分布式缓存,其实没有必要。
  这里的业务数据(transaction data)指的是数据量随时间线性增长的数据,主数据(master data)指的是数据量不随时间线性增长的数据(不增长或增长很慢)。这两个数据在缓存处理逻辑上的差别有:
1. 业务数据(transaction data) 的缓存一般只缓存最近使用的数据(Least Recently Used Cache);主数据(master data)的缓存是所有数据。
2. 两种数据都需要定期更新缓存:业务数据(transaction data) 的缓存更新是逐个进行;主数据(master data)的缓存更新是按分类,某种数据一起性地全更新。
3. 业务数据(transaction data) 的缓存,需要定时清除长时间不用的数据;主数据(master data)的缓存,不需要清除长时间不用的数据。

  有必要做一个工具类/工具包,用于主数据(master data)的缓存。可以在多个项目中复用。
  
  首先定义 cache 的公共接口,有两个操作:数据一起性全更新 reload(), 定义数据过期时间 getReloadPeriodMillis(). 这样便有了这个类:

为了排版,以下代码中用了中文全角空格!!!
  
package org.velocityweb.cache;

import Java.util.Map;

/**
 * interface for period lazy reload cache, normally for master data translating id to name/code
 * 
 * @author Jacklondon Chen
 * 
 */
public interface PeriodicLazyReloadCache {
  /**
   * get reload period in millis-seconds
   * 
   * @return
   */
  public long getReloadPeriodMillis();

  /**
   * reload data invalid for timeout
   * 
   * @param bufferMap
   */
  public void reload(Map bufferMap);

}

再定义个“内存、周期性更新、延迟加载的缓存”类 ——MemPeriodicLazyReloadCacheProvider,用于加载数据,更新缓存:
package org.velocityweb.cache;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
 * memory cache for period lazy reload cache, normally for master data translating id to name/code. Use this for
 * small-scale data cache. This class is thread safe.
 * 
 * @author Jacklondon Chen
 * 
 */
public class MemPeriodicLazyReloadCacheProvider {
  PeriodicLazyReloadCache cache;
  volatile Map bufferMap = null;
  volatile long lastReloadTime = -1;

  /**
   * constructor for MemPeriodicLazyReloadCacheProvider
   * 
   * @param cache
   *      cache implement
   */
  public MemPeriodicLazyReloadCacheProvider(PeriodicLazyReloadCache cache) {
    this.cache = cache;
  }

  /**
   * get data from cache
   * 
   * @param key
   * @return
   */
  public synchronized Object get(Object key) {
    long timePassed = System.currentTimeMillis() - lastReloadTime;
    if (timePassed > cache.getReloadPeriodMillis()) {
      Map newBufferMap = createBufferMap();
      cache.reload(newBufferMap);
      this.bufferMap = newBufferMap;

      lastReloadTime = System.currentTimeMillis();
    }

    return bufferMap.get(key);
  }

  /**
   * create buffer map, allow overwrite
   * 
   * @return
   */
  protected Map createBufferMap() {
    return Collections.synchronizedMap(new HashMap());
  }
}

这里使用了 volatile、 synchronized 来支持多线程并发访问。也可以用单独一个线程,来更新数据 buffer 的。可能那样更好,也未可知。

以下举一个例子,比如我们把配置参数保存在数据库中,运行时候可以读,如果直接读数据库,每次都需要运行 SQL. 而是用了上面的 cache ,则可以让读数据库减少到每分钟只读一次。
以下是定义一个自己的 cache 常量 applicationConfigBuffer (可以用 static),

static MemPeriodicLazyReloadCacheProvider applicationConfigBuffer = null;
PeriodicLazyReloadCache cache = null;

    // appconfig cache
    cache = new PeriodicLazyReloadCache() {

      public long getReloadPeriodMillis() {
        return DateUtils.MILLIS_PER_MINUTE;
      }

      public void reload(final Map bufferMap) {
        log.info("reload appliction config data for cache....");
        
        DataSource ds = ...;
        SqlRunnable run = new SqlRunnable() {

          public void run(final Connection con) {
            List dataList = xxx.getAll();

            for (Object obj : dataList) {
              ConfigData c = (ConfigData) obj;
              bufferMap.put(c.getKeyCol(), c.getValueCol());
            }
          }
        };
        JdbcTransactionUtils.doWithJdbcTransactionDefaultCommit(run, ds);

      }
    };
    applicationConfigBuffer = new MemPeriodicLazyReloadCacheProvider(cache);
    
  真正用的时候,只有一行代码:
Object obj = applicationConfigBuffer.get(key);

转载于:https://my.oschina.net/u/3624220/blog/1487225

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值