Actor模型是高并发事务的终极解决方案

首先看看道友提出的一个问题:
用户甲的操做
1.开始事务
2.访问表A
3.访问表B
4.提交事务
乙用户在操做
1.开始事务
2.访问表B
3.访问表A
4.提交事务 

若是甲用户和乙用户的两个事务同时发生,甲事务锁住了表A未释放(由于整个事务未完成),正在准备访问B表,而乙事务锁住了表B未释放(由于整个事务未完成),正在准备访问A表,但是A表被甲事务锁住了,等甲事务释放,而甲事务真正等待乙事务释放B表,陷入了无限等待,也就是死锁Dead Lock。

也有道友使用多线程来模拟存储过程:http://www.jdon.com/45727,每一个线程里开启一个事务,相似上述问题也会出现死锁。

问题出在哪里?

是咱们的思路方向出现问题:

其实不管是使用数据库锁 仍是多线程,这里有一个共同思路,就是将数据喂给线程,就如同计算机是一套加工流水线,数据做为原材料投入这个流水线的开始,流水线出来后就是成品,这套模式的前提是数据是被动的,自身不复杂,没有自身业务逻辑要求。适合大数据处理或互联网网站应用等等。

可是若是数据自身要求有严格的一致性,也就是事务机制,数据就不能被动被加工,要让数据本身有行为能力保护实现本身的一致性,就像孩子小的时候能够任由爸妈怎么照顾关心均可以,可是若是孩子长大有本身的思想和要求,他就可能不喜欢被爸妈照顾,他要求本身经过行动实现本身的要求。

数据也是如此。

只有咱们改变思路,让数据本身有行为维护本身的一致性,才能真正安全实现真正的事务。

数据+行为=对象,有人问了,对象不是也要被线程调用吗?

例以下述代码,由于对象的行为要被线程调用,咱们要使用同步锁synchronized :
 

public class A { 
        private volatile int lower, upper; //两个状态值
        public int getLower() { return lower; } 
        public int getUpper() { return upper; }
        public synchronized void setAUpper(int value){
             if (value < a.getUpper()) 
                    a.setLower(value);
        }

        public asynchronization void setALower(int value){
            if (value > a.getLower()) 
                   a.setUpper(value);
         }
   }



上面这段代码业务逻辑是想实现lower<upper:

1. lower和upper的初始值是(0, 5),
2.一个客户端请求线程A: setLower(4) 
一个客户端请求线程B: setUpper(3) 
3. lower和upper是 (4, 3) 

这个结果破坏了lower<upper这个逻辑一致性,因此,用锁并不能保证逻辑一致性,并且还带来了堵塞。锁用错了地方,不但没有获得想要的,并且还失去更多。

下图展现了锁带来堵塞,每一个时刻只能容许一个线程工做,如同只能容许一我的蹲马桶同样。html




从历史上看,锁的问题如鬼魂一直伴随着咱们:
1.用数据表一个字段来表示状态,好比1表示已付款未发货,2表示已付款已发货,而后用户来一个请求用SQL或存储过程修改,这时使用的数据库锁。

2.用ORM实现,好比Hibernate JPA来修改状态,虽然不用SQL了,可是Hibernate的悲观锁和乐观锁也让人抓狂。

3.完全抛弃数据库,直接在内存缓存中进行修改,使用Java的同步锁,性能仍是不够,吞吐量上不去。如上图提示,只能一个厕所蹲位一我的用,其余人必须排队。

4.Actor模型。

Actor模型原理
Actor模型=数据+行为+消息。

Actor模型内部的状态由本身的行为维护,外部线程不能直接调用对象的行为,必须经过消息才能激发行为,这样就保证Actor内部数据只有被本身修改。

Actor模型如何实现?

Scala或ErLang的进程信箱都是一种Actor模型,也有Java的专门的Actor模型,这里是几种Actor模型比较

明白了Actor模型原理,使用Disruptor这样无锁队列也能够本身实现Actor模型,让一个普通对象与外界的交互调用经过Disruptor消息队列实现,好比LMAX架构就是这样实现高频交易,从2009年成功运行至今,被Martin Fowler推崇。

回到本帖最初问题,如何使用Actor模型解决高并发事务呢?

转帐是典型的符合该问题的案例,转帐是将A账号到B账号转帐,使用Actor模型解决以下:

发出是否可转出消息--->消息队列--->A

A做为一个对象,注意不是数据表,对象是有行为的,检查本身余额是否可转帐,若是能够,冻结这部分金额,好比转帐100元,冻结100元,从余额中扣除。由于外部命令是经过消息顺序进来的,因此下一个消息若是也是扣除,再次检查余额是否足够......

具体详细流程可见:REST和DDD

那么,既然Actor模型如此巧妙,而解决方向与咱们习惯的数据喂机器的方式如此不一样,那么如何在实战中能明显发现某个数据修改应该用Actor模型解决呢?由于咱们习惯将数据喂机器的思路啊?

使用DDD领域驱动设计或CQRS架构就能明显发现这些特殊状况,CQRS是读写分离,其中写操做是应领域专家要求编写的功能,在这类方向,咱们都有必要使用Actor模型实现,由于在这个方向上,领域专家的要求都表达为聚合根实体,聚合根就是用Actor模型实现最合适不过了。而读方向,好比大数据处理,报表查询,OLTP等等都是数据喂机器的方式。

有的道友会疑问,咱们常用SSH,也就是Spring + Hibernate架构,这个默认是哪一种方向呢?很显然,默认是数据喂机器的方向,因此在实现写操做时,特别警戒高并发发生死锁等影响性能问题,固然也包括EJB架构。

有一种togaf架构,将企业软件架构分为数据架构和应用架构等,实际是EJB或SSH的变相描述,这种架构的问题咱们已经一目了然了,特别这样的系统若是从面向内部管理转向到SaaS模型时,这类高并发死锁问题就特别容易发生,几乎不具有可用性。前期12306火车票系统是这类问题的典型体现。数据库

相关文章
相关标签/搜索