首页 > 代码库 > 针对增量请求的缓存机制实现

针对增量请求的缓存机制实现

背景:

     在web应用中,我们经常使用黑白名单,在http://blog.csdn.net/troy__/article/details/39320699中我们实现了一个线程安全的针对全量请求的缓存机制,这种技术主要是用于黑白名单的全量更新。但是我们不能经常请求全量吧,网络和数据库都会累死,所以在此我们设计实现一个针对增量请求的缓存机制。全量请求提供黑白名单低频度的更新,增量请求提供黑白名单高频度的更新。当然,全量和增量的应用场景也并不只是黑白名单。

设计概述:

     使用缓存的关键之一是:缓存并不能保证实时性,但它提供的是曾经出现过的某个正确状态。(csdn大量使用缓存,且失效时长过长,导致博客专区中很多操作执行后很久都得不到页面上的更新。)
     在对实时性要求、缓存效果、实现难度、业务场景这四点的权衡和折中后,我选择了增量的缓存粒度为1分钟。意思就是说对每个1分钟内的增量进行缓存的存储。
          

设计要点:

  1. 增量请求并不是只对增加的数据进行更新,还要对以前删除的数据进行更新。所以应该返回两个List,一个是新添加数据的List,一个是已删除数据的List。
  2. 在实际工作中,在数据库层面,对于新增加数据和删除数据有两种常见的处理方式:
    • 在一张表里进行操作:利用某个字段标记这条数据当前是否有效,例如当删除这条数据,把这个字段设为99,而新增一条数据,此字段为值1。在这种方式下,对于新增加数据和新删除数据只能通过modefie_time来搜索,而非create_time。
    • 在两张表里进行操作:用一张表储存现有数据,一张表储存已删除数据。这样检索新增加数据和新删除数据分别在两张表中进行。我们可以通过create_time检索。
  3. 缓存的粒度为1分钟,我们每次访问数据库的时间范围也是这一分钟,在这次访问中数据库返回的关于新添加数据的List与已删除数据的List不会出现数据重叠。但是在前一分钟的List和后一分钟的List之间可能出现数据重叠,如前一分钟添加的数据,下一分钟可能就被删除掉了,或者前一分钟添加的数据,这一分钟又被恢复了。所以我们在从缓存取出每一分钟的数据后,需要对数据进行整合操作。

额外考虑:

     用不用多线程的锁来防止在缓存不命中的情况下,多个线程并发去获取数据库中的数据?用锁后,只需要一个线程去访问数据,其他线程等待并返回即可。
     答: 在全量请求的情境下,上面的方案是不错的选择。原因有二,一是单次对数据库数据请求量太大,如果并发访问,数据库压力太大。二是,因为数据源中数据不断增删,缓存数据失效时长较短,会侧面增加并发的程度。而对于增量请求来说,上面两个问题都不存在,每一分钟的增删情况是固定的,不会随着时间推移而改变,所以缓存失效时长取决于缓存自身条件。所以我这里没有使用锁的方式。

范例实现:

/**增量包装类*/
public class IncrementDO<T> implements Serializable {

    private static final long serialVersionUID = -6615881328594646517L;

    /** 删除数据列表 **/
    private final List<T> deletedList;

    /** 增加数据列表 **/
    private final List<T> addedList;

    /** 时间戳 **/
    private final Long timestamp;

    public IncrementDO(List<T> deletedList, List<T> addedList, Long timestamp) {
        super();
        this.deletedList = deletedList;
        this.addedList = addedList;
        this.timestamp = timestamp;
    }
    public List<T> getDeletedList() {
        return deletedList;
    }
    public List<T> getAddedList() {
        return addedList;
    }
    public Long getTimestamp() {
        return timestamp;
    }
    @Override
    public String toString() {
        return "IncrementDO[deletedList=" + deletedList.size() + ", addedList=" + 
               addedList.size() + ", timestamp=" + timestamp + "]";
    }
}
public interface ConfigService {
     /**用户拥有在timeStamp之前的正确数据*/
     public IncrementDO<Long> getIncrementUserIds(long timeStamp) 
          throws ServiceException;
}     

public class ConfigServiceImpl implements ConfigService{
     UserManager userManager;
     public IncrementDO<Long> getIncrementUserIds(long timeStamp) 
          throws ServiceException{
          if(timeStamp < 0) {
               throw new ServiceException("timestamp is wrong");
          }
          try{
               return userManager.getIncrementUserIds(timeStamp);
          } catch (ManagerException e) {
               throw new ServiceException(e);
           }
     }
}
/**ibatis sql,用STATUS=1表示数据有效,STATUS=99表示数据已删除*/
<select id="getAddedUserIds" resultClass="java.lang.Long"
     parameterClass="java.util.Map">
     <![CDATA[
     SELECT USER_ID
     FROM user
     WHERE GMT_MODIFIED  >= #begin# and 
          GMT_MODIFIED <= #end# and STATUS = 1
      ]]>
</select>

<select id="getDeletededUserIds" resultClass="java.lang.Long"
     parameterClass="java.util.Map">
     .....
</select>
public interface UserManager{
     /**用户拥有在timeStamp之前的正确数据*/
     public IncrementDO<Long> getIncrementUserIds(long timeStamp) 
          throws ManagerException;
}     
public class UserManagerImpl implement UserManager {
     UserDAO userDAO;LocalCache localCache;
          /**用户拥有在timeStamp之前的正确数据*/
     public IncrementDO<Long> getIncrementUserIds(long timeStamp) 
          throws ManagerException {
          long ts = this.userDAO.getDBTimestamp(); // db 当前时间戳

          Calendar reqTime = Calendar.getInstance();         // 请求时间
          reqTime.setTime(new Date(timestamp));
          reqTime.set(reqTime.get(Calendar.YEAR), reqTime.get(Calendar.MONTH),  
                reqTime.get(Calendar.DAY_OF_MONTH),
                reqTime.get(Calendar.HOUR_OF_DAY),                 
                reqTime.get(Calendar.MINUTE),
                 0); // 秒清零
   
          Calendar curTime = Calendar.getInstance();     // db当前时间
          curTime.setTime(new Date(ts));
          curTime.set(curTime.get(Calendar.YEAR), curTime.get(Calendar.MONTH), 
               curTime.get(Calendar.DAY_OF_MONTH),
         curTime.get(Calendar.HOUR_OF_DAY), 
               curTime.get(Calendar.MINUTE), 
               0); // 秒清零
          
          long diffTime = curTime.getTimeInMillis() - reqTime.getTimeInMillis();
          if (diffTime < 0) {
               throw new ManagerException("timestamp is wrong");
          }

          IncrementDO<Long> tmp;        
          Set<Long> add = new HashSet<Long>();
          Set<Long> del = new HashSet<Long>();
          long minCount = diffTime / (60 * 1000); // 相差分钟数          

          for (long i=0; i< minCount; i++) {     // 遍历相差的分钟数
        tmp = null;
        tmp = this.getSignalMinitIncreamentUserIds(
                        reqTime.getTimeInMillis(), reqTime.getTimeInMillis() + 60000);
        if (tmp != null) {
              del.removeAll(tmp.getAddedList());
              add.addAll(tmp.getAddedList());
              add.removeAll(tmp.getDeletedList());
              del.addAll(tmp.getDeletedList());
        }
        reqTime.add(Calendar.MINUTE, 1);
          }
          IncrementDO<Long> result = new IncrementDO<Long>(
               new ArrayList<Long>(del), 
               new ArrayList<Long>(add), 
               curTime.getTimeInMillis() );
          return result;
     }
     private IncrementDO<Long> getSignalMinitIncreamentUserIds(
         long beginTimestamp, long endTimestamp) throws DAOException {
           IncrementDO<Long> result = (IncrementDO<Long>)  
                localCache.get(Namespace.INC_USERIDS+"_"
                +beginTimestamp + "_" + endTimestamp);
           if (result == null) {
                 List<Long> addList = this.userDAO.getAddedUserIds(
                      beginTimestamp, endTimestamp);
                 List<Long> deletedList = this.userDAO.getDeletedUserIds(
                      beginTimestamp, endTimestamp);
                 result = new IncrementDO<Long>(deletedList, addList, endTimestamp);
                 localCache.put(Namespace.INC_USERIDS+"_"
                      +beginTimestamp + "_" + endTimestamp, result);
           }
           return result;
     }
}

针对增量请求的缓存机制实现