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火车票系统是这类问题的典型体现。数据库