当eventSourceing遇到并发和事务将变得复杂

11-11-04 gamex
据个例子:有个 仓库 共享对象

仓库『

events

addEvent

A 线程 处理一个事务(里面会用到仓库对象)如果失败需要回滚 如果成功store event to disk

b 线程 处理一个事务(里面也会用到仓库对象)如果失败需要回滚 如果成功store event to disk

这时候如果操作event是顺序存入共享对象的events队列里,那问题将更加的复杂,例如:假设存入在event队列里面的顺序如下

A线程的 event1

B线程的 event3

A线程的 event4

B线程的 event5

这时候A 线程的事务是成功的他需要commint 并 store event 到磁盘

可B线程事务这个时候失败了,这时候事情将变的复杂话。

所以你就会发现这种设计最理想的是single-write规则,假如想多加入处理器的话,共享事件队列将会让问题变得很复杂。

10
SpeedVan
2011-11-04 17:44
A做A的,B做B的,哪里复杂了?

1、事件本身就是相互独立的。

2、使用事务不是很适合逻辑的,因为逻辑是不讲究过程的,而回滚正是针对过程而存在。

3、多处理器不是问题,关键是处理的设计,可以看看下面的对比(event2在event1之后处理):

1)event1进入队列后,把event2放入队列;(计划任务)

2)event1进入队列后,待event1执行后才放入event2;(一件事完了才会有第二件事)

这里可以看出,不是队列很复杂,而是我们想得太简单了,发散一下思维,不要被原来理所当然的队列约束。

gamex
2011-11-04 22:04
2011年11月04日 17:44 "@SpeedVan"的内容
A做A的,B做B的,哪里复杂了?

1、事件本身就是相互独立的。

2、使用事务不是很适合逻辑的,因为逻辑是不讲究过程的,而回滚正是针对过程而存在。

3、多处理器不是问题,关键是处理的设计,可以看看下面的对比(event2在event1之后处 ...

还是不是很明白 ,event1处理完了,event2才开始处理这个是肯定毋庸置疑的(这是针对单对象),但是我们的事务不是只针对一个对象做一件事,扣完仓库还要去做别的也许是根据当前库存去做校验公式计算,在把计算结果保存到另一个对象(但是如果在计算另一个对象的时候出错,扣掉的库存该如何处理?)。

gamex
2011-11-04 22:11
2011年11月04日 22:04 "@gamex"的内容
1)event1进入队列后,把event2放入队列;(计划任务)

2)event1进入队列后,待event1执行后才放入event2;(一件事完了才会有第二件事)

...

你说的是不是大事件,例如:转账就是个大事件,然后排队处理?

banq
2011-11-05 07:50
2011年11月04日 22:11 "@gamex"的内容
A 线程 处理一个事务(里面会用到仓库对象)如果失败需要回滚 如果成功store event to disk ...

这里假设前提需要事务,然后再用Event Sourcing,这实际是矛盾的,因为事务是串行,事件是并行的,两者本质上是矛盾的,水和火。

在实际上,我们是缩小需要使用事务的范围,绝不将事务和并发混合。这样才能提供整体性能。

如果实际中有这种极端的实例,可以通过避免在一个内存中的这种线程Queue模式,转化为JMS的分布式事务来实现。也就是说,如果有楼主这个需求,又要求使用事件,又要求事务安全,那么不要使用线程来做,而是利用Disruptor这样Queue机制转发到JMS中,使用JMS这种异步的分布式事务来实现即可。

见这个讨论:事件与事务

[该贴被banq于2011-11-05 08:04修改过]

gamex
2011-11-05 08:26
2011年11月05日 07:50 "@banq"的内容
这里假设前提需要事务,然后再用Event Sourcing,这实际是矛盾的,因为事务是串行,事件是并行的,两者本质上是矛盾的,水和火。

在实际上,我们是缩小需要使用事务的范围,绝不将事务和并发混合。这样才能提供整体性能。

如果实际中有这 ...

像这种情况,我想到的是把这种事务性质的情况当成一件事也就是一个大事件,这就可以保证其原子性,单要求内部涉及到的对象采用immutability value + CAS 这种方式,当事件失败不会影响到其原有对象本身,但是这种方式的缺点是如果这共享对象访问频繁的话,Gc将会频繁出现很多临时对象。

SpeedVan
2011-11-05 13:32
2011年11月05日 07:50 "@gamex"的内容
还是不是很明白 ,event1处理完了,event2才开始处理这个是肯定毋庸置疑的(这是针对单对象),但是我们的事务不是只针对一个对象做一件事,扣完仓库还要去做别的也许是根据当前库存去做校验公式计算,在把计算结果保存到另一个对象(但是如果在 ...

1、只有一个事情彻底完成才会存在所谓的修改。

2、若果一个事件产生了修改操作,也就证明了该事件是没有错误的。

3、你获取的对象状态是当前事实。

“你保存到另外一个对象”这个是说明事件已经正常结束,怎么还要回滚呢?你确定还需要计算,而且出现错误,那么也就证明,“你保存到另外一个对象”是一个错误设计。

请记住,在并行过程中,逻辑没有彻底完成,是不会对任何对象作出修改的,若果你计算得到新值,那么请把新值一直使用下去,不要把新值赋给对象后,又从对象中获取,这样的做法是彻底打断逻辑的,因为引用对象是副作用部分。

>>但是这种方式的缺点是如果这共享对象访问频繁的话,Gc将会频繁出现很多临时对象。

<<访问频繁不会出现这样的状况,只有修改频繁时才会。这是因为并行是相互独立的,必然有各自的结果相互独立的。

[该贴被SpeedVan于2011-11-05 13:35修改过]

[该贴被SpeedVan于2011-11-05 13:40修改过]

gamex
2011-11-05 19:24
2011年11月05日 13:32 "@SpeedVan"的内容
1、只有一个事情彻底完成才会存在所谓的修改。

2、若果一个事件产生了修改操作,也就证明了该事件是没有错误的。

3、你获取的对象状态是当前事实。

“你保存到另外一个对象”这个是说明事件已经正常结束,怎么还要回滚呢?你确定还需要计算,而且出现 ...

能否以一个具体的例子来说明下?比如:转账 A帐号 转5$给 b帐号

A要先扣除5$ 然后 b帐号要加5$ 整个过程才算完成。

donglangjohn
2011-11-05 20:52
从 “事件与事务” 转过来 接着这说吧,

就那上面的 “转账” 来说吧。

直接点,从code角度来说下:

method:转账(源--A帐号,目的--B帐号,金额){......}

这个应该会激发一个事件(一个大事件),但是具体 +/-的operation应该是在A/B帐号 这个Domain内部中吧。

那么A/B中的operation都会有相应各自的event产生,被send到event bus

此时两个event是“各自为政”,无法保证事务性。

to bang:

应用中每个op都是原子性(即事务),在目前应用不太可能保证做到,至少目前我这边的情况是这样的,基本上都是一系列小method组合从而形成一个大事件。所以才用了那个事务的“壳”的想法。

再激进点的想法,Domain中的op不一定都是原子的,而是以不同role参与到特定场景下时其行为的事务性由场景来决定。

以上纯属胡思乱想 呵呵

banq
2011-11-06 11:03
2011年11月05日 20:52 "@donglangjohn"的内容
我想到的是把这种事务性质的情况当成一件事也就是一个大事件,这就可以保证其原子性,单要求内部涉及到的对象采用immutability value + CAS 这种方式,当事件失败不会影响到其原有对象本身,但是这种方式的缺点是如果这共享对象访问 ...

是的,产生频繁对象是副作用,这也是Scala在JVM上跑的一个副作用,有大量临时对象产生。

SpeedVan
2011-11-06 12:57
2011年11月05日 19:24 "@gamex"的内容
能否以一个具体的例子来说明下?比如:转账 A帐号 转5$给 b帐号

a要先扣除5$ 然后 b帐号要加5$ 整个过程才算完成。 ...

1、首先这个涉及N个对象同时修改的问题;

2、其次这个要求修改要求强同步,导致了并行事件并不好设计。(并行点存在于不同账号间,不并行点在于同个账号,于是导致并行线难于设计)

3、这个适合Actor模型,这是因为账号是自己处理自己(原子性造成),并不是参与到逻辑中。账号自己的增减与其他账号增减无关,于是关联需要外界提供。

a账号减少5$,从转账的逻辑上说,是不能独立存在的。但却独立成一个事件,于是带来的问题是不可避免。

思考设计的话,多处理器时,对于正在处理的账号进行忽略(标识等机制),向后取事件。

donglangjohn
2011-11-09 21:21
估计多数的business logic里基本上都是多个有关联的对象被要求同时修改且在一个事务内的吧。

回想一下: 早前的开发模式是基于DB的,有DB保证来保证事务性,如果遇到error了就直接rollback然后再重新从DB取出数据set给那个Domain对象来保证Domain的正确性。

现在的env: 每个Domain对象会由operation来触发event(这个event要先被持久化再send出去),再由event listener来处理event从而达到持久化的目的。

来比对下 “早前”和“现在”,后者是增加event层从而让Domain脱离DB的束缚,但是由于business data是重要的,肯定要被及时的持久化,而且当business error时肯定要将相关的Domain对象rollback到前一正确的时刻,尤其是涉及到敏感数据时。

而问题也就随之而来的,“事务性”,无可避免的。

“现在” 是在内存中来做到及时的反馈(Domain的业务都在内存中来完成),而持久化是交由event那边来处理,比“早前”多了event这层,从而不太好做到“事务性”,确切的说是error时rollback的及时的反馈。

个人观点: 在“现在”中要想做到“事务性”

1) 想办法让那些必须属于一个事务的事件都有一个标记来表明它们处于同一个事务内

2)缩小事务的范围(尽量少的Domain对象)

3)根据business的要求来决定事务的 同步 or 异步

4)当business error时 可根据相关的Domain对象的rollback的复杂度来选择是 手动---从代码级别来rollback 那些Domain对象

被动---从DB中再get一次数据,重新初始化那个Domain对象

rollback时 由于event的执行进度问题,可能会出现脏读的情况, 这点应该依据business的要求(也就是同步 异步)来考虑。

强一致性 用同步

弱一致性 用异步

以上纯属个人观点(闲暇时胡思乱想的),请多指教

banq
2011-11-10 07:33
2011年11月09日 21:21 "@donglangjohn"的内容
弱一致性 ...

你说的弱一致性就相当于最终一致性:

最终一致性在现实世界中到处存在

SpeedVan
2011-11-10 09:18
2011年11月09日 21:21 "@donglangjohn"的内容
而问题也就随之而来的,“事务性”,无可避免的。 ...

原子是必然的,事务不是必须。事务是过程原子化的产物,在逻辑中并不需要事务。

donglangjohn
2011-11-10 12:04
to banq

关于弱一致性确实如你所说的最终一致性,有些businesss能容忍一定的delay

,就好比电汇,不管这个电汇是否ok,都只能在转天才能见分晓.

而有些business是不能够容忍delay的,如实时转帐,有特定手段来保证其正确性,即使failure了,也会立即rollback前一刻的状态.

就转帐来说, failure了,你的钱不会有变化,一份不少你的.

如果用弱一直性来做,那account里的money可就不知道什么时候才能还回来啊.

to SpeedVan

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

猜你喜欢
3Go 1 2 3 下一页