库存Repostiory连带事务的解决方案

项目开发中遇到了类似论坛评论的问题
需求是维护产品的库存可用数量(需要经过计算,如库存数-预订数等等),在销售时改变库存数,考虑缓存的解决方案,

论坛评论解决方案:初次调用时缓存,以后都从缓存读写
库存解决方案:在销售时更新库存数,同时有其他的事务处理,如创建销售单,通知下一级人员处理等等。此处与论坛评论不同,更新库存是事务的一部分,有rollback的情况要处理,而缓存没有这样的能力...

请教此时该如何设计呢?

2010年05月05日 11:26 "Foxswily"的内容
更新库存是事务的一部分,有rollback的情况要处理,而缓存没有这样的能力 ...

缓存就是内存,不是说内存没有这个能力,可以使用语言锁等机制;你可以在更新数据库库存事务中,同时更新缓存中库存数据,你要把缓存看成是数据库的影子,shadow即可。

纠结的地方在于,数据库事务在提交之前,其他的线程是看不到变化的,而缓存一旦变更,在这个时点其他读线程是可以访问的,这就造成了不同步。
比如:
当前库存:100
新销售:10
事务过程
开始事务
更新库存(此时是缓存)
数据库插入销售记录
其他事务处理
提交事务

可能的情况
1.数据库更新成功,缓存更新成功——OK
2.数据库更新失败,缓存需要rollback——至少需要对缓存额外的处理
3.缓存更新失败,数据库rollback——OK
以上各种情况都存在瞬间的数据不同步。

语言锁机制能很好的解决这个问题吗?可以想到的是对缓存的读写设置锁机制。但缓存更新后、提交前这段时间的状态呢?需要缓存提供事务机制?

既然使用缓存, 那么就应当保证缓存的数据是最新的, 通过异步来更新数据库.
涉及到事务时, 可以利用锁机制:
如果是单JVM简单的线程同步即可,
如果是多JVM/分布式, 那就需要在JVM级别同步(这个机制貌似比较复杂), 也可以参考CAP/BASE, 只需要eventual cosistence即可

另外, 数据库事务保证了在没有错误的时候才提交事务, 那缓存机制也可以在没有异常的时候才更新缓存, 所以说rollback与否与缓存没有什么直接的关系.
--菜鸟级回复
[该贴被icycrystal4于2010-05-05 13:37修改过]

2010年05月05日 12:51 "Foxswily"的内容
数据库更新成功,缓存更新成功——OK
2.数据库更新失败,缓存需要rollback——至少需要对缓存额外的处理
3.缓存更新失败,数据库rollback——OK
以上各种情况都存在瞬间的数据不同步。 ...

其实这是一致性问题,一般缓存也就是内存更新不会失败,写内存有失败过吗?除非掉电或硬件质量不好,这就要容错性failover来实现,一般是没有必要的。

第2种和第3种发生的可能性是极其低的,为了再降低这个概率,你在启动数据库写事务之前,清除缓存,不是更新缓存,事务结束后,由第一个访问这个数据的请求从数据库获得,然后再放到缓存中。

当然,这有脏读发生,也就是你写事务进行中,有读发生,可以通过数据库的ACID来实现。

总之,你一致性要求越高,可用性,也就是性能可能越差。

2010年05月05日 13:33 "icycrystal4"的内容
另外, 数据库事务保证了在没有错误的时候才提交事务, 那缓存机制也可以在没有异常的时候才更新缓存, 所以说rollback与否与缓存没有什么直接的关系. ...

没有异常才更新,这就包含了rollback的情况,怎么能说rollback与缓存没关系呢?不要把rollback理解成db才有的东西,在这里可以理解成缓存的回滚。
比如:缓存更新了,后续的DB更新失败,那么DB会rollback,同样,缓存也要保持一致,也需要rollback。

2010年05月05日 13:41 "banq"的内容
总之,你一致性要求越高,可用性,也就是性能可能越差。 ...

确实如此,正在为现行系统大量存在的类似逻辑找优化方案。

2010年05月05日 13:41 "banq"的内容
你在启动数据库写事务之前,清除缓存,不是更新缓存,事务结束后,由第一个访问这个数据的请求从数据库获得,然后再放到缓存中。当然,这有脏读发生,也就是你写事务进行中,有读发生,可以通过数据库的ACID来实现。

现在的解决办法就是靠锁表或check时间戳,增加了不少复杂度。但是,这样还有使用缓存的必要性吗?直接把库存数存入表岂不是更直观?

主要看你的业务场景,看看你缓存的东西是否被经常使用,是否是所有用户都使用的,这样命中率会好一点。还有就是内存和DB回滚的问题,一般为了保证数据域DB一致,可以像banq老师说的,先清除缓存。同时缓存也有读缓存和写缓存,读缓存要求更新不要太频繁,如果你的更新很频繁,要考虑是否需要缓存。而写缓存一般是定时异步刷新缓存到DB。

2010年05月05日 14:28 "xmuzyu"的内容
主要看你的业务场景,看看你缓存的东西是否被经常使用,是否是所有用户都使用的,这样命中率会好一点。还有就是内存和DB回滚的问题,一般为了保证数据域DB一致,可以像banq老师说的,先清除缓存。同时缓存也有读缓存和写缓存,读缓存要求更新不要太频 ...

恩,要翻回头看看使用缓存这种解决方案是否可行了。想用这种方式就是参考了论坛评论的解决方案,类似的把当前可用库存数缓存起来。这样尽量把对库存的读写独立出来。否则对库存的读写总要考虑同步问题,太繁琐。
现在看,要找别的出路了,比如DAO级别的共通,传递connection来保证事务一致性。

2010年05月05日 13:50 "Foxswily"的内容
没有异常才更新,这就包含了rollback的情况,怎么能说rollback与缓存没关系呢?不要把rollback理解成db才有的东西,在这里可以理解成缓存的回滚。
比如:缓存更新了,后续的DB更新失败,那么DB会rollback,同样,缓存 ...

我有这样一个理解, 需要更新缓存, 那其在业务逻辑上不会有问题, 缓存更新失败只有一种原因, 像banq大哥说的, 硬件问题(这个我想是不需要考虑的), 即缓存更新应该不存在rollback的问题;
至于说缓存更新成功, 后续的DB更新失败. 有一个假设, 即在业务逻辑正常的情况下, DB更新失败也只有一个原因, 就是硬件问题(包含网络问题), 出现这种问题应该有一种机制确保在硬件问题恢复的时候重新让缓存与数据库同步.

PS: 可能上面描述的漏洞百出, 敬请大家指正:)

2010年05月05日 15:17 "icycrystal4"的内容
DB更新失败也只有一个原因, 就是硬件问题(包含网络问题), ...

DB更新失败的原因很多,比如数据重复,逻辑check失败等等,可不单单只有硬件因素。缓存更新失败的可能性确实很小,即便失败也好处理。

2010年05月05日 15:42 "Foxswily"的内容
比如数据重复,逻辑check失败等等 ...


这些似乎都是属于数据检验范畴, 这些数据是不允许通过更新进入缓存, 从而通过异步方式同步到数据库的.

2010年05月05日 16:12 "icycrystal4"的内容
这些似乎都是属于数据检验范畴, 这些数据是不允许通过更新进入缓存, 从而通过异步方式同步到数据库的. ...

以时间戳举例,在更新时以时间戳为条件更新,结果返回更新记录数为0,此时报错,这种情况就需要缓存回滚。举一反三一下嘛。
话说回来,不论DB什么情况的回滚,缓存势必需要一起回滚(可以类比全局事务)。

另外,JTA中对JMS的处理方式是在其他事务提交前暂缓执行,其他事务成功了才发消息。缓存有点另类,需要高一致性,这么做不保险。

2010年05月05日 16:33 "Foxswily"的内容
以时间戳举例,在更新时以时间戳为条件更新,结果返回更新记录数为0,此时报错,这种情况就需要缓存回滚。举一反三一下嘛。

我估计我没表达清楚, 我有一个完全属于个人的想法: 缓存不需要rollback. 理由如下:
1. 缓存更新不会出错;(似乎过于理想化)
2. 如果缓存出现需要"rollback", 那只能说明这些数据在逻辑上是脏数据(这个应该在更新前校验), 不应该进行缓存更新.
打个比方, 就像往数据库里面插入条数据, 其ID与现有的数据冲突, 似乎数据库需要rollback, 但我想说的是, 我们不应该通过数据库的这种主键机制来确保数据的一制性, 而是应该在数据入库之前就应该发现, 噢, 数据库已经存在这条数据了, 我不应该插入.
我不敢说利用数据库来校验数据不好, 但我只是提出了缓存数据时保持数据一致性的一个方案.

2010年05月05日 16:33 "Foxswily"的内容
话说回来,不论DB什么情况的回滚,缓存势必需要一起回滚(可以类比全局事务)。

我有一个前提, 凡是进入到缓存的数据都是经过校验符合规则的, 这样的数据进入到DB应该不会回滚, 如果回滚只能是硬件等问题, 这些数据会最终(在问题解决时)提交的.

2010年05月05日 16:33 "Foxswily"的内容
另外,JTA中对JMS的处理方式是在其他事 ...

JTA? 是指基于2PC实现的分布式事务吗? 我没有在实际项目中用过分布式, 但是"分布式第一准则, 尽量不要使用分布式", 另外似乎分布式事务与缓存不是一个范畴, 所以他们设计的侧重点也不一样, 不具有可比性

PS: 我提出了一个假设, 然后在假设的基础上有一些简单的推论. 这些假设在某些环境下并不适用. 呵呵
[该贴被icycrystal4于2010-05-05 17:04修改过]

2010年05月05日 17:00 "icycrystal4"的内容
如果缓存出现需要"rollback", 那只能说明这些数据在逻辑上是脏数据(这个应该在更新前校验), 不应该进行缓存更新. ...

如果这理论可行,DB也不需要事务了,如果DB出现需要rollback,那只能说明不需要进行DB更新。

某种程度上JTA多少适合这个需求,只是一般的缓存不支持而已,不了解Ehcache的集群是否支持JMS,如果支持也许可以解决。