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

背景:

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

设计概述:

使用缓存的关键之一是:缓存并不能保证实时性,但它提供的是曾经出现过的某个正确状态。(csdn大量使用缓存,且失效时长过长,导致博客专区中很多操作执行后很久都得不到页面上的更新。)

在对实时性要求、缓存效果、实现难度、业务场景这四点的权衡和折中后,我选择了增量的缓存粒度为1分钟。意思就是说对每个1分钟内的增量进行缓存的存储。

除此之外,我们使用了AOP技术进行代码的复用,以及对业务代码的解耦,并且使用Annotation技术对增量方法进行标记。

详细设计:

  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 + "]";
    }
}
/**Annotation*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LocalIncreaCacheOperation {
}
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{
               long ts = this.userManager.getDBTimestamp(); // db 当前时间戳
              return this.getIncrementUserIds(timeStamp,ts);
         } catch (ManagerException e) {
              throw new ServiceException(e);
          }
    }

    /**
     * 增量缓存的辅助接口,AOP将拦截这个接口
     */
    @LocalIncreaCacheOperation
    public IncrementDO<Long> getIncrementUserIds(long beginTimesStamp,
		Long endTimesStamp) throws ServiceException{
         try{
              IncrementDO<Long> result = null;
             List<Long> addList = this.userManager.getAddedUserIds(
                       beginTimesStamp, endTimesStamp);
           List<Long> deletedList = this.userManager.getDeletedUserIds(
                     beginTimesStamp, endTimesStamp);
           result = new IncrementDO<Long>(deletedList, addList, endTimesStamp);
           return result;
         }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>
/**
* 核心切面实现
*/
@Aspect
public class LocalIncreaCacheAspect {
     private LocalCache localCache;

     @Around("execution(@LocalIncreaCacheOperation * *(..))")
     public Object doCache(ProceedingJoinPoint pjpParam) throws Throwable {
          final ProceedingJoinPoint pjp = pjpParam;
          Signature sig = pjp.getSignature();
          if (sig instanceof MethodSignature) {
               MethodSignature mSig = (MethodSignature) sig;
               String localCacheKey = mSig.getName();
               Long beginTimesStamp = (Long) pjp.getArgs()[0];
               Long endTimesStamp = (Long) pjp.getArgs()[1];

               Calendar reqTime = Calendar.getInstance(); // 请求时间
               reqTime.setTime(new Date(beginTimesStamp));
               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(endTimesStamp));
               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 ServiceException("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 = (IncrementDO<Long>) localCache.get(localCacheKey + "_"
                              + reqTime.getTimeInMillis() + "_"
                              + (reqTime.getTimeInMillis() + 60000));
                    if (tmp == null) {
                         tmp = (IncrementDO<Long>) pjp.proceed(new Object[] {
                                   reqTime.getTimeInMillis(),
                                   reqTime.getTimeInMillis() + 60000 });
                         localCache.put(localCacheKey + "_"
                                             + reqTime.getTimeInMillis() + "_"
                                             + (reqTime.getTimeInMillis() + 60000), tmp);
                    }
                    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 pjp.proceed(pjp.getArgs());
     }
}

时间: 2024-10-27 12:14:48

针对增量请求的缓存机制实现 - AOP的相关文章

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

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

Android三级缓存机制工具类的实现

一.三级缓存概述 (一)三级缓存的三级 第一级是内存,最快,不需要网络 第二级是本地,不需要网络 第三级是网络,需要网络请求 三级缓存机制的思想: 如果在内存中获取到数据,就不去本地和网络中获取. 如果在本地中获取到数据就不去网络中获取, 如果内存和本地中不存在数据,就要去网络中请求数据 三级缓存技术能有效节省用户的流量,但是也会增加一些内存负担. 二.使用示例展示三级缓存工具栏类的使用 程序运行后的页面: 虽然只用一个按钮和一个图片显示,但是通过测试(联网状态和断网状态对比)能知道图片是从网络

线程安全的缓存机制 - AOP设计与实现

最近几天由于工作原因,需要设计实现一个线程安全的缓存机制,拿出来和大家分享交流一下. 应用背景: 缓存是在实际工作中经常用到的,主要作用呢?1. 提高响应速度 2. 降低cpu压力或者数据库压力. 在此,我的应用背景是拦截一些RFC请求(不要求获取实时数据),以降低数据库及自身应用的访问压力. 目标: 高可扩展性:可以方便配置需要使用缓存的方法. 线程安全性:在并发情况下,要求线程安全,且尽可能高效. 使用技术: 使用AOP的插件性质来降低缓存与原系统的耦合性,即在切面层做缓存的处理. 使用An

contenttype组件、Django缓存机制以及跨域请求

1 昨日回顾 版本控制 *** (1)url=127.0.0.1/course/?version=v100000 1 versioning_class=QueryParameterVersioning 'VERSION_PARAM':'version', 'DEFAULT_VERSION':'v2', 'ALLOWED_VERSIONS':['v1','v2'] 2 配置成全局:在setting里:QueryParameterVersioning (2)重要(以后建议用这种):127.0.0.1

IE 针对ajax get请求的缓存问题

在chrome,ff中发送ajax请求get内容时没有缓存的现象,但是在IE中会对请求进行缓存,从而导致更新的内容没有改变的问题. 如果,你把CPU的阈值设置为25了,前面页面也提示设置成功了,但是CPU的阈值却还是原来的50.调试时,会发现返回的值确实是原来的值50而不是你最新设置的值25.你换到chrome浏览器却没有这种现象. 结论: IE浏览器对ajax请求进行了缓存. 解决办法: 方法1: url地址后添加随机后缀进行url欺骗 xmlhttp.open("GET",&quo

NET下三种缓存机制(Winform里面的缓存使用 )

原文(http://www.cnblogs.com/wuhuacong/p/3526335.html)非常感谢伍华聪作者的分享! 缓存在很多情况下需要用到,合理利用缓存可以一方面可以提高程序的响应速度,同时可以减少对特定资源访问的压力.本文主要针对自己在Winform方面的缓存使用做一个引导性的介绍,希望大家能够从中了解一些缓存的使用场景和使用方法.缓存是一个中大型系统所必须考虑的问题.为了避免每次请求都去访问后台的资源(例如数据库),我们一般会考虑将一些更新不是很频繁的,可以重用的数据,通过一

手把手教你构建 Android WebView 的缓存机制 &amp; 资源预加载方案

前言 由于H5具备 开发周期短.灵活性好 的特点,所以现在 Android App大多嵌入了 Android Webview 组件进行 Hybrid 开发 但我知道你一定在烦恼 Android Webview 的性能问题,特别突出的是:加载速度慢 & 消耗流量 今天,我将针对 Android Webview 的性能问题,提出一些有效解决方案. 目录 1. Android WebView 存在什么性能问题? Android WebView 里 H5 页面加载速度慢 耗费流量 下面会详细介绍. 1.

微服务架构下静态数据通用缓存机制

在分布式系统中,特别是最近很火的微服务架构下,有没有或者能不能总结出一个业务静态数据的通用缓存处理机制或方案,这篇文章将结合一些实际的研发经验,尝试理清其中存在的关键问题以及探寻通用的解决之道. 什么是静态数据 这里静态数据是指不经常发生变化或者变化频率比较低的数据,比如车型库.用户基本信息.车辆基本信息等,车型库这种可能每个月会更新一次,用户和车辆基本信息的变化来源于用户注册.修改,这个操作的频率相对也是比较低的. 另外这类数据的另一个特点是要求准确率和实时性都比较高,不能出现丢失.错误,以及

静态数据通用缓存机制

静态数据通用缓存机制 http://blog.bossma.cn/architecture/microservice-business-static-data-universal-cache-mechanism/ 微服务架构下静态数据通用缓存机制 在分布式系统中,特别是最近很火的微服务架构下,有没有或者能不能总结出一个业务静态数据的通用缓存处理机制或方案,这篇文章将结合一些实际的研发经验,尝试理清其中存在的关键问题以及探寻通用的解决之道. 什么是静态数据 这里静态数据是指不经常发生变化或者变化频