把Repository上升到框架时,实体平铺遇到问题

一旦实现平铺,那么缓存的“<唯一标识,实体>对”就不能替换实体对象了,所有修改都要基于值对象(实体状态)修改。

不能替换的理由:cache是以替换对象来更新“key,value对”的,B1聚合于A1(A1内聚B1),A1和B1都缓存,那么怎么修改cache对B1的缓存,也不会改变A1内聚B1的关系,不过实现方法还是有的,通过引入注册方式,缓存一个实体(A1)时,注册其内在所有实体与其的关系(B1与A1的关系),当一个其内在的实体更新了,则更新所有关注它(内聚它并已经缓存)的实体,不过……这方法貌似太沉重了,有没有更好的思路?

实现替换的目的在于:通过复制替换实现并行(快照,替身思维)。

替换与不替换的实现差别:替换是以唯一标识为关联关系,不替换则是以对象为关联关系(因为对象中包含唯一标识的关联关系,所以省去了步骤)。替换,同一实体会被缓存多次,不替换,只有头一次缓存,只有驱逐后,才能再缓存,所有修改都是基于同一个对象。
[该贴被SpeedVan于2011-03-05 12:56修改过]

2011年03月05日 12:34 "SpeedVan"的内容
cache是以替换对象来更新“key,value对”的,B1聚合于A1(A1内聚B1),A1和B1都缓存,那么怎么修改cache对B1的缓存,也不会改变A1内聚B1的关系 ...

恭喜你碰到本质问题了,对象(数据)之间的关系是最难处理的,原来JBOss Cache是可以的,但是性能不好,想想看遍历一个树多耗费时间啊,这也是基于关系数据库的缓存的本质问题。

NoSQL去除了这个关系,应该说实现了平铺,但是一些关系是无法简单用去除来对付的。

因为对象的获取是基于引用的,与实体基于唯一标识不同。所以引起了上述问题,若果一个cache基于唯一标识建立树的话,如果真如你所说的效率会产生问题,那么还有什么方式改变这种情况呢?还是要从根本上改变思维?

没有办法,这是终极难题,属于没有一条裤子适合所有人,没有一个解决方案适合所有情况。

大家很烦Java的null报错,对象中无限个嵌套如何自动解决null报错,也就是说,A对象中有子对象的B,B有C,如果我要引用C,但是B是空的,Java就报错,这个问题很烦恼,必须我们自己手工来做判断。

B为null?若果是聚合根获得,A不会直接调用C吧?若果是平铺后直接获取,C也可以从缓存中获得才对啊?若果C是B的实体状态(值对象),那应该C随B的存在而存在才对?

我们想的问题可能不太一样。

我的问题主要在于,实体间的关系在cache中的处理,cache是只考虑你传给他的对象引用,但不会考虑内部结构的。平铺的目的在于方便读取,


entityB内聚于entityA
cache.put(entityAKey,entityA);
cache.put(entityBKey,entityB);

cache.put(entityBKey,[b]entityB2[/b]);
//但由于之前说到的
//不能通过cache.put(entityBKey,[b]entityB2[/b]);而更改“entityB内聚于entityA”为“entityB2内聚于entityA”;


当然,根据聚合根的定义话,这样是不可取的,因为其回避了聚合根,而进行修改。但这样说的话,感觉平铺失去意义了。

平铺的好处在于,子实体的缓存周期不需要等于父实体,感觉有点不可思议,这是因为缓存失效,并不等于消失(删除)。


[该贴被SpeedVan于2011-03-05 17:25修改过]
[该贴被SpeedVan于2011-03-05 17:34修改过]

貌似JbossCache符合要求,先试用下

这种问题属于设计上的问题,之前也讨论过,没有办法解决,想依靠某个底层框架解决的话,这样估计不靠谱,我想性能也不会上去。

另外使用缓存,首先要明确目的,缓存中的东西一般是不频繁修改的,频繁修改的东西,放在缓存,主要会遇到两方面的问题:

第一方面要考虑并发更新原子性的问题,比如你使用了远程cache,一般你更新的方式是cache 服务器获取对象,更改,然后放回到远程cache服务器,这种并发控制的原子性是很难做到的,除非一些比如++,--类的,memcached等缓存能解决原子性的问题,一些复杂的对象操作,没有办法保证原子性。可能你会说我不使用remote cache,使用local cache,但是这样在前段web server有N台的时候,你怎么保证这N台服务器内存中的对象都是一致的呢?也解决不了。

第二方面缓存中内容一致性的问题,就是楼主所提的问题。

因此频繁修改的东西放入缓存,除了以上的问题,另外还会导致缓存命中率极低,既然命中率那么多,缓存有意义吗?
所以解决楼主的问题,不要从技术的角度去解决,从你业务的角度去思考,业务上对数据一致性的要求是否很高,如果很高的话,那么可以采用首先更新底层持久源,然后清除缓存的方法,如果一致性要求不高,比如你根据业务,用户可感知的数据的一致性窗口可以为2小时,那么你这个时候,更新了底层数据即可,缓存可以设置缓存失效时间来失效之。

总之,这种问题,不要企图从技术的角度去解决问题,得看业务场景。

关于原子性,我的方式是通过复制,替换方式实现(对象不变性),并不是通过修改对象,所以就如我之前说到的,可能会产生缓存频繁的修改(缓存对象替换)。至于N个服务器,对象是否一致,这个我也没办法,只能依赖缓存自身的同步技术了。

频繁修改的对象放入缓存?不是的,不是指对象频繁修改,而是“key,value对”的value可能会频繁替换。关于直接更新数据源方面,我的方式是通过队列更新,没有直接数据源更新,获取也是通过队列。而且频繁修改的东西放入缓存,不也是证明频繁获取了吗,正是需要命中它,放入缓存有何不妥?因为同步问题?嗯~对于localCache这的确是一个问题。不过我想问的是,既然更新了数据源,为什么不把缓存也更新,而是清除呢?

2011年03月06日 13:16 "SpeedVan"的内容
关于原子性,我的方式是通过复制,替换方式实现(对象不变性),并不是通过修改对象,所以就如我之前说到的,可能会产生缓存频繁的修改(缓存对象替换)。 ...

哥们,不是太理解,你说的复制,替换的技术,举个例子,假如有2台web服务器和一台远程cache服务器,这个时候更新一般是从远程cache获取数据,修改,然后放回,假如同时有两个人从不同的web服务器修改远程数据的话,怎么做到原子性呢?

另外为什么要清楚而不是更新,更新必然会引来一个并发同步的问题,这无疑给系统复杂性的同时,也影响系统的伸缩性,因此直接清楚,下次直接从数据源获取,问题也不会很大,毕竟更新不是很频繁。

2011年03月06日 13:42 "xmuzyu"的内容

哥们,不是太理解,你说的复制,替换的技术,举个例子,假如有2台web服务器和一台远程cache服务器,这个时候更新一般是从远程cache获取数据,修改,然后放回,假如同时有两个人从不同的web服务器修改远程数据的话,怎么做到原子性呢? ...

把复制,修改,替换中,复制和修改都看作和替换同一时刻。若果是同时获取,那么就相当于各自拿着一份副本,自己修改,然后提交替换。当然这里这两次操作是没有先后依赖的。若果需要先后依赖,则需要引入版本判断,例如旧版本和新版本一起提交,那么后提交者将提交失败。

数据库、缓存保存的是实体状态(对象状态),状态只是最新状态。而且cache是保存状态,远程获取和提交也是状态。至于类似投票类的就有点不一样了,这些是动作,提交的是“+1”动作,而并非一个状态,所以这些就有点不一样了。于是也就是在队列中加入的是动作事件,而并非更新状态事件。
[该贴被SpeedVan于2011-03-06 14:26修改过]

2011年03月06日 14:07 "SpeedVan"的内容
把复制,修改,替换中,复制和修改都看作和替换同一时刻。若果是同时获取,那么就相当于各自拿着一份副本,自己修改,然后提交替换。当然这里这两次操作是没有先后依赖的。若果需要先后依赖,则需要引入版本判断,例如旧版本和新版本一起提交,那么后提交者将 ...

这岂不是会出现更新丢失的问题?

2011年03月06日 14:37 "xmuzyu"的内容
这岂不是会出现更新丢失的问题? ...

因为是要求先后依赖,那么也就是后提交者原来得到的副本是无效的。例如报名,9/10的名额情况,两人同时获取,提交先后也就决定了有效无效了。当然还有一种是投票,其实投票提交的是动作事件,它与状态无关。想不丢失提交,要么没有先后依赖,要么提交的是动作。

其实更新数据源也是同样的情况啊,获取的都是同一个状态,若果两个web服务器不同步,提交的两个状态也是无关的,那么也只能是后者来覆盖前者的数据。
[该贴被SpeedVan于2011-03-06 16:02修改过]