论坛首页 Java企业应用论坛

缓存框架之AOP渐进实现

浏览 12931 次
精华帖 (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是最新数据就可以了。

不知道我的理解对不对


对于线程安全的问题,我的回复已经给出了答案,对Key加锁,然后双重检查。或者对CacheObject加层包装,引入乐观锁,然后在Cache中加入悲观锁,这样可以很大程度减少锁的持有时间

 

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这样的实现。至于其它的数据不太稳定的情况不太适合扔进缓存。其实看了这么多,我还真的没有发现需要非只读缓存的实际场景,大家可以一起探讨一下,有这样的实际场景吗

 

0 请登录后投票
   发表时间: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,可以毫不夸张地讲,楼主贴的代码越多,我就会发现越多的问题,但楼主却总是有藉口和理由,我也就没必要再浪费时间了。
0 请登录后投票
   发表时间: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如何优雅的切入到代码中去么?
0 请登录后投票
   发表时间: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之所有有用的关键:它掌握着数据的逻辑,所以能妥善处理缓存问题。



0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics