微信号:ArchDigest

介绍:每天一篇架构领域重磅好文,涉及一线互联网公司的互联网应用架构、大数据、机器学习等各个热门领域.

腾讯文学内容中心分布式统一框架的设计与实现(上)

2016-04-03 11:52 架构文摘

来源:94geek.com


摘要

我们设计并开发了内容中心统一的分布式开发框架Albian,由于它是基于Java的框架,也被简称为Albianj。这个框架主要是面向海量数据处理和访问的,也用于解决互联网开发中经常碰到的数据海量增长问题,以及开发团队中人员水平参差不齐的问题。Albianj应当具有良好的可扩展性与可定制性,它设计并运行在简单的Web容器中,如Tomcat或Jetty,也可以运行在application类型的应用中,不过它提供了企业级开发应当具备的一切功能。

目前业界成熟的框架很多,但自从EJB因太过复杂而被大多公司放弃之后,Spring+Hibernate就成了事实上的标准。总体来说,spring+Hibernate可以解决掉企业开发的很多问题,但在面对快速开发、海量数据处理和快速迭代引起的统一问题时,还是有或多或少的问题存在。所以,最后我们决定自行开发一个适合的框架,来对付SH组合无法解决的问题。

虽然Albianj和SH组合有很多的相似性,也许其中很多功能都是重复实现,但Albianj来自于我们对于掌控业务和代码的现实需求,所以更针对我们的实际情况,一方面更实用,另一方面也能有更好的可用性和可维护性。

Albianj满足了我们对于程序代码控制和对于处理海量实时数据的需要,目前已应用在生产环境中,主要为内容中心提供数据的存储和读取之用。目前部署的机器一共有上百台,以完成对于几十个数据库的各种不同模式的访问。

在本文中,我们主要阐述设计和开发Albianj的一些观点和某些策略选择的原因,以及如何设计和实现Albianj的,最后会列出实际中的开发效率和性能数据。

简介

Albianj的主要任务是统一程序员的工作,让他们在统一的口径下完成自己的工作,不过这仅是长远的战略目标,更现实的目标是提供一套简单的机制,以便访问具有复杂部署结构的后端数据库,并在数据量增长的过程中,方便数据再划分等功能。在开源的项目中,我们考察了guzz和hbm shared,前者配置过于复杂,同时我们对显式的指定数据路由不太能接受;而后者只是一个质量一般的补丁,且不支持分布式事务。使用数据路由之后,分布式事务肯定是无法避免的重要功能。综合考量之后,我们决定自己设计并完成整个框架系统。

其实与市面上提供的各种开发框架功能类似,Albianj最基本的功能只是IoC和ORM,可以看出我们还是在追求对于OO的控制。但Albianj不仅有这些功能,更多强调一致性:包括数据的一致性,甚至对程序员写代码的一致性和可控性,兼具对各种主键的一致性和可控性;其二,它还需要解决一个大多数的框架都没有解决的问题,那就是数据路由的能力。简单而一致的数据路由功能可以快速开发大数据量的业务,也可以在短时间让自己的系统重新适应并且无缝的连接已经重新切分好的数据。相比传统的开发框架,我们在设计和开发Albianj的时候考虑了更多的内容,并在其中进行了有的放矢的选择,引申出和以往不同的开发思路,也实现了我们开始对于Albianj设计时制定的目标。

首先,Albianj和别的框架不一样的核心点是“单维性”,简单来说就是一种需求只会用一种办法来解决,轻易不会出现第二种办法。就算为此牺牲一定的编程便利性,Albianj也需要守住这个底线。这个模式与spring之类的框架有质的不同。一方面无需迎合新的概念或者技术,频繁对Albianj进行升级和改造;另一方面,也无需为了多功能而引入不必要的复杂性。从程序员写代码的角度来说,只需学会一种途径就能依葫芦画瓢似地解决类似的问题。这样的结果就是代码会无比的统一,熟悉Albianj之后,开发也是无比的迅速。这是我们一直追求也一直遵循的必须准则,Albianj所有的功能都建立在这个标准之上。

其次,和别的框架一样,我们必须也需要使用配置,但未采用“约定优于配置原则”。随着Java越来越依赖Xml的配置,很多Java程序员有40%的时间在写代码,其余时间都在编写或copy配置。随着抱怨增多,出现了一个规则,就是坑爹的“约定优于配置原则”。出发点其实和HBM的sharing是一样的,也仅是一个补丁而已。互联网开发10多年之后,我们总结了一下:目前Xml的配置一部分是因为功能的复杂性,另一部分是因为滥用,两点相加才出现了目前这种境况。为了解决前面的失误,又加入了一个补丁,就是约定优于配置,但问题没有真正解决。所以Albianj在设计的过程中就尽量对配置做减法,仅仅保留必要的配置。不过如果配置和一致性、可维护性出现冲突,则选择一致性和可维护性,放弃了精简配置。

第三:Albianj是所有组件的总称,其实包括了多个不同功能的组件。这些功能性组件除了基本kernel以外,都被设计成插件式的。我们采用了一键安装和尽量透明的策略。一些地方只需加上某些配置,就会自动启用功能。保证解决问题一致性的同时,又不失开发上的便捷。对于整个系统而言,不仅解决了开发效率的问题,更是解决了年久失修这个困扰团队已久的问题。

第四:Albianj还解决了团队中开发人员水平参差不齐的问题。因为恪守一致性的原则,只要使用得当,写出来的代码几乎是一个模板刻出来的,不管程序员是什么水平,写出来的代码几乎都大同小异。在关键的数据路由等功能性问题上,Albianj提供一些接口,程序员只需实现这些接口,除此之外并没有任何代码需要完成。

最后也是最重要的一点是:Albianj是我们整个团队在互联网开发10多年来的经验结晶,它容易设计且容易完成,而且由于量身定做,使用上不存在任何碍手碍脚的地方。由于总体架构全是我们自己从头搭建的,解决bug或易用性问题也非常简单。我们并没有因为整个Albianj的开发而导致业务延期,相反在业务的开发中,我们不时地发现Albianj需要改进地方并进行改进,生成的效率也有所提升。这些更改和优化也会一并在本文体现。

设计概述
目标

上面我们简单的介绍了Albianj的一些设计的思想和准则,并声明了Albianj在系统中的地位和一些基本功能。本节中我们将详细说明Albianj设计的目标和中间出现的挑战。在设计和实现的过程中,我们印证了一些初定的目标,也根据实际情况对其进行了一定的筛减和优化。


  1. 首要目标就是一致性:在开始设计Albianj的时候,一致性就是终极追求。在一个软件的生命周期内,一致性的作用不仅体现在代码可读性上,也体现在后期易维护性上。尤其在团队人员开始流动时,拥有一致性和可维护性的软件会拥有更长的生命周期。另外,一致性更能节省前期程序员的工作,降低bug出现的机率。

  2. Albianj必须能应对海量数据的访问。现在的互联网系统中,海量数据和高并发访问越来越普遍。对于庞大的系统来说,应对海量数据和高并发流量的策略就是拆分。所以本质上Albianj需要面对上百台甚至上千台的后端服务器,它们可能是分布式存储,也可能是关系型数据库,或者是后端的业务接口服务器。Albianj可以提供一种简单而有效的方法来应对那么多的机器。实际的情况其实并不只是这么简单,因为Albianj本身就在分布式的环境中,它需要部署在成百上千台服务器上,以提供最基本的框架服务。

  3. 作为一个开发框架,Albianj必须能随时应对机器的增加和损坏,必须在有限的时间内来应对机器部署的改变,而不需要更改代码或只是更改有限的配置。传统来说,就算机器一切安全而稳固,整个服务也会因为数据量的增加而需要重新审视架构以及机器的部署,更多时候需要拆分和移动数据,而我们需要在面对复杂架构调整的时候,轻易解决问题,让系统重新稳定架构的成本降到最低。

  4. 作为整个系统最基本的底层,Albianj需要适应更多功能的快速加入,并在Albianj的规范内快速形成组件化,做到即插即用。这不仅是代码级别所考虑的事情,更多是需要在设计的过程中就注意新功能添加的便捷性。也就是说需要从头就开始考虑以后的可扩展性,做到一致性、可维护性、可扩展性的平衡和兼得。

  5. Albianj作为这个内容中心的框架,也必须要对程序员做到尽可能的友好,又必须考虑一些敏感信息对于系统安全的影响,在这两者之间取得相应的平衡,达到最好的亲和性和安全性。这个挑战不仅在于Albianj的使用上,更多是需要平衡团队内部参差不齐的开发者的编程水平。

  6. 因为Albianj是从头搭建的一整套完整的框架,难免会出现bug或使用不那么顺畅的地方,因此还必须提供一整套的完备机制来第一时间解决bug或易用性的问题。


在总体的设计思想上,Albianj遇到的挑战非常严峻,特别是对于一致性的追求,与安全性、易用性、可扩展性之间平衡的需求,难度不是一点点。然而不管怎么样,最后还需要具体的细化到各个技术点的目标上才可以告别空中楼阁,有落地实现的机会,所以我们又对Albianj在技术上定下了一些目标,组合起来就可以实现上文提到的一些对于Albianj的期望。


  1. 首先为了解决一致性问题,我们必须使用一致的方法来实现各种功能。Albianj必须设计和实现一套OOP编程机制,其中包括接口、实现、配置之类的相关标准;还必须设计和实现一套代码的命名标准与相关逻辑组织标准;必须建立和实施一套我们使用Albianj的标准和方法。因为我们的业务代码最后都会建立在Albianj上,所以必须要夯实作为基础的Albianj和相关的规则规章,才有可能实现严格的一致性需求。

  2. 对于Albianj来说,它其实是一整套我们经常使用的功能性工具集的集合,所以务必要做到在不损失alabinaj整体性的同时,使用合理的切分方法来设计和实现Albianj的各个子功能集。所谓合理的方法,其实无非也就是系统的划分原则,在Albianj中我们使用了“统一依赖、各自管理、各自实现、减少干涉”的原则,我们给这个原则取了一个名字叫“边界原则”。具体就是各自实现自己的功能,尽可能减少子系统间的相互依赖,尽可能减少跨系统生命周期内的依赖。

  3. Albianj放弃了“惯性原则”,使用显式的声明来完成所有的功能性配置或代码的编写。每个人的受教育程度不同,开发经历也不一样,对待系统的认知和视角也各不相同,所以基本无法真正的做到大家在同一个认知水平上来处理事务,也就是说“惯性原则”中最重要的“惯性”对整个团队来说基本上是无法实现的。所以必须要提供一种技术或一种机制来约束这个开发者各自的“惯性”,达到对整个系统有一致的惯性的理解。

  4. 作为大家使用的一个功能性集合,Albianj必须要保持简单,并且要通过Albianj的存在隐藏Albianj后端的业务部署关系,不管是业务集群还是数据库集群。开发者只要使用Albianj,就可以简单地把整个系统当成单机系统一样的开发和测试。


架构

在设计Albianj之初,其实并没有非常明确架构标准。我们一致认为:架构的设计实现与架构的层次类别区分并不是由某些理论、某篇论文、某个观点来进行的,而是在真正实现的过程中,随着功能点的增加和实现的代码逐渐增多,自然而然的去对整个系统进行审视和调整,这部分的工作会一直循序渐进的继续下去,永远不会停止下来。

到目前为止,Albianj已经演化成了具有8个子功能集的大架构,它们之间并无复杂的依赖关系,除了简单依赖于同一个kernel以外,别的所有依赖都是按需而定的。



在框架开发发展了这么多年的今天,对于一个框架来说,寻找一个架构上标新立异的机会几乎不可能了。而不同点在于使用的便捷性和框架的设计出发点。Albianj主要面向互联网开发,这是一个基本的立足点,所以首先不会是大包大揽的功能性集合,一些互联网不会用到的功能一律删减,但对于关心的问题也结合实际情况和需求解决掉了,从而分门别类划分的这几个功能性子集基本上已经代表了Albianj所能精简的极限了。下面会依次的对这些功能性子集做出解释,并且解释可能选型的取舍原因。

kernel

作为整个Albianj的核心,它有2部分组成。一部分是Albianj基本上都需要用到的公共功能;另外一部分也是Albianj都需要用到的,并且是更加重要的、满足我们对于管理service需要的ioc功能。

先说公共功能。这部分包括了很多的功能性的封装,包括但不限于hash算法、输出型参数、日期操作、网络基本接口、反射基本接口、运行时栈信息、加密解密安全、数据验证接口、xml解析等功能,包括所有Albianj功能集可能会使用到但又无法分开归类的功能。我们把这些功能统一的放到kernel下面的公共功能集下面。

这部分的功能可以直接拿来使用,但更多的时候除了验证机制以外,别的功能都不太用得着。所以不清楚或不知道这个功能包并不影响合理地使用Albianj。

而ioc功能则是整个Albianj的核心,同时对于使用Albianj作为开发框架的业务系统来说,也是它们的核心。ioc提供了一个控制反转的功能,将我们的接口和实现分开,然后根据自己的需要加载实现。传统的ioc(比如spring)会有N多种的注入方式,初始化参数、接口、属性等等。但是Albianj仅仅使用一种方式:接口方式。选取这个方式的主要原因有以下几个方面:


  1. Albianj认为提供多种的注入方式会引起代码的混乱和配置文件的复杂度,对于可维护性来说,过多的解决方案可能会适得其反。当有多个同事使用多种方法时,虽然方案被限制在一个框架内,相互之间还是会增加学习和维护成本;

  2. 选择接口注入的方式是因为Albianj需要对于service进行控制。在比较了一些注入方式后,接口注入是最适合Albianj的一种方式。为了更好的管理各个service和以后的扩展(在下一点有详细说明),Albianj必须要干扰service的初始化和使用过程。Albianj需要给每个service标注它的生命周期,以提供包括惰性加载之类的各种功能;

  3. 上面讲到以后的扩展。对于Albianj来说,目前的service都是在本地运行的,不会出现remoting的调用,也不会出现微服务的模式。但互联网的世界瞬息万变,现在不需要不代表以后也不需要,为了让Albianj活的更长久,有更强的生命力,Albianj的ioc功能被设计成可扩展的。以后如果需要加入remoting的过程调用,也只需要修改service初始化部分即可,并且加入remoting调用后,生命周期和惰性加载也将会更加适用。


Albianj的ioc只需要一个配置文件来控制,配置好name、接口和实现,除了配置以外,Albianj还要求所有的service都必须实现IAlbianService接口,如果是使用默认Albianj的service功能,为了方便Albianj也提供了FreeAlbianService的基类。这些规则都满足以后,开发者就可以根据Albianj提供的方法从ServiceRouter中根据配置的name获取service就可以了。剩下的事情都是由Albianj内部来完成。

所以Albianj的kernel提供了一个解析xml的功能,albainj会把配置在config文件夹中的配置文件根据实际启用的功能集来加载和解析xml。但是Albianj的xml解析只是提供了最基本的接口功能,因为每个配置文件的格式都是不一样的,所以具体的解析配置文件的工作被丢给了实际的功能集来完成,这样一来方便了开发者对于Albianj的二次开发,二来也是大大的节省了Albianj本身的代码和人力成本。

那么现实的问题是那些service从哪里来?在Albianj启动的时候,它们根据你的配置已经初始化了。显然Albianj使用了单例模式对service进行控制。Albianj在使用的时候需要使用者在进程启动的时候显式的调用Albianj的启动函数,已启动并初始化整个Albianj环境。这部分的启动不仅初始化Albianj的kernel,更初始化了使用Albianj的所有已配置的service和所有已配置的功能,包括后面会讲到的数据路由、ORM、配置等。为了更快的启动Albianj和进程,Albianj的加载方式还提供另外的一种异步加载功能。但使用Albianj必须在Albianj加载后使用。异步加载只是提供了进程,可以在加载Albianj的同时,加载它所需要的另外的一些功能。

Albianj的kernel除了提供最重要的这两部分之外,在kernel内部还提供了线程池和异常处理,也定义和实现了log接口(关于log后面会有详细的叙述)。线程池的作用主要是提供异步的数据操作,这部分会在ORM和缓存部分为使用和提及,而异常是整个Albianj的基础组件。这里所提及的异常是经过完善后的异常,和程序直接抛出的异常并不是完全相同。这里的异常将会包括比原始异常更丰富的堆栈信息,它主要面向的是程序员开发和业务处理过程中的日志,让开发者或是运维更快速的确定发生异常的地点和原由。

就目前来说,Albianj的service已经足够我们使用,当面对的压力变大,系统需要扩展的事情,只要扩展FreeAlbianService的生命周期实现功能就可以了。Albianj给每个service都定义了一个生命周期,状态依次是:


  1. Normal:表示service刚刚构建出来,还不能被Albianj初始化,并且不能直接使用;

  2. Initing:表示service在构建出来后正在执行Albianj的初始化功能,在这里你可以初始化刚刚生成的service,典型的例子是当service作为微服务提供的时候,在这里可以想nameservice注册service;

  3. Running:表示service已经在正常运行状态,使用者可以通过Albianj框架调用这个service;

  4. UnLoading:表示service正在被卸载,一般来说,这步除了设置状态和卸载initing的时候加载的资源就没有什么了,但是如果这个service作为微服务,那么需要在这里把service从nameservice中注销掉;

  5. Unloaded:表示service已经卸载完毕,正在等待GC回收。service到这一状态后,它将不能被Albianj调用,所以这一状态对于开发者来说是透明的;


Albianj的ioc是Albianj所有功能的基础,通过简单而可靠的方式实现的ioc为Albianj提供了最大的方便。在扩展性方面,Albianj也给了开发者在统一的基础上最大的自由。下面的所有Albianj组件都是建立在Albianj ioc的service模型的基础上。所以搞清楚Albianj的ioc,基本上就搞清楚了Albianj的一半。


DataRouter

数据路由其实设计和实现Albianj的初衷,也是花费那么多的力气,重新找一个轮子的最基本的动力。上面提到的kernel其实也是因为数据路由的存在而设计和实现的,kernel的最初功能只是为了管理数据路由中使用到的service,但是在实践中发现这部分其实也是整个系统所需,才开始认真的重新设计和重构了原本属于DataRouter的这部分,然后把它单独的作为一个功能开放给开发者。

我们设计和实现数据路由很大一部分的原因是:目前没有找到一个合适我们实际情况的路由组件。数据路由:它主要完成的功能是当开发者对数据库进程操作的时候,数据路由可以根据配置的路由信息把查询或者是数据提交到正确的目的地。这些目的地包括但是不仅限于数据库,还有可能是一个分布式的存储,或者是一个nosql的数据存储服务。目前互联网的现状是,几乎每个公司对于数据库的路由功能相对来说迫切程度比较大,但是找了一下开源,结果还是挺失望的,发现并没有一个简单而有效的数据路由可以直接拿到使用。连可以拿来做二次开发的都没有。那么在无路可走的时候,我们开始自己设计和实现数据路由。

结合我们的实际情况,首先的问题就是数据存储的异构性。目前我们大量使用了mysql作为主力存储,mysql确实也因为优异的性能表现扛住了压力,但是这不代表我们仅会在mysql上做开发,可能也会引入别的数据库,比如oracle。所以Albianj的数据路由必须要支持异构的数据存储。在支持异构存储的情况下,后面的扩展才不会大动筋骨。

其次,根据实际情况,albanj的数据路由必须要支持存储的运维部署。比如支持双写模式、读写分离模式等等。更高的需求是这些运维模式的支持必须是无缝的,对于开发者来说是透明的。在支持存储部署模式的情况下,Albianj还必须做到简单而有效的支持数据的迁移等等后期的维护问题。这样在目前我们的运维人手和DBA人手都不是很充足的情况下,可以做到尽量简单的维护我们的系统,节省人力成本。

再次,Albianj的数据路由必须可以随心所欲的进行控制。比如路由的开启和关闭,或者是路由的重定向等等。只要存储的数据已经到位的情况下,Albianj的路由必须可以在第一时间进行系统的支持。这部分也需要对开发者透明,仅仅需要运维的改动就可以实现。

然后,Albianj的数据路由还必须支持数据的完整性。对于所有的系统来说,数据的完整性是必须的要求之一,也是最重要的要求之一。因为数据路由的加入,使得其实对于开发者来说,后端数据库已经被屏蔽了。后端的数据库是不是采用分布式部署等等信息全部被屏蔽掉,所以Albianj必须要替开发者在Albianj的层面上解决这个数据的完整性问题。这个完整性问题在分布式数据库的时候被扩大,原本的事务被扩大成分布式的事务,也就是说Albianj也要必须能支持分布式的事务。

最后:也是Albianj的一直坚持的一点,在支持这么多的功能的时候,必须要做到统一。必须要做到配置上的统一、代码级别上的统一。

经过前期实际的项目实施,Albianj的数据路由被总结成4种情况:


最简单的一种是直接把一个对象保存到路由指定的数据库.

稍微复杂一点的是需要同时保存多个对象到各自指定的路由数据库,典型的操作是在保存一个对象的同时再保存一份日志.

再复杂一点的保存多份,也就是多写.

最后是Albianj必须还有路由sql语句的能力,而不仅仅是路由数据对象.

上面的饼已经画的足够的大,现在是实现这个饼的时候了。把空中楼阁落地。首先确定的一点是这个庞大的数据路由必须在我们的Albianj内部实现,而不是使用类似于mysql proxy这种组件。最简单的原因就是proxy这种组件只能解决相关的单一类型数据库的问题,它不能解决所有数据库的问题。也就是说不能解决在扩展中需要的异构数据存储的问题。那么唯一能剩下的就是在JDBC的时候做手脚了。先来看一段简单代码:


    cmdText="INSERT INTO User01 Values....";
    connString = "database=db1;user=root...."
    Connection c = new Connection(connString);
    SqlCommand cmd = new SqlCommand(c,cmdText);
    SqlTransaction tran = c.getTransaction();
    try {
        tran.begin();
        cmd.Exec();
        tran.commit();
    } catch() {
        tran.rollback();
    }finally{
        c.close();
    }


这基本上就是实现一个JDBC最简单的模型,我们需要在这段代码中绣出花了,让这朵花能支持Albianj的数据路由功能。仔细看这段代码,其实除了cmdText和connString这两句以外,别的代码都是已经被公式化的,也就是没办法更改。而这两句的是仅仅是能定义的两句。其中cmdText中的表名和connString中的database值是能自定义的,那么现在的问题就是怎么样通过一个规则确定我们这里表名和basedata的值。其实这个规则就是确定数据路由。在Albianj中,因为Albianj位于底层,Albianj只是调用路由规则,并不定义路由规则,路由规则都是有开发者定的,所以现在的问题又变成了怎么样来调用规则?

在调用规则之前,还要确定一个事情:在哪里调用规则?或者说为了什么而调用规则,一个很明显的问题是不可能让程序员自己直接去写JDBC代码,所以Albianj必须要提供一套对象和数据库的映射,那么ORM也就被引进进来了。有了ORM,又需要更改表名,那么一个地方已经找到了,就是在数据对象转成Sql语句的时候把表名给拼出来。那么最后一个问题,database的问题,在什么时候确定database的值?database的使用是在连接数据库的时候,所以必须要在连接数据库之前确定这个值。而数据库又和路由相关,所以在确定路由的时候就可以确定database的值。那么最后,Albianj演变成路由3部曲:



对着这3部的路由选择,在Albianj中也提供了一个基类来完成实现这个功能。在Albianj中,FreeAlbianObjectDataRouter基类就是这个实现类,在通常的情况下,开发者只有继承这个类就可以了,对于自己的路由需求,可以通过这个类里面的不同的方法来实现,这个类一共提供了6个方法,分成2组,一组提供给读服务,另外一组提供给写服务。但是仅仅是这个基类还无法完成Albianj提到的路由功能,整个的路由功能必须还需要配置文件的配合。其中drouter.xml就是配置路由的配置文件,drouter根据每个对象来定义它的路由,在发生数据操作的时候这些路由会被Albianj自动调用;还有一个配置文件是storage.xml,这是配置所有数据库连接的文件,这里的配置信息需要和drouter.xml中的路由配合起来使用。

Albianj在这里并不会牵涉到实际的业务数据路由拆分策略,这是因为具体的拆分策略都是需要根据自己的实际业务来决定,而Albianj只是要实现一个可以实现这种策略的机制而不是具体的事实策略,所以这部分将不会在本文中出现。


ORM

在Albianj中,因为一些异构数据库的问题,要实现数据路由,必须要依靠ORM来解决这个问题,那么现在,我们开始说说ORM。

在Albianj中,ORM也是被作为一个基本的组件实现。但是Albianj中的ORM和很多开源框架的ORM有很多功能上的取舍。Albianj对于ORM进行了精简,已保留Albianj最需要的部分为前提,把所有不相干的功能全部删除掉。所以Albianj中的ORM其实仅仅只是一个对象和关系数据库的实体和表结构映射和相关操作。它并不提供在ORM层面处理复杂的对象间关系依赖的问题,它也没有涉及到很多的ORM都会存在的多种功能,比如查询方言等等。这部分的工作有的被Albianj转嫁给了程序员来完成,有的直接就是不在Albianj的设计理念之内,所以被删除了。而被转嫁的部分,在Albianj的设计理念中,可以让程序员更好的完成业务开发,也可以更好的处理数据的加载等问题,虽然在工作量上看似增加了开发者的工作,其实对于系统整体来说,这部分的工作可以让开发者更好的应对业务问题和系统压力问题。

但是Albianj的ORM集成了上文提到的DataRouter,Albianj主要是为互联网开发而设计和实现的。对于Albianj来说,解决大并发和海量数据显然要急迫于解决复杂而吃力不讨好的ORM中的对象问题、全功能问题等等。也因为加入了DataRouter,Albianj必须要重新设计ORM的数据一致性功能。

数据一致性的问题在ORM中可以简单的转换成事务的一致性问题。当数据库只有一台的时候或者数据都塞在同一个数据库的时候,数据库可以解决掉事务的一致性问题,但是当需要使用分布式数据库的时候,显然单机的事务已经无法满足系统对于数据一致性的要求,所以Albianj必须引进能在多台数据库服务器之间可以保证数据一致性要求的技术。在Albianj中,这种技术使用了二次提交来完成。

在Albianj中,每一次的数据库访问被成为是一个job,这个job仅仅与现实业务相关,和Albianj需要访问的数据库实例的数量没有关系。或者说Albianj可以在一次job中访问多个数据库实例。所以,job在abianj中其实是一个分布式事务的控制中心,它对多个数据库实例的事务进行了抽象和统一接管,但是job不直接管理各个数据库实例的事务和链接,这部分的工作被Albianj交给了task来处理。在一个job中,需要操作的数据库实例一一对应于task。task接管了对于数据库实例的操作和事务过程。从而可以让Albianj支持分布式事务。



Albianj的分布式事务并没有采用并行策略,也没有采用嵌套策略。而是同时采用了这两者的结合体。因为分布式事务的性质决定了在一些极端的是情况下,数据库中的数据还是会有可能存在不一致的情况。这是采用分布式事务的时候无法避免的问题。但是Albianj也进行了最大程度的去规避这个严重的问题。Albianj直接使用分布式锁搭配上job-task结构就可以处理规避掉这个问题。

Albianj在对操作数据的接口中,置入了通知机制,当Albianj的事务发生错误的时候,首先是自动去回滚,但发生了硬件级别的错误,比如断电等问题时自动回滚无济于事,Albianj会通过通知机制来通知系统维护人员,对这个数据进行回滚。在回滚的时间段,Albianj的锁服务一直锁定着资源保存事务发生时的状态,最后维护人员将这个资源释放。这种极端的情况几乎是不太可能发生的。现实中,因为业务的特殊性问题,Albianj目前服务中严重资源抢占情况比较少,所以这套流程还没有真正派上什么用处,但Albianj为分布式事务提供了这种机制,以满足以后系统扩展所带来的数据一致性问题。

显然Albianj的分布式事务对于使用环境要求比较苛刻,产生的效果也并不一定能达到100%的事务完整性。但这并不是我们故意为之,而是因为分布式事务本身就是不可控的。分布式的不可控性到目前为止都没有非常完善的解决机制,只是尽量通过各种策略方法规避问题,以达到事务的一致性。

是不是就没有一种办法能解决这种不可控性?所谓魔高一尺道高一丈,方法总比苦难多。所以前辈们又研究出来另一种方案来替代Albianj所使用的方法,总结下来就是:拆分-重试-记录。

我们把它称为“记帐”模式。把整个的事务切分成多个单个事务,最后由job把这些事务的对象发送到一组消息机器。这时处理方式分成两步,前台直接给用户返回提交成功;服务器开始自己的工作。服务器端有一组机器不停地去取消息中的数据,取到数据就执行,每次记录下取到的数据和版本号等信息,作为判重的依据,这样依次执行队列中的消息,当执行过程中发现异常的时候,还是自动回滚,如果发生硬件级别的问题,也是通过通知机制强行恢复数据。




记帐方式相比Albianj方式的优点是可以重试,可以在网络不那么稳定的环境中进行安全的事务,而Albianj事务的解决方案其实就是一锤子买卖,无法在网络不稳定的情况下不断重试来实现分布式事务。但是Albianj使用的方法成本更低,性价比相对第二套方案更高,对于开发人员来说也更方便。所以我们最后采用了这种直接通过job和task抽象,管理数据库实例连接和事务的方式。

事务的问题解决后,Albianj开始设计和实现数据访问接口。在Albianj中,对于数据的访问一般都是通过JDBC来处理,目前Albianj提供的接口有MySQL和SQLServer的。当然也可以很方便的就实现Oracle的。对于数据层操作来说,也就是简单insert、modify、delete和select功能,这些都需要Albianj使用反射和配置文件配合解决。这里的配置文件是persisten.xml,一个数据实体结构和数据库表结构一个对应关系的文件,Albianj提供了对字段各种属性的配置,比如是否主键、长度、数据类型等。每个Albianj使用的实体必须在这里登记,以便Albianj引擎可以在启动时加载这些实体的信息,供后面的ORM使用。这个配置文件还提供与缓存的集成,可以通过缓存的配置节,让实体自动支持缓存操作。这样我们在Albianj的persisten层也要提供一个对缓存支持的操作,所以就有了find和load簇函数的区分。find簇的函数表示先查找缓存,当数据无法在缓存中找到的时候,再调用load簇函数从数据库中直接获取。

为了给程序员提供最大的方便,Albianj还集成了save方法。简单来说就是当这个实体的数据在数据库中存在时就更新,没有的话就执行插入操作。那么这就牵涉到两个问题:怎么让Albianj知道数据是不是在数据库中存在?如果更新,怎么知道数据库中的数值和目前保存的数值有无区别?

要解决这些问题,Albianj对所使用的对象进行了统一的接管和管理。Albianj提供一个IAlabianObject接口,来完成对象的统一工作,按照惯例也提供了一个基类FreeAlbianjObject供开发者使用。在Albianj中更推荐使用FreeAlbianObject基类而不是IAlbianjObject。因为使用IAlbianObject,开发者还必须要自己实现Albianj制定的规范,而FreeAlbianObject已经实现了这个规范,没必要再实现一次。而且自行实现还有考虑不周或实现失误的风险,不如直接使用FreeAlbianObject方便。那么这个接口到底提供了什么功能呢?它包含了一个IsNew属性,这是属于Albianj的kernel属性,开发者无法更改,它标明了实体对象是新创建还是从数据库中获得。另外这个接口还定义了一个私有的map来管理这个实体原来版本的值(如果有的话),在Albianj执行ORM的时候,会通过这个map中的值和对象中的值进行比较来确定哪些字段需要更新。这也说明了从另外一个角度来看,当有对象需要更新的时候,必须先从数据库中先load一下最新的数据,才能使用已发生更改的对象来更新数据库。

另外一种特殊的情况发生在读取时,读取数据并不一定都是通过对象的唯一ID来获取,虽然Albianj推荐使用唯一ID。所以Albianj提供了IFilterCondition来实现对于数据的筛选,为了更快的满足业务,还提供了IOrderByCondition接口来提供排序。这是Albianj为数不多的不OO的地方,也是为了节省劳动力而变通的地方。

Albianj的ORM重点并不在于实现全部的ORM功能,它删掉了诸如延迟加载等很多原本属于ORM的功能,但却独树一帜的和数据路由功能结合在了一起。Albianj的ORM通过合理的工作分配和调度,使用最小的代价实现了最大的实用价值。


未完待续……请继续往下看。


版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。


-END-


架构文摘

ArchDigest

架构知识丨大型网站丨大数据丨机器学习

更多精彩文章,请点击下方:阅读原文

 
架构文摘 更多文章 免费开源分布式系统日志收集框架 Exceptionless 电商峰值监控经验谈 小米网抢购系统开发实践 唯品会峰值系统架构演变 从0到1,一号店通用推荐平台的搭建
猜您喜欢 Web前端设计原则 移动应用测试平台(测试APP) 你还需要另一个周刊吗? 走向线下的社交场景 MySQL replicate-ignore-db详解