Gemstone OODB 将对 JRuby 及 Rubinius 提供支持

  • Werner Schuster
  • Jason lai

2007 年 8 月 16 日

话题:Ruby数据库DevOps语言 & 开发架构

Gemstone对象数据库Object DatabasesOODB)类产品的开发商,它的 OODB 产品中包括基于SmalltalkGemstone/S和基于JavaFacets。目前这家公司正在致力于为他们的产品提供Ruby支持。简而言之:JRuby 将获得 Facets 的支持,而对于非 JRuby 用户则会使用Rubinius,使用一些和 Gemstone/S 相似的特性。

简单地说,OODB 允许用户透明地持久化对象图(Object Graphs),这和关系数据库(Relational Database,RDBMS)正好相反,在关系数据库中对象数据一定是保存在数据表中的,从而引入了对象关系映射(Object Relational Mapping,ORM)的概念。ORM 可以手动实现,比如说开发人员将数据储存在数据表中,也可以借助工具或者类库来完成,比如说 Ruby 的ActiveRecord或者 Java 下的Hibernate

我们采访了 Gemstone 的Alan McKean,借此发掘出近期与 Gemstone 和 Ruby 相关的开发工作的更多细节:

Alan 解释了这些努力是如何与他们的产品范围相契合的:

GemStone 拥有两个对象数据库产品:其中一个基于 Java(Facets),而另一个则针对 Smalltalk(GemStone/S)。我们正着眼于将 Facets 应用于 JRuby 之上,以及将 GemStone/S 用于 Rubinius。由于 JRuby 项目已经发布了 1.0 版本,因此我们正在这个平台上进行第一次想法证实的尝试。到目前为止,一切都进展得不错。要使用 Facets,必须下载我们自己的JVM(这个 JVM 已经经过 Sun 的授权,并且我们对一些字节码进行了调整,以便对 Java 对象进行透明持久化。这个 JVM 的速度会比目前 Sun 的 JVM 稍慢一点(在我的测试中大约慢 5% 左右),原因是在持久化上做了一些附加工作。

Alan 目前与 JRuby 团队一道着手对由 JRuby 运行时当前实现细节所引发的一些问题进行处理:

在 JRuby 实现中存在着一个要去处理的问题,而我已经向 JRuby 团队提交了修复代码。目前 JRuby 并不支持序列化(或者我们的持久化方式),因为每一个 Ruby 对象都有对 JRuby 运行时的直接引用(线程、动态方法和其它内部对象)。当这些对象被序列化(或者持久化)的时候,它们会带着整个运行时系统一同序列化(持久化)。因此我发现了一种使这些对象从整个运行时系统中剥离出来的办法,并允许序列化和透明持久化。这项工作完成之后,我们整个持久化引擎就可以正常工作了。

要想体验一下到底将 JRuby 和 Gemstone 用在一起是什么样子的,我们可以看看下面的范例代码,代码中使用了持久化对象:

# to persist an object in one VM  declare a # persistent 'root' that allows access to objects with the # 'name' of the object persistent :people => :name    # begin a transaction, put a Ruby object and all objects that are  # connected to it into the persistent root, and commit it   p = Person.new('Alan') p.children << Person.new('Jimmy') p.children << Person.new('Janie') transaction   people << p commit   # in another VM  persistent :people => :name p = people['Alan'] puts p.name p.children.each do { |child| puts child.name }

Alan 对 API 的设计进行了阐释,并解释了在Rails中如何使用这套 API:

我们正在为这套透明持久化方案设计一套 API。在 Rails 中,你不会用到“transact”和“commit”。你将只需声明持久化的根对象,然后将对象放入根对象。当请求进入时,事务边界会被创建,随后会在响应被发送之前进行事务提交。

当我们把 OODB 的工作方式和ActiveRecord作比之后,前者带来的好处就更加显而易见了:

因为 ActiveRecord 的大部分代码存在的原因是对象和关系型数据库的不匹配现象,许多 ActiveRecord 的代码都将变得毫无必要。例如,belongs_tohas_many等的存在都是为了提供对关联(数据库中的外键等)的支持。同样的,像acts_as_list等,我发现很有意思的是,ActiveRecord 需要许多重复的构造,违反了 DRY 原则。在 Ruby 类中声明关联并在 Schema 中设置外键,这一切都会消失的。

但我们确实打算支持 Filters 和 Validations 等,这些部分看起来是否还会和 ActiveRecord 一模一样是值得怀疑的,因为和 ActiveRecord 不同,Ruby 对象是不需要从一个特定的类子类化的。透明持久化解决了的 ActiveRecord 的另外一个问题就是子类化。在关系数据库中,并不存在任何描述类层次结构的有效解决方案(可选的有稀疏数据表或者表连接)。我们的持久化方案则对这个问题提供了自然而然的支持。子类化你的核心内容,为你的领域类混入任意模块,这正好能正常工作。

接着,谈到技术细节,Alan 解释了 Gemstone 的定制化 JVM 如何让持久化成为可能:

我们是对 Ruby 对象的 Java 表现形式进行持久化的。JRuby 对象是一些简单的 Java 对象,这些对象带着实例变量的 Map,它们并不基于映像。尽管我向 JRuby 团队提交的补丁确实允许序列化,但是我们并没有使用序列化。我们的持久化方案基于页缓存。在数据库提交时,会有一些小型(8K)的内存页被同步到数据库中,而因为应用程序使用的是对象引用,内存页面可能会从持久化存储中错误地换入。由于一般来说对象都会聚集在同一个内存页中,在页面被换入的时候很少会有分页动作的发生。

Ruby 对象是不具备良好定义的结构的。同一个 Ruby 类的两个实例在结构上可能会有很大差别,之所以出现这个现象的原因是因为针对不同实例调用的方法可能会动态地给实例加入实例变量。因此一个类实例所具有的结构(和实例变量)是和它生命周期内发送给它的消息密切相关的。这和 Java 及 Smalltalk 的情况是非常不同的,在后两者中,所有的实例变量都被静态地声明在类中的固定空间内。因此,对 Ruby 对象的迁移并不是什么大不了的问题。当然,还是有有一些需求是有必要支持的(例如,在生成实例变量的方法被从类定义中移除的时候,同时也将这个实例变量和它相关联的值一起移除,但我们认为这是一个管理员的任务,应当由工具来支持)。

Gemstone 的 Ruby 支持还有另外一个分支,这个分支将基于 Rubinius。Rubinius 是一个采用了 Smalltalk VM 理念并(主要)使用 Ruby 编写的 Ruby 虚拟机。(InfoQ 最近发布了两篇对 Rubinius 项目的 Evan Phoenix 的采访文章:Rubinius 虚拟机内幕Rubinius 对象空间和线程模型内幕剖析)。就 Gemstone 将如何使用 Rubinius 的问题,Alan 做了如下解释:

顺带说一下,请多多关注 Rubinius VM 的开发进度。这个 Ruby 实现不在 JRuby 上运行,也用不到我们的 JVM。我们正在与Evan Phoenix一起协力,以确保这个 VM 能够立等可得地支持我们的需求。然后很可能你只需要安装一个 gem 就可以使用我们的持久化方案了。我们的初衷是为了提供一个免费的产品(单服务器限制4G 大小的数据存储库 [data repository]),我们在 Seaside 上就是这么做的。一旦您需要超过 4G 的数据或者您需要将扩展到多台服务器上,那么您就得购买产品和相关许可了。

Gemstone 一直忙于为新的 Web 框架和工具提供支持。紧随新的 Ruby 支持之后,Seaside也会得到相应支持。

我们正在将我们的持久化方案应用到 Smalltalk 的 Seaside 框架中,在今秋会发布出来。在 Seaside 中的方式将会和我们正在为 Ruby 和 Rails 探索的方式非常相似。

在早期的公告中这被称为GLASS,是 Gemstone/Linux/Apache/Smalltalk/Seaside 的缩写。

查看英文原文:Gemstone OODB to support JRuby, Rubinius

Ruby数据库DevOps语言 & 开发架构