分布式系统
摩尔定律如果一直能实现,不管是涉及或者实现一个OLTP的系统,我们是不是都会轻松点,用硬件堆就可以了。但是现在硬件已经在求变了,那么我们也得求变,云的概念如此之火,本质就是设施虚拟化,也可以认为是逻辑化,那么我们做软件是不是也要来虚拟化一下呢,当然,软件本身就是虚拟逻辑化。
如果摩尔定律继续支持IO设备性能往上走,那就皆大欢喜,可惜不是这样。于是我们将系统做成多个实例,也许一个系统中还有很多子系统,全部实例化,一股脑扔进一个大的“计算机”里面,这个“计算机”是逻辑的,物理上就有太多组合了,可能在中国有一个服务器是一个系统实例或者子系统实例,在美国也有。从外面看,就是一个完整的系统,从内部看,由多个系统实例组成,因为是逻辑隔离的,所以认为是分布的。
一个系统的多实例,也许是部署在不同机器上,或者容器,或者单台机器,但是他们都是独立的运行单元了,假如将他们理解为在一个机器上运行的多个程序,要协调他们正常运作,那么我们是不是需要模拟操作系统呢,这就是分布式系统的基础设施,基本上与操作系统的各个基础设施所对应,甚至实现方式都类似,不过在现实当中连接他们的是网线而不是总线。
关系数据库
ACID特性,实质上ACID只是一种指导思维,其实现有各种各样,不管是ORACLE、MSSQLSERVER、MYSQL以及其他关系数据库,大多实现了ACID,我们的系统有各种需求,有时特别需要数据一致性,例如账号存钱取钱之类的场景,所以在关系数据库上我们是基本放心的。在把系统设计成分布式之后,无论我们怎么拆系统,都会因为系统与关系数据库这个短板耦合而无法适应,于是后来的CAP,BASE理论就相继出现了。关系数据库是一个成熟的集成好的软件,我们无法控制,所以干脆我们抛弃ACID中不适应我们系统的需要特性,于是我们将关系数据库给拆了,将存储拆成一个基础设施,其他的我们按需来设计,比如需要一致性的我们用最终一致性来代替,实质上我们将关系数据库的各种组件都拆出来自己来实现。当然这里严格来说不能认为是实现了关系数据库,只能说借鉴了它的一些特性衍生到现在这样。因为没办法,于是我们只好将各种组件拆成可以分区扩展的组件。拆完了数据库,换种说法是持久化设施,我们就可以拆我们的系统了,不拆也可以,直接使用分区扩展的实例,没有问题,因为我们已经将短板给干掉了。
领域驱动
物理架构差不多了,我们需要考虑考虑真正的业务架构与逻辑架构了,DDD的理论不细说,业务架构从DDD的映射来看,可以认为是上下文,当然在物理视图当中,它也许是多实例的。那么对于每个上下文的逻辑架构呢,目前来说,应用最广泛的是经典的4层架构,也有很多变种,根据实际项目的需要,我更喜欢采用六边形架构,不过在实际项目中,我们的采用的框架所面对的问题更实际,可维护性,可扩展性可能更需要注重,也许一个聚合根的概念就需要沟通很多次,各种概念是因人而异的,不管怎么实现,只要最后有这个功能满足了功能需求与非功能需求即可。所以也许最后又变回了原始的3层架构。如果我们坚持贯彻了DDD的概念,那么未来在可扩展上一定是非常方便的。所以要实现DDD,团队成员的要求是比较高的。
CQRS ES
CQRS典型的借鉴了我们在优化关系数据库架构中常用的方法,读写分离。在写DDD的时候,是不是碰到过模型适应写与读场景的不匹配,各种令人烦躁的ORM的N+1问题,也许已经想办法从框架内实现了只读模型的DTO,用CQRS来形容并形成一个标准更能让大家接受。不同的架构师面对同一个问题有不同的解决方案,但也许只是变种,如果能统一大家的使用方式形成专业的语言,例如领域语言,那么我们就可以集合大家的力量齐头并进了。CQRS就算是一种。
为什么要使用ES呢,我们来想想关系数据库实现数据一致性的方式,假如我们要插入一条数据,数据库先写日志,然后插入表中,这样的描述不够准确,但大致如此,不要纠结,我想说的是关系数据库采用了一种方式称为写屏障,在真实写入之前先记录,在异常情况下,我们就可以保持数据的一致性,现在Linux的很多新型文件系统就是采用了此种方式,那么ES呢,也是一种写屏障,只不过将数据定义为了面向DDD中的领域事件,我们可以通过Event来查询源头重新演绎领域对象的状态。同时,我们也在ES中使用了写屏障,那么这里我们也实现了一致性,不过是最终一致性。但是如果我们从关系数据库来看,其本质也是最终一致性,例如关系数据库在插入数据的某一刻它突然断电,重启之后,查询日志,该插入的数据还是插入(逻辑不是如此简单),那么从内部来看都是最终一致性。
从性能角度来考虑,那就有关于聚合根的粒度了,聚合根的粒度越细,Event必然越多。
ENode
EDA ES框架
前面所讲的都是我在看到ENode框架之后想到的,ENode框架不仅满足了分布式的需求,在DDD以及ES的概念上真正的做成了可以使用的框架,只要对DDD以及CQRS的概念理解的足够深,那么使用它将会是最好的选择。
要想使用ENode的所有特性完成项目,那需要团队成员对DDD有足够多的理解。
因为想采用ENode框架,所以将预研ENode框架的过程和大家分享一下。
在业务的可扩展性我们从DDD上我们就可以保证,所以无需多说。
引用作者的设计图
因为是预研性的项目,所以对于框架的易用性、易理解性、维护性,关注的稍微要少些。更多的是想理解作者的设计思想。
关注框架是否能满足项目的非功能需求、核心功能需求,所以先尝试关注以下几点:
1. 分布式
划分好上下文后,采用ENode框架实现,就可以直接使用分布式的基础设施将上下文的多个实例连接起来。
其实ENode本身就是支持分布式的,内部采用了作者自己编写的消息中间件EQueue,EQueue有点类似于Apache Kafka的设计,也是支持动态扩容的消息框架,从性能上来说整个就满足了分布式的需求。
2. 是否能满足一致性
(1)从写屏障的角度来说,ES可以部分支持一致性,一致性在分布式的环境中实现的方式有多种,涉及到几个概念,对一个操作至少执行一次,绝对只执行一次,当然还可以自己设计,例如不知道执行与否,就是我会执行但不知道执行结果。扯远了,如果要绝对只执行一次,在实现以及性能上都会有一些折扣,作者采用的是至少执行一次,怎么讲呢,至少执行一次所遵从的原则是必须经过了写屏障,如果没经过写屏障,那么我们可以认为这是无效的,经过了写屏障,系统宕机,可以通过ES重新发起事件。这里执行的就是Domain Event。
既然是至少执行一次,那就会有幂等的处理,作者设计非常灵活,允许使用者自己来装配,在作者的Sample中,使用了MSSQLSERVER来持久化Event,通过MSSQLSERVER中的唯一约束做乐观锁来做到幂等。
在聚合根的内部采用乐观锁来消除幂等,可以在很多个地方使用乐观锁,不管是ENode框架本身提供的,例如在Command Store、Event Store处由SQLSERVER来实现,也可以通过ZooKeeper实现,作者也提供了分布式悲观锁的接口以及采用SQLSERVER的实现,乐观锁就像是一个检查点,台湾人喜欢称这种检查点为机关,我们可以设置合适位置设置合适数量的机关来检查并发。
(2)对应关系数据库的隔离性,在作者的设计中,是按照聚合根来隔离并发的。
使用了聚合根的粒度来实现隔离,在聚合根内部采用了乐观锁或者悲观锁,减小了锁的粒度,来换取性能的提升。
在作者的设计图中还未完全标注出一些组件,
CommandHandler,EventHandler,ApplicationMessageHandler,ExceptionMessageHandler,不是很准确,有兴趣的朋友可以自行查看源代码。
Command、Event是DDD的领域语言
ApplicationMessage、Exception是作者在框架设计的框架领域语言
这些都是作为底层的可传递的消息,可以理解为分布式的消息,上面罗列的Handler都是作者默认实现的,采用的是Actor模式,都是有自己的MailBox,那么其实现就是顺序的并且是单线程的。在某一时刻,单个的系统实例中,Command、Event、ApplicationMessage、Exception都是在并行,但他们内部是只有一个在运行实际的逻辑。这也是可以装配的。