锁定老帖子 主题:缓存框架之AOP渐进实现
精华帖 (1) :: 良好帖 (3) :: 新手帖 (0) :: 隐藏帖 (7)
|
|
---|---|
作者 | 正文 |
发表时间:2010-07-21
jansel 写道
whitesock 说的场景可以概括成:
A、B、C代表线程 A从缓存中获取了i(10),然后减1,把10-1=9放入缓存; B从缓存中获取了i(10),然后加1,把10+1=11放入缓存; C从缓存中获取了i(10),然后乘2,把10*2=20放入缓存; 问题就是,不清楚A、B、C何时结束,所以也就不知道9、11、20哪个是最新的数据了。 如果要保证更新是按照多线程触发的顺序来的话,那么应该是对i加锁,而不是应该在Cache的get和put上加锁。 对i加锁的意思是,加、减、乘都是i的方法,在这些方法上加锁。 此时Cache只需要知道最后调用的put是最新数据就可以了。 不知道我的理解对不对
public Element get(Object key){ synchronized(lockSource.getLock(key)){ Element element = getFromCache(key); if(element == null){ element = new Element(key); putInCache(element); } return element; } } public void put(Element element){ synchronized(lockSource.getLock(element.key())){ Element cacheElement = getFromCache(key); if(cacheElement == null){ putInCache(element); }else{ if(element.version() >= cacheElement.version()){ putInCache(element); }else{ throw RefreshException(); } } } } public Object invoke(MethodInvocation methodInvocation) throws Throwable { ........ Element element = get(cacheKey); if(element.value() == null){ element.value(methodInvocation.proceed()); try{ put(element); }catch(RefreshException e){ //donothing } } return element.value(); } 至于缓存对象的克隆保护,因为我目前需要一种自动Flush的机制,所以没有提供unmodifed这样的实现。至于其它的数据不太稳定的情况不太适合扔进缓存。其实看了这么多,我还真的没有发现需要非只读缓存的实际场景,大家可以一起探讨一下,有这样的实际场景吗
|
|
返回顶楼 | |
发表时间:2010-07-21
最后修改:2010-07-21
潜在问题1:
一旦methodInvocation.proceed()的返回值为null,那么基于element.value() == null便不够健壮。 潜在问题2: element.value(methodInvocation.proceed()):对value的赋值存在潜在的可见性问题。 潜在问题3: 1 invoke方法中,两个不同的线程以相同的cacheKey调用get(cacheKey)。假设两个线程都得到相同的element引用,并且element.value() 为null。 2 两个线程可能同时调用methodInvocation.proceed()。 3 假设A线程调用methodInvocation.proceed(),返会a;B线程调用methodInvocation.proceed(),返会b; 4 最终不能确定放入缓存到底是a或b。如果最终放入的是a,而b是最新版本的数据,那么导致后续操作得到不正确的值。 建议: 1 看看concurrent包 2 参考一下spring-modules-cache 平常很少在一个帖子上花这么多时间,这么做的原因是笔者曾开发过一个分布式的非只读缓存,深知其深浅。我一再强调缓存的正确性,大概是因为一直从事金融相关的项目(信贷和FX,特别是FX,一个微小的错误都可能造成上千万的损失)的原因。 在这里指出楼主的不足,是因为楼主目前的实现还有long way to go,可以毫不夸张地讲,楼主贴的代码越多,我就会发现越多的问题,但楼主却总是有藉口和理由,我也就没必要再浪费时间了。 |
|
返回顶楼 | |
发表时间:2010-08-12
whitesock 写道 潜在问题1:
一旦methodInvocation.proceed()的返回值为null,那么基于element.value() == null便不够健壮。 潜在问题2: element.value(methodInvocation.proceed()):对value的赋值存在潜在的可见性问题。 潜在问题3: 1 invoke方法中,两个不同的线程以相同的cacheKey调用get(cacheKey)。假设两个线程都得到相同的element引用,并且element.value() 为null。 2 两个线程可能同时调用methodInvocation.proceed()。 3 假设A线程调用methodInvocation.proceed(),返会a;B线程调用methodInvocation.proceed(),返会b; 4 最终不能确定放入缓存到底是a或b。如果最终放入的是a,而b是最新版本的数据,那么导致后续操作得到不正确的值。 建议: 1 看看concurrent包 2 参考一下spring-modules-cache 平常很少在一个帖子上花这么多时间,这么做的原因是笔者曾开发过一个分布式的非只读缓存,深知其深浅。我一再强调缓存的正确性,大概是因为一直从事金融相关的项目(信贷和FX,特别是FX,一个微小的错误都可能造成上千万的损失)的原因。 在这里指出楼主的不足,是因为楼主目前的实现还有long way to go,可以毫不夸张地讲,楼主贴的代码越多,我就会发现越多的问题,但楼主却总是有藉口和理由,我也就没必要再浪费时间了。 看了各位的讨论,感触很多,我们公司最近在用memcached做业务逻辑的非只读缓存,由于我们的项目是做的网站开发,对于有些业务的一致性强调不是很高,没有发现过数据一致性的问题。 目前,我和楼主的思想是一致的,把setcache,deletecache分别做成切面,切入到业务层中。 但是现在遇到一个问题,就是对于key值的选定,目前使用的是calss+parameters,但是这样造成的问题就是切面不够通用,每次在创建key的时候需要耦合到方法的参数中去,删除key的时候同样还需要对key的创建重新编写相关的逻辑。 不知道大家有兴趣讨论下memcached如何优雅的切入到代码中去么? |
|
返回顶楼 | |
发表时间:2010-09-27
gigi_112 写道 whitesock 写道 潜在问题1:
一旦methodInvocation.proceed()的返回值为null,那么基于element.value() == null便不够健壮。 潜在问题2: element.value(methodInvocation.proceed()):对value的赋值存在潜在的可见性问题。 潜在问题3: 1 invoke方法中,两个不同的线程以相同的cacheKey调用get(cacheKey)。假设两个线程都得到相同的element引用,并且element.value() 为null。 2 两个线程可能同时调用methodInvocation.proceed()。 3 假设A线程调用methodInvocation.proceed(),返会a;B线程调用methodInvocation.proceed(),返会b; 4 最终不能确定放入缓存到底是a或b。如果最终放入的是a,而b是最新版本的数据,那么导致后续操作得到不正确的值。 建议: 1 看看concurrent包 2 参考一下spring-modules-cache 平常很少在一个帖子上花这么多时间,这么做的原因是笔者曾开发过一个分布式的非只读缓存,深知其深浅。我一再强调缓存的正确性,大概是因为一直从事金融相关的项目(信贷和FX,特别是FX,一个微小的错误都可能造成上千万的损失)的原因。 在这里指出楼主的不足,是因为楼主目前的实现还有long way to go,可以毫不夸张地讲,楼主贴的代码越多,我就会发现越多的问题,但楼主却总是有藉口和理由,我也就没必要再浪费时间了。 看了各位的讨论,感触很多,我们公司最近在用memcached做业务逻辑的非只读缓存,由于我们的项目是做的网站开发,对于有些业务的一致性强调不是很高,没有发现过数据一致性的问题。 目前,我和楼主的思想是一致的,把setcache,deletecache分别做成切面,切入到业务层中。 但是现在遇到一个问题,就是对于key值的选定,目前使用的是calss+parameters,但是这样造成的问题就是切面不够通用,每次在创建key的时候需要耦合到方法的参数中去,删除key的时候同样还需要对key的创建重新编写相关的逻辑。 不知道大家有兴趣讨论下memcached如何优雅的切入到代码中去么? 是有这样的问题。 whitesock很关注缓存的正确性,确实在金融行业这是必须的。 而互联网行业一般不在乎,在我的应用场景里,根本不需要关系一些细微的数据不一致,后面的缓存值直接覆盖前面的,没问题,就这么干。 但是缓存的更新楼主没考虑到,基本还不具备实用价值。 你提出的key删除就是个棘手的事。 用AOP拦截,key的生成就取决于参数,通用的方式就是楼主做的,对query方法的Method,Args做信息摘要得到一个key。 但我有时候还需要拦截另一个delete方法来删除缓存,显然这个delete方法生成的key和query肯定不一样。 @CacheInput String queryTopicByName(String name); @CacheDelete void deleteTopicById(long id) 两个参数风马牛不相及,后面的delete会影响query的结果,但这个关系是极其隐晦的,通过方法参数无法识别。 此路不通! 稳妥的方案是由调用者来决定key怎么生成,如此则AOP的意义不大。 这就是hibernate之所有有用的关键:它掌握着数据的逻辑,所以能妥善处理缓存问题。 |
|
返回顶楼 | |