开源框架Jdon架构研究

发布时间 : 星期三 文章开源框架Jdon架构研究更新完毕开始阅读

实体模型的缓存架构如下:

注意:这里可能是Jdon框架 6.2的一个创新或重要点,在JF6.2之前实际上没有对模型进行突出的支持,就象画个圈,圈子外面基本都就绪,圈子里面留白,这个圈子就是Domain Model,原来因为考虑持久层Hibernate等ORM巨大影响力,就连Spring也是将Model委托给Hibernate处理,自己不做模型层,但是当NoSQL运动蓬勃发展,DDD深入理解,6.2则找到一个方式可以介入模型层,同时又不影响任一持久层框架的接入。

Jdon框架6.2通过在持久层的获得模型对象方法上加注释的办法,既将模型引入了内存缓存,又实现了向这个模型注射必要的领域事件Domain Events。

事件Event Sourcing

JF的最大特点是领域模型驱动技术架构; 如果说普通编程缺省是顺序运行,那么事件模型缺省是并行运行,两者有基本思路的不同。

Event Sourcing适合将复杂业务领域和复杂技术架构实现分离的不二之选。实现业务和技术的松耦合,业务逻辑能够与技术架构解耦,将”做什么”和”怎么做”分离

事件模型也是一种EDA架构Event-driven Architecture,可以实现异步懒惰加载

Asynchronous Lazy-load类似函数式语言的懒功能,只有使用时才真正执行。

具有良好的可伸缩性和可扩展性,通过与JMS等消息系统结合,可以在多核或多个服务器之间弹性扩展。

事件模型也是适合DDD落地的最佳解决方案之一。领域模型是富充血模型,类似人类的DNA,是各种重要事件导向的开关。用户触发事件,事件直接激活领域模型的方法函数,再通过异步事件驱动技术活动,如存储数据库或校验信用卡有效性等。

2009年Jdon框架6.2就推出了Domain Model + In-memory + Events.,2001年Martin fowler在其文章LMAX架构推荐In-memory + Event Sourcing架构。以下是该文的精选摘要,从一个方面详细说明了事件模型的必要性:

内存中的领域模型处理业务逻辑,产生输出事件,整个操作都是在内存中,没有数据库或其他持久存储。将所有数据驻留在内存中有两个重要好处:首先是快,没有IO,也没有事务,其次是简化编程,没有对象/关系数据库的映射,所有代码都是使用Java对象模型。

使用基于内存的模型有一个重要问题:万一崩溃怎么办?电源掉电也是可能发生的,“事件”(Event Sourcing )概念是问题解决的核心,业务逻辑处理器的状态是由输入事件驱动的,只要这些输入事件被持久化保存起来,你就总是能够在崩溃情况下,根据事件重演重新获得当前状态。

事件方式是有价值的因为它允许处理器可以完全在内存中运行,但它有另一种用于诊断相当大的优势:如果出现一些意想不到的行为,事件副本们能够让他们在开发环境重放生产环境的事件,这就容易使他们能够研究和发现出在生产环境到底发生了什么事。

这种诊断能力延伸到业务诊断。有一些企业的任务,如在风险管理,需要大量的计算,但是不处理订单。一个例子是根据其目前的交易头寸的风险状况排名前20位客户名单,他们就可以切分到复制好的领域模型中进行计算,而不是在生产环境中正在运行的领域模型,不同性质的领域模型保存在不同机器的内存中,彼此不影响。

LMAX团队同时还推出了开源并发框架Disruptor,他们通过测试发现通常的并发模型如Actor模型是有瓶颈的,所以他们采取Disruptor这样无锁框架,采取了配合多核CPU高速缓冲策略,而其他策略包括JDK一些带锁都是有性能陷阱的: JVM伪共享。

JF的领域事件是基于号称最快的并发框架Disruptor开发的,因此JF的事件是并行并发模型,不同于普通编程是同一个线程内的顺序执行模型。

JF的领域事件是一种异步模式 + 生产者-消费者模式。主题topic和Queue队列两种。领域模型是生产者;消费者有两种:

.@Consumer;可以实现1:N多个,内部机制使用号称最快的并发框架Disruptor实现。适合轻量;小任务;原子性;无状态。

.@Componet;直接使用普通组件类作为消费者,使用jdk future机制,只能1:1,适合大而繁重的任务,有状态,单例。

Domain Events实现机制如下:

JF的事件模型还是一种CQRS架构,可以实现模型的修改和查询分离,也就是读

写分离的架构:

无堵塞的并发编程

顺序编程和并发编程是两种完全不同的编程思路,堵塞Block是顺序编程的家常便饭,常常隐含在顺序过程式编程中难以发现,最后,成为杀死系统的罪魁祸首;但是在并发编程中,堵塞却成为一个目标非常暴露的敌人,堵塞成为并发不可调和绝对一号公敌。

因为无堵塞所以快,这已经成为并发的一个基本特征。

过去我们都习惯了在一个线程下的顺序编程,比如,我们写一个Jsp(PHP或ASP)实际都是在一个线程

下运行,以google的adsense.Jsp为例子: <%

//1.获得当前时间

long googleDt = System.currentTimeMillis(); //2.创建一个字符串变量

StringBuilder googleAdUrlStr = new StringBuilder(PAGEAD); //3.将当前时间加入到字符串中

googleAdUrlStr.append(\//4.以字符串形成一个URL

URL googleAdUrl = new URL(googleAdUrlStr.toString()); %>

以上JSP中4步语句实际是在靠一个线程依次顺序执行的,如果这四步中有一步执行得比较慢,也就是我们所称的是堵塞,那么无疑会影响四步的最后执行时间,这就象乌龟和兔子过独木桥,整体效能将被跑得慢的乌龟降低了。

过去由于是一个CPU处理指令,使得顺序编程成为一种被迫的自然方式,以至于我

们已经习惯了顺序运行的思维;但是如今是双核或多核时代,我们为什么不能让两个CPU或多个CPU同时做事呢?

如果两个CPU同时运行上面代码会有什么结果?首先,我们要考虑两个CPU是否能够同时运行这段逻辑呢?

考虑到第三步是依赖于前面两步,而第二步是不依赖第一步的,因此,第一步和第二步可以交给两个CPU同时去执行,然后在第三步这里堵塞等待,将前面两步运行的结果在此组装。

很显然,这四步中由于第三步的堵塞等待,使得两个CPU并行计算汇聚到这一步又变成了瓶颈,从而并不能充分完全发挥两个CPU并行计算的性能。

我们把这段JSP的第三步代码堵塞等待看成是因为业务功能必须要求的顺序编程,无论前面你们如何分开跑得快,到这里就必须合拢一个个过独木桥了。

但是,在实际技术架构中,我们经常也会因为非业务原因设置人为设置各种堵塞等待,这样的堵塞就成为并行的敌人了,比如

我们经常有(特别是Socket读取) While(true){ …… }

这样的死循环,无疑这种无限循环是一种堵塞,非常耗费CPU,它也无疑成为并行的敌人。

比如JDK中java.concurrent.BlockingQueue LinkedBlockingQueue,也是一种堵塞式的所谓并行包,这些并行功能必须有堵塞存在的前提下才能完成并行运行,很显然是一种伪并行。

由于各种技术上的堵塞存在,包括多线程中锁机制,也是一种堵塞,因为锁机制在某个时刻只允许一个线程进行修改操作,所以,并发框架Disruptor可以自豪地说:无锁,所以很快。

现在非常流行事件编程模型,如Event Sourcing或Domain Events或Actor等等,事件编程实际是一种无堵塞的并行编程,因为事件这个词语本身有业务模型上概念,也有技术平台上的一个规范,谈到事件,领域专家明白如同电话铃事件发生就要接,程序员也能明白只要有事件,CPU就要立即处理(特别是紧急事件),而且事件发生在业务上可能是随机的,因此事件之间难以形成互相依赖,这就不会强迫技术上发生前面Jsp页面的第三步堵塞等待。

因此,在事件模型下,缺省编程思维习惯是并发并行的,如果说过去我们缺省的是进行一个线程内的顺序编程,那么现在我们是多线程无锁无堵塞的并发编程,这种习惯的改变本身也是一种思维方式的改变。

在缺省大多数是并发编程的情况下,我们能将业务上需要的顺序执行作为一种特例认真小心对待,不要让其象癌细胞一样扩散。我们发现这种业务上的顺序通常表现为一种高一致性追求,更严格的一种事务性,每一步依赖上一步,下一步失败,必须将上一步回滚,这种方式是多核CPU克星,也是分布式并行计算的死穴。值得庆幸的是这种高一致性的顺序编程在大部分系统中只占据很小一部分,下图是电子商务EBay将他们的高一致性局限在小部分示意图:

由此可见,过去我们实现的顺序编程,实际上是我们把一种很小众的编程方式进行大规模推广,甚至作为缺省的编程模式,结果导致CPU闲置,吞吐量上不去同时,CPU负载也上不去,CPU出工不出力,如同过去计划经济时代的人员生产效率。

联系合同范文客服:xxxxx#qq.com(#替换为@)