Reladomo:自备全套功能的企业级开源Java ORM(二)

\

本文要点

\
  • Reladomo是Goldman Sachs开发的一个企业级Java ORM,在2016年开源发布。 \
  • 性能是Reladomo的关键交付。通过提供对IO最小化、组合并扩展的功能,赋予了开发人员优化自身应用性能的工具。 \
  • Reladomo的定制内存有效地利用了内存,实现了IO的降低,并提升了性能。 \
  • Reladomo的企业特性集合使得它有别于传统的ORM。分片和支持时态对象是企业特性中的亮点。 \
  • 可测试性并非是后添加到Reladomo中的。Reladomo所提供的测试资源非常适合于编写高质量的测试,将会改进应用代码库的长期生存力。
\

本文第一部分中,我们介绍了Reladomo的可用性和可编程性等特性,并给出了一些指导开发的核心理念。在第二部分和最后一部分中,我们将介绍Reladomo的性能、可测试性及部分聚焦于企业应用的特性。\

性能

\

高性能解决方案是大规模可扩展企业应用的基石。从框架的角度看,性能包括两个方面:\

  1. 框架代码本身应该是良好优化的。 \
  2. 框架所暴露的模式应允许使用高性能代码实现应用代码。

具体到数据库的ACID交互,最关键的关注点可归结为正确的IO操作。我们在Reladomo中还发现,相对于带宽而言,延迟的问题更大,因此我们围绕延迟开展优化。简而言之,“正确的IO操作”意味着编写可最小化、可组合(即批处理)和IO可扩展(即多线程)的代码。\

最小化IO

\

在先前给出的本文第一部分中,我们已经介绍了deepFetch功能和Reladomo中的高级关系,这些功能显著地降低了对象图上的IO。在读路径上,Reladomo的完全定制缓存对降低IO会有显著的效果,我们随后将做详细介绍。在写路径上,对同一对象的多重写将会自动地组合到同一工作单元中,实现对数据库调用的最小化。\

组合IO

\

下面两个特性使得应用在进行查询时适当地组合IO:\

  • Reladomo支持将临时对象映射到临时表; \
  • Reladomo支持查询中的元组集。

回到我们分类账目例子中的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)中做出选取。\

分布IO

\

Reladomo主要采用两种有助于扩展IO的模型:\

  1. 分片(Sharding)。正如下面将介绍的,分片允许更多的并发写操作,因为它可以使用更多的硬件,并且无需过多地操心锁。 \
  2. Reladomo的对象身份合约(即给定主键在整个JVM中只具有一个持久对象)简化了多线程代码的推理。编写多线程代码可能会对IO产生巨大的影响。很多Reladomo中的基础API都暴露了内建的多线程,例如MithraList.setNumberOfParallelThreads,以及MatcherThread和MultiThreadedBatchProcessor等。

Reladomo的多线程加载器就是这样一种集成了所有概念的通用工具。它覆盖了一个简单但是高度循环并可重用的用例,即对于给定的输入的大型数据集(例如一个文件),什么是最有效地在数据库中插入、更新和删除相应数据行的方法?当以批处理方式将数据从一个系统拷贝到另一个系统时,该用例是非常典型的。使用多线程加载器的情况下,源和目标将被异步读取、比较并有效地回写。其中所涉及的组件包括:完成比较功能的MatcherThread、读取源和Sink的InputThread和DbLoadThread,以及完成写操作智能批处理的SingleQueueExecutor。大量地对读、比较和写操作使用多线程,这就是我们扩展IO的方法。DbLoadThread使用Reladomo提供的forEachWithCursor方法构建数据库的数据流,使得读操作最小化。SingleQueueExecutor对IO做智能组合,降低了死锁。只需要几行代码,就能让多线程加载器的实例跑起来。如果你具有这个用例,那么尝试一下!\

要使用上面所提供的功能,关键在于应用设计。在我们给出的分类账目例子中,如下设计将会显著地提升吞吐量:\

  • 分片,用于扩展IO。 \
  • 每个分片中,使用同一工作单元中处理多个交易,实现提交和查询的最小化,并将写操作组合在一起。\
    • 例如,为了查找传入交易中的产品信息,我们使用了大量的交易信息,实现为单一的数据库查询。
  • 余额计算以批量方式写数据库。 \
  • 在多个线程中实现对多个分片的用户界面查询。

Reladomo的缓存

\

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。在该实现中也非常易于插入其它的广播实现,只需要实现一系列的小型接口。对缓存通知机制的更多介绍,参见此处。\

被复制的堆外缓存

\

根据应用访问模式的不同,一些问题需使用内存中架构才能很好地解决。如果数据是存活于数据库中的(并且可能是分片的和双时态的),内存中缓存的扩张并保持最新是非常困难的,其中的原因包括:\

  • 要保持数据与数据库同步,需要一种避免将缓存置于不确定状态过长时间的策略。 \
  • Java的垃圾回收将在堆的规模达100GB时启动工作,应用在计算的过程中制造了垃圾。 \
  • 如果对象是在堆外区域之内或之外被序列化,那么堆外缓存结构将可能会导致更多的垃圾问题。 \
  • 堆外缓存结构难以维护对象间的关系。 \
  • 当单一节点无法满足计算需求的增长时,缓存最好应在多个节点上可用。

Reladomo的堆外缓存解决了如下的问题:\

  • 为保持缓存与数据库及复制的同步状态,Reladomo使用了审计时态维度。对于随新数据到来而不断推进的里程碑,Reladomo总是可保证应用代码所看到的是一致的数据。 \
  • 缓存结构允许不做反序列化即可访问所有的数据,这对于解决那些满足整体中大部分问题的应用(例如聚合)是非常重要的,也有助于保持垃圾回收的最小化。 \
  • 堆外缓存对象的API与堆内对象的完全一样,因此应用代码不需要根据缓存模式做更改。 \
  • 同样的索引结构也适用于堆内缓存,索引数据同样采用堆外保存。 \
  • 数百GB的缓存将能提供非常好的性能。 \
  • 缓存复制中采用了一种高效的算法,可轻易实现十个备份服务于同一个主机(Master)。 \
  • 正如堆内对象一样,对象间的关系是动态解析的,这使得堆外缓存具有一种简单且规范化的结构。 \
  • 字符串是特殊处理的。在此幻灯片中提供了更多的技术细节。

企业级特性

\

在企业场景中,相比于传统ORM可以提供的,需要对代码和数据做更宽泛的考虑。企业级ORM需要适合对象的全生命周期,从生成到停用。\

回到有价证券分类账目这一教科书例子,它具有如下的需求:\

  • 对收入交易的高速交易处理。 \
  • 基于收入数据(交易、价格等)的余额计算。 \
  • 繁重的用户交互,其中包括一些基于时间的查询,例如:\
    • 在过去的一天、一周或一月中,余额是如何改变的? \
    • 在过去的一个月中,这一系列交易是如何影响某个账户的? \
    • 在上一个季度中,特定收入产品的利润和损失情况如何?
  • 用户(或上游数据)在不损失再现性的情况下,对过去事件的更正能力。 \
  • 清空不再需要报告的数据。

要在统一的代码库中实现如上需求,Reladomo提供了一系列很广泛的能力。\

分片:水平可扩展的ACID

\

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\
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值