2011年11月10日 12:04 "@donglangjohn"的内容
to SpeedVan

你的观点我基本上认同, "事务不是必须" 这个不太认同, 现在的system都有一定的复杂度,多多少少的会涉及到多个domain的operation 从而产生事务的刚性需求, 我们只能是尽量的缩小其范围,根据bus ...

实现逻辑表达的原子性,现在来说,主要有两种方式,一种是事务,一种是不变模式。
事务正是我们在可变模式中,对变化过程原子化的手段。而不变模式却可以离开事务达到原子,因为根本不存在变化过程,而替换天生就是原子的(事务也用到替换,其主要区别在于事务是站于可变模式中,所以它需要复制副本的手段达到隔离变化,而不变模式不需要这样的复制隔离,也没必要隔离,其犹如骨牌效应,自然而然地进行着,不变量的函数式编程就具有这样的特点)。

理解不变模式,可以发现很多地方,事务其实是一个技术性的包袱,是它入侵到逻辑当中。

从生到死,就是从“生”到“死”。而不是考虑“生”里的各个字段如何变成“死”里的,状态迁移,就是打破过程化思想的一种方式。

我给个例子看看大家如何去解:没事务的情况
仓库 现有袜子 50双
A和B两者 几乎在同一时刻 进行

A 入库 50双 改变了内存当中的库存 为100 但是这个步骤还没做完 ,这个时候b出库 100 双看到刚好有库存为100 马上出库 ,但是A在后续的步骤当中出了问题(也就是说前面的操作要重来),但是库存已改变,有的方式就是在减掉库存50恢复原有库存重来,但这时候你减不掉了,库存为0了因为b出库了100但他成功的执行了,遇到这种问题如何解?
[该贴被gamex于2011-11-10 13:51修改过]

2011年11月10日 13:50 "@gamex"的内容
我给个例子看看大家如何去解:没事务的情况
仓库 现有袜子 50双
A和B两者 几乎在同一时刻 进行

A 入库 50双 改变了内存当中的库存 为100 但是这个步骤还没做完 ,这个时候b出库 100 双看到刚好有库存为100 马上出库 ,但 ...


这问题相当大,入库50双都没完成,为何去改库存为100呢?

事实观认为“库存为100”为假的,也就是说后面B根本不会出库成功。

假如入库50中途出错,根据不变替换方式进行的话,只要入库50没有完成,所有实体状态都不会改变,该时段只是单纯的逻辑运算而已,逻辑运算中断就中断,不会对其他地方造成影响。


若果你问“同取同加”还好点:

当库存有50时,A与B同是请求50,因为读取的的确是50,所以都通过,这是一个并行问题——库存不是并行点,这种问题下,只能通过库存原子化,原子化原理就是CAS。

[该贴被SpeedVan于2011-11-10 17:13修改过]

2011年11月10日 17:03 "@SpeedVan"的内容
这问题相当大,入库50双都没完成,为何去改库存为100呢?

事实观认为“库存为100”为假的,也就是说后面B根本不会出库成功。

假如入库50中途出错,根据不变替换方式进行的话,只要入库50没有完成,所有实体状态都不会改变,该时段 ...


只是举了个例子可能例子不怎么恰当,我也相信会有类似这种一增一减,增的一方还会做其他事。我们也肯定不能假设这种情况不会发生。

2011年11月10日 18:57 "@gamex"的内容
只是举了个例子可能例子不怎么恰当,我也相信会有类似这种一增一减,增的一方还会做其他事。我们也肯定不能假设这种情况不会发生。 ...

我想你可能还没有理解我的意思。

这么说吧,逻辑不是对象的一个操作,如A库加入50,B库加入100,对于对象来说,这是两个独立操作,但在该事件,该逻辑上,他们是一个整体。你可以用锁来进行同时替换,也可以通过不变模式实现。(不变模式的同时替换,就是对AB所在的容器进行替换,可通过浅拷贝,其实就是组合变化问题。)

逻辑前是原始状态,逻辑后是新状态,也就是说不存在中间改了再继续执行,从而出现错误情况。记住,只有成功时才会进行修改,逻辑中的除初始状态外,其他都是临时的,运算“成功”后才得到新状态。

回原题,“入库50”是一个逻辑,没有成功的话,“库存”是不会改变的,只要成功了就替换(替换是原子的,不存在替换一半),此时“入库50”事件处理成功,若果“出库100”在“入库50”事件成功后才开始处理,那么就执行成功,否则失败,当然失败后是否继续重新排队,这属于另一个机制问题了。

说一个实际的解决方案,银行与交易所的资金交割过程:
1. 银行出账5¥,并写入数据库,然后等待交易所确认
2. 交易所入账5¥,并写入数据库,然后发出确认消息
3. 如果银行在规定的时间内没有收到交易所的确认,则自动入账5¥,然后发送消息给交易所
4. 交易所如果收到银行的退款消息,则自动出账5¥,然后发出确认
5. 如果银行没有收到确认则多次发送
6. 如果银行多次发送后还没有收到确认,则放弃此过程

这样会产生一个结果,在极端的条件下有可能银行的数据和交易所的不一致。。。。怎么办?

解决方案是每天17点后银行会生成交割记录发送给交易所,交易所按照记录查账,如果这个过程中发现资金错误则由人工确认后手动平帐。

to SpeedVan

事务 确实是站在可变模式这边,我们可以用锁 or 拷贝副本等手段来达到同样的目的。

不变模式,这个更需要替换吧,这是用new来替代old,一次性动作,是一个状态切到另一个状态,这定然会产生大量的old 对象从而引起gc的动作。
demo就不用找了吧,banq以前的帖子里就有,直接new替代old。


关于“同取同加” 说白了就是 并发的问题

A与B同时以库存那个50为condition来进行后续operation,结果都pass 都各自执行operation(这个operation的动作就是改库存的50)
结果那不就乱了么,都pass了 都能改啊

当然了,A与B库存的问题还是有解的。 如同乐观锁 悲观锁


不变模式 --- 没有中间过程, 从A状态 直接变为 B状态 这个认同


就拿刷卡为例,account只有$50了,很不幸卡被人秘密的copy了
你和那个盗用的人 同时刷那$50

按照 不变模式 都check 那 amout

method(简版): 刷卡(account_number,消费){......}

如果同时involved这个method的话,是不是意味着 你 和 盗用者都能刷呢

我想你不会允许这种情况发生吧,至少会在business所要求的安全等级来适当的“锁”吧


关于“入库50中途出错”那段 同意你的说法, 不过单纯的逻辑运算定然会涉及到一系列的object的copy,为什么? 因为要保证不变性。
我的天,一系列的copy啊,多少object啊,除非逻辑运算中用到的对象都是无状态的(这到不用copy了,这种情况不太多吧)。

毕竟system是复杂的,是一系列的domain之间的组合关联从而构成这个system啊。

再说下不变模式,你直接替换不要紧,至少要确认替换时前一时刻的状态是没问题的。
因为替换是有依据的,是前面的老状态。

千万别出现 此时非彼时


下班脑子乱, 一通胡言乱语 有不敬之处 望谅解

希望 speedvan 能就 刷卡 再深入点 多多提示


2011年11月10日 22:42 "@donglangjohn"的内容
不变模式,这个更需要替换吧,这是用new来替代old,一次性动作,是一个状态切到另一个状态,这定然会产生大量的old 对象从而引起gc的动作。 ...

恩,同意,这个问题我也早在前面的回复中有提到用 不变值+CAS 但是始终逃不开gc


@donglangjohn @gamex

太多问题,选取关键的回答吧:

一、事务与不变模式的对比:

不变模式是否增加了对象数量级别。我看未必,有时反而降低。
不变性与CAS的原子类型相比,肯定原子类型产生的对象少,因为原子类型使用的是系统函数。但与事务相比,不变模式反而少了,为什么呢?
首先一个事务,它所涉及的对象(实体)全部都要复制一次,换种思维,就是需要复制的是条件与结果的集合(Set),若果条件是(1、2、3)(注:理解为key),结果是(2、3、4),那么集合就是(1、2、3、4),所以是复制4个;基于不变模式的话,需要复制(或者说新建)的只是结果,在上面例子就是3个。再想个极端一点的,我们需要在一个特定的公式中,统计1000个对象的信息,再生成一个统计结果,那么事务就是1001个复制,而不变模式只需要1个新建(已经没算底层生成的其他对象,现只算逻辑上的)。谁更少对象呢?其实我想说的是事务被封装作底层了,别忘记了事务的本来面貌。

事务因为可变才需要不断copy,不变模式从一开始就不变了,还需要copy么?

二、“同取同加”问题:

这个问题只是机制问题,事务也会遇到,事务隔离变化后,仍然需要替换的手段来改变对象,否则复制的副本干什么去了?同取同加,除了锁可以解决外(当然锁中还可以分开几种情况,但这里就不详细了),还有就是CAS,当然还有机制。还有不要想避开GC,GC问题是永久的问题,但不是不能解决的问题,要么是拥有一个定造的GC,要么加大内存,有些问题不是想着如何回避,而是一开始就不能回避的。

思考并行,其实关注是否当中元素都是并行点就可以了。刷卡问题,因为存款不是并行点,所以根本就不能完全基于不变并行,而需要用到了CAS原子性。思考什么是并行,并行就是两个不相干的过程或者逻辑,各自进行。注意当中“不相干”相当重要,这是并行的必要条件。

在刷卡例子中,我刷卡对存款的影响和小偷刷卡对存款的影响是“相干”的,于是这里对存款进行处理让其的变化可以“不相干”,还有另外一种方法,就是通过更高层的逻辑设计来回避“相干”,如并行处理时,对非并行点进行检测,发现正在处理中,则回避处理,也就是非并行点进行串行处理,并行点进行并行处理(A卡处理时不涉及B卡,B卡就是一个并行点)。还有一种机制就是高层次的CAS,可以算是进化版的锁机制。

刷卡例子是一个很好的例子,因为其并行概率相当高,如A转B与C转D并行等。

to SpeedVan

基本上了解,我再总结下:

不管什么事务还是不变模式,都无法逃离那个“串行”(即非并行点),在这个“串行”点上,我们所能做的就是采取一定的手段来保证“串”的执行顺序。
例如:锁。 看来得要在“锁”这个方面再多下点功夫啊!!!


其实“事务” 不一定都得要copy来保证,也可以用“锁”。

从“非并行”这个点来看 “事务”与“不变模式” 这两个是不是有点可以“约等于”的意义。

个人观点: (纯粹 受传统思维的影响)
不变模式 = “事务(从非并行点来看)” + “Domain全部状态”


再从code层次来说下, 在实现不变模式的时候,“非并行点”最好是可设置的而非固定的。

再次感谢 SpeedVan的帮助, thanks a lot


最后再唠叨一句: 前端发出的event,由后端event bus接受执行时 应该要多多考虑,不仅仅是拿来event放到DB就ok了。
举例: event list(A):{A1,A2} event list(B){B1,B2}
B是根据A产生的event, B1对A1,B2对A2
但是event listener肯定是两个分别处理A和B吧,
问题来了, listener A 执行到A1时,listener B 执行到B2而failer了(因为A2未执行,DB中未有相应的A2值)。
除非在event执行进DB的op都是update,这样就能保证DB里最终结果是正确的。


to SpeedVan

关于对象数量级别的问题

从“事务”来看,对非并行点 用锁来处理而非copy,此时仅仅锁定资源并最终改变被锁的资源或产生新的Domain。 一个都未copy而是改变Domain的状态,从old到new的过程被加锁了。

从“不变模式”来看,对并行点,Domain从old到new状态好像是靠copy或generate新的来替代吧,对非并行点,按照你的说法也是copy了吧,怎么看都是在copy啊, 毕竟那个“1” 不会是一成不变的Domain吧,一旦变化就要copy或generate的。

现在好像是陷到 矛盾状态了 @_@

2011年11月12日 22:41 "@donglangjohn"的内容
to SpeedVan

关于对象数量级别的问题

从“事务”来看,对非并行点 用锁来处理而非copy,此时仅仅锁定资源并最终改变被锁的资源或产生新的Domain。 一个都未copy而是改变Domain的状态,从old到new的过程被加锁了 ...

其实“事务”就是副本模式,每一次访问都会复制一份供外界使用,从而不影响自身。

不变模式就不需要担心该情况。


事务是并行的,因为其具有隔离性,所以不可能是用锁机制进行的,若果用锁机制就证明是同一对象,该对象若果修改了,其他事务对该对象访问就会发生一致性问题,甚至不可预知的错误。

你出现这种混乱的原因,是你只想着替换部分,缺忘记了逻辑运算前获得的“条件”。copy与新建从技术上来看其实消耗是差不多的,但我们不是关注与copy和新建,而是什么时候需要copy和新建。

事务下,当我需要获取A时,无论我需不需要对A进行修改,我都要copy一份,因为1)其他事务可能会修改,2)而且,从机制上无办法在事务“执行前”知道事务会不会对其进行修改。做成这种需要的根本原因,正是因为对象可变。

不变模式正是回避了那两个问题,所以天生具备了并行特点。

你说到的其实是替换部分,是的,替换部分无论事务还是不变模式,都是存在“新”和“旧”两个概念,当然会存在所谓的copy或者new,而这是不避免的,但对于不变模式来说,它“逻辑上”只存在new,而不是copy。因为在不变模式中,copy后还是不变的,即等于没有意义了。

[该贴被SpeedVan于2011-11-13 02:57修改过]

to SpeedVan

又翻看 不变性,如何打败CAP...等相关的帖子,终于明白点了。

多谢解惑,有点清楚了,稍稍总结下。

不变模式 --- 因为是替换(我理解为update)的关系从而自然有了并行能力

在涉及到征用同一资源时(也就是非并行点),无论是事务还是不变模式都无能为力,我们只能是在非并行点下功夫,可采取的手段
1)锁
2)在上层逻辑预先回避

1)和2)
个人观点--- 1)的粒度小,2)的粒度大,需要根据requirements来做选择。

不知SpeedVan还有关于非并行点方面的建议么?

再问下 关于“CAS”的帖子 未曾在jdon搜到,可否给出link,多谢。


可能是工作的关系,更多的关注事务和锁这两个方面,这阵子多有打扰,多谢SpeedVan了, thanks a lot

CAS CompareAndSwap(java原子类型里面叫CompareAndSet) 是CPU内存-缓存机制,其实就是想替换时,提交-提交失败(与期望值不相等等原因)-重新执行。

jdon没有提及过这个,这个是属于lock-free范畴的知识。

随便找到的外链
[该贴被SpeedVan于2011-11-14 01:13修改过]

2011年11月14日 01:12 "@SpeedVan"的内容
CAS CompareAndSwap ...

CAS在Java中已经比较底层了,如果它的API告诉你它是使用CAS实现的,你就可以放心使用,也知道它属于一种非锁,有很好的并发性和线程安全性。