本文共 4912 字,大约阅读时间需要 16 分钟。
\\本文要点
\
- Reladomo是Goldman Sachs开发的一个企业级Java ORM,在2016年开源发布。 \
- 性能是Reladomo的关键交付。通过提供对IO最小化、组合并扩展的功能,赋予了开发人员优化自身应用性能的工具。 \
- Reladomo的定制内存有效地利用了内存,实现了IO的降低,并提升了性能。 \
- Reladomo的企业特性集合使得它有别于传统的ORM。分片和支持时态对象是企业特性中的亮点。 \
- 可测试性并非是后添加到Reladomo中的。Reladomo所提供的测试资源非常适合于编写高质量的测试,将会改进应用代码库的长期生存力。
在中,我们介绍了Reladomo的可用性和可编程性等特性,并给出了一些指导开发的核心理念。在第二部分和最后一部分中,我们将介绍Reladomo的性能、可测试性及部分聚焦于企业应用的特性。\
高性能解决方案是大规模可扩展企业应用的基石。从框架的角度看,性能包括两个方面:\
具体到数据库的ACID交互,最关键的关注点可归结为正确的IO操作。我们在Reladomo中还发现,相对于带宽而言,延迟的问题更大,因此我们围绕延迟开展优化。简而言之,“正确的IO操作”意味着编写可最小化、可组合(即批处理)和IO可扩展(即多线程)的代码。\
在先前给出的本文第一部分中,我们已经介绍了deepFetch功能和Reladomo中的高级关系,这些功能显著地降低了对象图上的IO。在读路径上,Reladomo的完全定制缓存对降低IO会有显著的效果,我们随后将做详细介绍。在写路径上,对同一对象的多重写将会自动地组合到同一工作单元中,实现对数据库调用的最小化。\
下面两个特性使得应用在进行查询时适当地组合IO:\
回到我们分类账目例子中的Balance对象。如果我们想要从一对Account/Product的列表中检索Balance,可以如下编写代码:
MithraArrayTupleTupleSet tupleSet = new MithraArrayTupleTupleSet();\tupleSet.add(1234, 777); //在查询中添加Account和Product信息。\tupleSet.add(5678, 200);\tupleSet.add(1111, 250);\\TupleAttribute acctAndProd = BalanceFinder.acctId().tupleWith(BalanceFinder.productId());\\Operation op = BalanceFinder.businessDate().eq(today);\op = op.and(BalanceFinder.desk().eq(\"A\"));\op = op.and(acctAndProd.in(tupleSet));\\BalanceList balances = BalanceFinder.findMany(op);\
对于小规模集合,Reladomo会将上面的操作转译为一个“or-and”语句。而对于大规模集合,则需要在后台使用到临时表,并对Balance表做连接运算。\
在写路径上,包括自动批处理在内的批量操作工具将有助于增强写操作。Reladomo对事务中的写操作重新排序,在无需对正确性做出妥协的情况下最大化批处理。Reladomo也会选取一个适合于工作情况和数据库规模的批处理策略。例如,Reladomo可以在四种不同的插入策略(即bulk、union、multi-value和jdbc-batch)中做出选取。\
Reladomo主要采用两种有助于扩展IO的模型:\
Reladomo的多线程加载器就是这样一种集成了所有概念的通用工具。它覆盖了一个简单但是高度循环并可重用的用例,即对于给定的输入的大型数据集(例如一个文件),什么是最有效地在数据库中插入、更新和删除相应数据行的方法?当以批处理方式将数据从一个系统拷贝到另一个系统时,该用例是非常典型的。使用多线程加载器的情况下,源和目标将被异步读取、比较并有效地回写。其中所涉及的组件包括:完成比较功能的MatcherThread、读取源和Sink的InputThread和DbLoadThread,以及完成写操作智能批处理的SingleQueueExecutor。大量地对读、比较和写操作使用多线程,这就是我们扩展IO的方法。DbLoadThread使用Reladomo提供的forEachWithCursor方法构建数据库的数据流,使得读操作最小化。SingleQueueExecutor对IO做智能组合,降低了死锁。只需要几行代码,就能让多线程加载器的实例跑起来。如果你具有这个用例,那么尝试一下!\
要使用上面所提供的功能,关键在于应用设计。在我们给出的分类账目例子中,如下设计将会显著地提升吞吐量:\
Reladomo提供了一个定制缓存,该缓存比任何通用缓存都更好地匹配了ORM的需求。Reladomo缓存并非Map结构,而是一系列的无键索引,其中每个索引都是一个可搜索的集合或多重集。构成特定索引标识的属性是任意的。缓存总是具有一个主键索引,其它索引是根据应用的定义或是对象间的关系而添加。\
为允许缓存覆盖整个JVM,Reladomo保证具有特定主键的对象在JVM中只存在一次。这使得该缓存比基于会话的缓存更为有用。此外,也使得该缓存比二级序列化缓存更加高效,因为无需对同一对象做二次存储,也无需做序列化和反序列化。不同于其它的ORM缓存,Reladomo缓存中的对象对应用是可见的,并可被应用所使用。\
缓存也完全可感知Reladomo的事务上下文。事务上的更改操作(即insert/update/delete操作)在事务内部是可见的,但只有在更改提交后才对外部可见。\
缓存可配置为按需填充(基于被触发的查询),或是在启动时完全填充。完全填充的缓存适用于小型静态对象(例如,“国家”或“货币”)。在一些适当的场景下,完全填充的缓存也适用于大型的数据集。\
缓存结构也可完全定制,用于临时对象的存储(参见下文)。临时对象需要一种完全不同的内存中存储,因为业务主键并不能唯一地标识一行。Reladomo的SemiUniqueDatedIndex是一个单一数据结构,对同一数据以两种不同的方式做哈希。\
对缓存做了更专业的讨论。\
在企业场景中,一个特定的数据集不太可能只被一个JVM访问或更改,这对ORM缓存提出了严重的问题。为解决该问题,Reladomo的缓存支持缓存间的通知机制。通知由一系列轻量级的缓存过期消息组成,通过广播网络发送。Reladomo创造性地给出了一种广播网络的TCP实现,适合于数百个规模的JVM。该TCP实现为一个基本的“轴辐式”(Hub-Spoke)模型,并出于容错的考虑而加入了双重Hub。在该实现中也非常易于插入其它的广播实现,只需要实现一系列的小型接口。对缓存通知机制的更多介绍,参见。\
根据应用访问模式的不同,一些问题需使用内存中架构才能很好地解决。如果数据是存活于数据库中的(并且可能是分片的和双时态的),内存中缓存的扩张并保持最新是非常困难的,其中的原因包括:\
Reladomo的堆外缓存解决了如下的问题:\
在企业场景中,相比于传统ORM可以提供的,需要对代码和数据做更宽泛的考虑。企业级ORM需要适合对象的全生命周期,从生成到停用。\
回到有价证券分类账目这一教科书例子,它具有如下的需求:\
要在统一的代码库中实现如上需求,Reladomo提供了一系列很广泛的能力。\
ACID,即原子性(Atomic)、一致性(Consistent)、隔离性(Isolated)和持久性(Durable),是Reladomo存储的基本假设之一。扩展ACID需要进行审慎的设计。为此,Reladomo提供了内建的分片特性。对分片的识别也保存在对象中,一并维护在内存中,而不是持久性存储上,并成为对象完整身份的组成部分。这简化了传统主键的构建,不必担心全局唯一性。例如,可以赋予分片A中的一个交易以一个简单数字标识“17”,这样以此为标识的分片就可以被其它交易所用的分片重用。\
分片是作为Reladomo API的头等部分处理的。这意味着,与分片对象交互不需要进行配置转换或是代码隔离。一个查询可以跨越多个分片,这是Finder API天然提供的功能,将分片属性与其他属性等同对待。例如:
Operation op = BalanceFinder.businessDate().eq(today);\op = op.and(BalanceFinder.desk().in(newSetWith(\"A\
转载地址:http://wtjtx.baihongyu.com/