海派开源潮流社区

一起参与开源.共同打造海派开源潮流社区(Kernel/Fedora/CentOS/Perl/Drupal)

Drupal + Oracle:OraDrup 项目内幕

Drupal + Oracle:OraDrup 项目内幕

作者:Gavriel Pedros

出处:http://www.oracle.com/technology/global/cn/pub/articles/pedros-drupal.html

“开源的”和“标准的”这两个形容词缺一不可吗?将 Drupal 与 Oracle 数据库 XE 组合在一起所做的努力证明,答案是:这不是必需的。

2007 年 1 月发表

试举下列情形:在对一个有关实现的项目进行研究时,您阅读了一篇有关某个开源软件的网志或文章,其中说该软件是“不依赖数据库的”并且可以与“任何”支持 ANSI SQL 的数据库(例如 Oracle 数据库)协作。

经过一番调查,您发现该软件是一项很了不起的杰作。然而,再经过一段时日的深入探索后,您发现,事实上该软件只可用于极少量的数据库管理系统,而不是象写网志的人/作者所宣称的那样适用于所有标准数据库。因此,您取消了该项目。

该问题在开源软件中很常见,为了保留其所有优势不必采用“标准的”方法。该案例研究向您介绍 OraDrup 项目,其目标是将常用开源 Drupal CMS 与 Oracle 数据库组合在一起,使前者可供庞大的 Oracle 开发人员社区选择使用,反之亦然。

Drupal 是什么?

开 源 CMS 已经流行很久了。它们以各种形式出现,通过 Java、PERL、Python 以及无所不在的 PHP 等众多技术实现。某些 CMS 可能专注于特定类型的内容,例如可能带少量图像处理的文字。其他 CMS 则有很多功能,包括音频、视频、任意文档、结构化信息以及事件。

例如,Drupal 就是一种高级的内容管理框架。利用 Drupal 可以通过一致、有序的方式管理各种内容。Drupal 站点是面向多个用户的,可以充当网志、论坛、协作创建工具、目录、一般社区站点或者以上几项的任意组合。它是一种构建复杂 Web 站点或者将基于 HTTP 的界面整合到现有系统中的强大工具。

Drupal 在 2000 年刚开始出现的时候只是一个简单的、没什么名气的内部消息站点,其中有一个内置的公告板,供一群学员朋友在这里互留信息。之后不久,该站点就在万维网上占 据了自己的一席之地。它有了现在的名称,并以开源软件的形式进行发布。它引起了很多人的兴趣,很快开始呈现出自己的特色,其中综合了很多理念,如协作、内 容管理、工作流、联合、分布式身份验证等。现在,全球有大概 400 人正致力于该系统各个方面的工作,并且该人数还在持续增长。

Drupal 的构建高度模块化。它具有一个支持一组核心模块小内核,这些模块负责提供应用程序服务。例如,系统模块负责管理站点的一般功能、书籍模块允许用户协作创作 书籍、搜索模块负责建立内容索引以及进行基于关键字的搜索。核心模块数量很多,您可以根据要求随意启用或禁用它们。这确实使得该系统十分灵活。

在外部看来,扩展核心包提供的 Drupal 功能就变成了在其他模块中进行构建和插入。在该特殊的域,有贡献的模块并不匮乏。例如,如果要求对软件进行管理,则可以安装和激活项目模块。项目问题跟踪 模块进一步扩展了项目模块的功能。这些模块由 Drupal 社区主动进行维护和改进。这些模块丰富多样,如地理空间图示化、OpenID、思维导图甚至英文到“海盗语言”的翻译。

Drupal 的另一个优点是它的主题化系统。主题是指定 Drupal 站点外观的术语。在其他 CMS 中,该功能有时称为模板化或“换肤”。Drupal 很适应这样的自定义。

Drupal 主题化的好处是,自定义站点外观时根本不需要修改现有的代码。可显示项目(例如页面、图像、表单元素等)的生成通过一组名为“主题功能”的基准线呈现功能 来处理。主题系统允许开发人员将可选择的主题功能放入主题目录中,以使用和覆盖这些功能。例如,一个包含以前呈现为无序列表的选项的菜单可以很容易地针对 特定主题转换为一组动画选项卡。

为什么要将 Oracle 和 Drupal 放到一起?

Oracle 的契入点是什么?到目前为止,Drupal 主要是一个面向 MySQL 的系统。Drupal 安装中很流行的部署选择是 LAMP 堆栈 (Linux-Apache-MySQL-PHP)。当然,Drupal 相当于在家运行于能够安装 MySQL 和 PHP 的任何平台之下。

这对平常的实现来说都很好,但是有时 LAMP 中的“M”不是一个选项。有些机构可能没有那么大的财力可以将其他数据库服务器合并到他们的运营中。在其他情况下,MySQL 可能不是任务的合适解决方案。在此类情况下,不可能采用 Drupal。

此外,Drupal 有着这样一个活跃的开发人员社区,因此它完全可以进行扩展以将 Oracle 技术包含在内。为此,Drupal 可供基于 Oracle 的开发人员选择使用,反之亦然。如果是这样,看到它可以利用或受益于以下这些 Oracle 特性将会是多么令人感到兴奋的一件事:

  • 数据分区和并行化
  • 复制和队列技术
  • 真正应用集群
  • 透明应用程序故障切换和其他高可用性特性
  • 数据库级别的细粒度访问控制
  • 自动段空间管理

此外,Oracle 数据库的巨大影响力将会为 Drupal 争取到更多之前其往往都会受到忽略的展示机会。

关 于 Drupal 支持任意数量的数据库平台的潜力已经有过很多讨论。实际上,支持 PostgreSQL 与支持 MySQL 是紧密相连的。那么,为什么没有关于 Drupal 可在 Oracle 之下运行的报道呢?尽管他们对构建具有 Oracle 兼容性的项目抱有美好愿望,但这一愿望似乎深陷“百慕大三角”。因此,基于 Oracle 的 Drupal 实现似乎成为了一个神话 — 至今还无人见过此种实现。

我决定去探索该神话起源的第一手资料。将 Oracle 支持构建到 Drupal 4.7.0 中的难易度如何?我将通过创立 OraDrup 项目来一探究竟。

检查源代码

第 一个研究的问题是 Drupal 与底层 RDBMS 交互的方式。在检查代码时,我首先注意到的一件事是,Drupal 使用其自己的简单抽象层来与数据库通信。接口自然也是按一般和专用来划分。高级功能构建到通用层中,核心代码直接与该层对话。特定于供应商的实现很方便地 隐藏于可在后者类别中找到的 API 函数之后。

例如,分页查询的情况就可以展示该方法的优点,此时您希望 仅从查询的结果获取行的子集。您会发现核心代码中不会不断出现专用 LIMIT 子句。相反,该请求通过 API 函数 db_query_range() 实现,每个目标数据库都具有该函数。特定于平台的特性仍然留在本地并隐藏于此类逻辑操作之后。

如此,我们可以开始考虑下一个问题。鼓励在整个代码中使用不依赖于平台的 SQL,以努力保持 Drupal 不依赖于数据库。该方法可能极具争议性。尝试在所有数据库平台中寻求共识的过程中,大家会就最终使用的 SQL 的最低通用性这一问题产生争执。很有可能出现这样的情况,同一个查询在一个数据库的运行无懈可击,而在另一个数据库上则与之相反。以往的经验表明,使用基 于 SQL 的通用平台几乎不可避免地会涉及到修改某个目标平台,以便在要求很高时找出性能问题。然而,这些都是铁证般的事实。

当 我发现 Drupal 的数据模型中的某些列名有问题时,一个小问题暴露了出来。假设 Drupal 具有面向用户的特性,有很多表碰巧包含到用户表的外键。不幸的是,该表中的主键列恰巧名为 uid,与一个 Oracle 的保留字列表成员产生了正面冲突。其他相关的列名包括会话、注释和模式,更不要说存在名为“access”的表了。传说中的不依赖平台的 SQL 方法不能阻止该冲突,需要修改查询以适应目标平台,这一事实无法逃避。

说起查询,其中有少量查询不适合在 Oracle 上运行。例如:

  • SELECT 表达式(没有 FROM 子句和表名)
  • 涉及比较带空字符串的字符列的查询。根据定义,空字符串在 Oracle 数据库中为空,因此这些查询不会按照预期的那样运行。
  • 对非限定、歧义列执行 GROUP BY。这是非标准 SQL。

其中的一些问题后 来在 Drupal 的高级版本中得到了纠正,但是,最值得注意的问题可能是长文本的使用。Drupal 的数据模型大量使用很长的文本列。尽管 Oracle 的 VARCHAR2 数据类型远远可以满足大多数需求,但是很明显 VARCHAR2 的 4000 字符的限制在这些情况下并不适合。毕竟,如果 Drupal 可以在 MySQL 下管理任意大小的内容,您肯定也希望在 Oracle 下进行同样的操作。

很自然地,Oracle 的字符大对象 (CLOB) 数据类型是对此类列建模的不错选择。但是,在这种情形下选择 CLOB 也面临着一些挑战。

首 先,Drupal 的数据库查询喜欢使用长文本列,这在某些平台上是不受欢迎的 — 包括执行 SQL 来比较一个长文本值(长度可能达到 1 Mb)与另一个使用等式操作符的值。还有就是在某些查询中对此类列进行分组。当涉及 CLOB 和其他大对象 (LOB) 列时,这些操作在 Oracle 中是不允许的。

不依赖平台的 SQL 语法原则(尽管它的愿望是好的)并不能帮助避免这个问题。至此,如果还有人存在任何避免修改 SQL 的想法,很快就会打消这些念头。有时,开发人员更喜欢努力克服挑战,而不是去做他们更有把握的事情。坦白地讲,如果这个任务一点挑战也没有就完成了,那么 完成后的喜悦感也不会太多。

克服这些挑战的关键并不在于 Drupal 中的大部分 SQL 都与 Oracle 兼容 — 这是执行所有查询都要遵循的方式。检查类似系统的代码库时,源代码检查有时会伴随着恐怖的尖叫声,原因是检查中发现查询包含直接嵌入 SQL 主体中的参数值。开发人员由于可避免的硬分析、库缓存冗余以及资源浪费而受挫的现象十分普遍。

让人感到欣慰的是,Drupal 不会出现这种现象。人们很高兴地看到查询及其参数得到隔离,查询本身包含其参数的占位符。也许是同意 JDBC?无论动机是什么,这一事实使得引入绑定变量的任务几乎是小事一桩 — 并发现这是决定项目值得继续进行还是应该放弃的关键时刻。

总结起来,到目前为止有以下发现:

  • Oracle 保留字存在于数据模型中,在多个查询中出现
  • 执行过与 Oracle 不兼容的 Unorthodox 和非标准 SQL
  • 查询及其参数根据策略保持分离。

如果不可避免地需要解决这些问题,选择是很简单的:一头扎入核心代码中对查询内联进行修剪,或者挂钩到数据库 API 中,动态地给查询打补丁。

人们本能地认为前者是首选方法:如果出现问题,那么可以在其源头进行纠错。不幸的是,这意味着要直接修改核心代码并脱离主代码库。该项目的目的是表 明 Drupal 可以在 Oracle 下运行,最好不需要将大块的内心代码拆成小段。我们需要找出这样一种方法,不仅核心 Drupal 可以在 Oracle 中运行,而且起作用的模块也可以。如果这些模块也能利用该解决方案,那就更好了。我们值得投入精力来查明这是否是一个可行的解决方案。

实现决策

一个初步拟定的 Oracle 方案已根据 MySQL 数据模型整理好。而后经过如下简化,为以后的细化留出了空间并避免了浪费:

  • 所有 MySQL 整数类型(不管规范或域)都将映射为 INTEGER 类型。
  • 所有 MySQL 浮点类型都将映射为 FLOAT 类型。
  • CLOB 类型的使用将不受限制。

查询将利用参数隔离将绑定变量占位符插入 SQL。要使用该方法,需要重新实现通用数据库 API 函数 db_query()。这是不可避免的,经验表明该函数并不象预期的那样通用。

为减少登录数据库的次数,决定在获取结果时,请求结果集中的所有行,让这些行在本地缓冲供会话使用。这样做的原因如下:

  • 行的数量不会特别大。
  • 所有请求的行最终都会得到处理。
  • PHP 的 OCI8 扩展不会报告结果集中的行数。

请求所有行并在本地缓冲使得该信息可供获取。

接下来是查询重写机制。关于保留字的消除,简单的方法是在 Oracle 的数据库 API 实现中构建一个检查器,识别这些保留字并将它们放到引号里。简单的搜索和替换就可以了,但是必须注意这是 regex 搜索,很是费力。不过,它非常简单。

棘手的任务是识别需要重写的有问题查询。为了帮助解决该问题,构建一个简单的 QueryRewriter 类,该类可以提取 SQL 字符串,并决定其是整洁还是需要修改。方法是为每个“恶意”查询创建一个实例,并在识别到此类查询时删除其转换任务。这样,所有通过 Oracle 数据库 API 的 Drupal 查询都将得到纠正而具兼容性。此外,按 Drupal 模块对 QueryRewriter 实例进行分组可以将检查器的数量减少到只是激活模块的数量。采用该方法的缺点很明显。获得该级别灵活性所需的代价需通过 CPU 循环来衡量。由于大多数 Drupal 的 SQL 查询未经过更改就通过该过程,因此需要执行大量本可以避免的工作。这给直接在核心代码中修复 SQL 的参数带来的额外负担。然而,采用重写方法的主要动力是好奇心使然。

综合全部

开发使用 Oracle9i 数据库和 PHP4 进行。通常的想法是使用该环境作为起点,然后在以后转移到 Oracle Database 10g 和 PHP5。最初的转移不是没有挑战,但是大多数问题都可以使用 OCI8 的最新版本得到纠正。

初步创建和建立数据模型。构建 Drupal 数据库 API 过程简单正规,很快就完成了。之后,创建 Drupal 安装并禁用 Drupal 中的所有非必要模块。构建查询重写器是一个很辛苦的过程,因为有些恶意查询并不容易发现。最后,它变成了既要分析源代码又要进行即席测试。每过去一天都会 看到一个新的内核模块加入 Oracle 大家庭,直到模块列表完成。理论上,Drupal 现在与 Oracle 兼容。

编写一个简单的 PERL 脚本,用不同长度的文章来填充表。之后一千次调用该 Fortune 程序,Oracle9i 之下的 Drupal 已经准备好模拟实际的安装。手动创建其他文章,并对其应用过滤器。这些文章会得到相应的评论,进而这些评论会收到回复。而后通过内容生成 RSS 反馈等。将站点从英语切换为法语以及从法语切换为葡萄牙语时也是如此。所有内容都运行得相当好。测试期间,用不了多久就可以发现系统的整体响应时间不长, 但却一点也不令人兴奋。考虑到涉及 PHP 任务的工作量,就很好理解了。同时,考虑到这是初步拟定的实现,对于全面提高应用程序的性能还是相当乐观的。

但是,一个惊喜已经出现端倪。升级到 PHP5 之后,响应时间显著加快,原因可能有多种,其中包括更新的 OCI8 扩展重构版本。Drupal 页面在远低于一秒的时间内就可以返回。启用 Drupal 的缓存机制还会进一步缩短响应时间。记住,整个系统在单独的 1GHz Pentium III(充当 X、Web、MySQL 和 Oracle 服务器的主机)下运行,系统的确运行得相当好。

然而,最好的还没有出现。是时候在 Windows 2000、PHP5 和 Apache 下尝试 Drupal 了。安装 Oracle 数据库 10g 快捷版 (XE)(Oracle 数据库 10g 的免费版)轻而易举。(相当有意思的是,整理好 PHP5 和 Apache 并使其正常运行所需的时间竟然比 DBMS 多。)与之前一样在相同的硬件上运行,该特定配置的 Drupal 安装令人震撼。响应时间只能用“惊人”来描述。最后,情况变成有必要验证 Drupal 的配置,以确保它没有错误地与 MySQL 数据库交流。结果是没有出现错误交流。与项目之初所想一样,Drupal 可运行在 Oracle 数据库 XE 之下。神话最终消失了。

我们现在进展如何?

测试系统的所有方面的期限延长后,对功能问题的关注也增加了。我们提供了源代码下载,并进行了志愿者征求。截至 2007 年 1 月,这就是该项目的当前状态。如果有希望成功完成大量的用户测试,发现问题解决问题,那么该过程的下一阶段是着眼于可伸缩性,集中改善性能。

在开发完成时,我们得出了下列结论:

“不依赖平台的”SQL 不是一个好的想法。使 用不依赖平台的 SQL 的方法愿望很好,但是对于复杂的软件来说是不切实际的。该项目中的经验对此提供了有力的论证。语法只是部分因素。我还没有提及不同锁定模型导致的细微差 别,以及“所有数据库通常以相同的方式工作”这样的假设对正确性和可伸缩性的影响。这似乎是很明显的,但是,通过类似 Drupal 主题化运行的灵活方式,利用将不同的数据库平台全部包含在内的体系结构,该项目的实施会相对容易一些。更重要的是,这将为充分利用目标数据库管理系统打开 方便之门。在我看来,如果可以随意使用负担沉重的 DBMS,那么,若不鼓励您使用其提供的众多自定义特性,那会是多么遗憾的一件事。这说明 Drupal 还很年轻,还可以继续发展。它的设计有很多正确的东西,未来版本中会有许多可供谈论的精采特性。看到提倡对数据库 API 进行此类更改的一些创新思想是不足为奇的。

与数据库的干净通信很重要。系统化隔离 Drupal 使用的所有 SQL 查询及其参数的战略决策似乎无关紧要。该方法作为 Drupal 清理查询参数以阻止 SQL 插入攻击的方式的一部分出现。系统并不是将该职责留给每个模块开发人员,而是将清理作为其查询处理的标准部分来执行。主要是该特定设计决策极大地简化了 Oracle 兼容数据库 API 的构建,原因很简单,构建 Oracle 数据库 API 时首先想到的将是使用绑定变量。如果查询需要以编程方式清理或者与其参数分离,则该项目在开始之前就被取消的可能性很大。Oracle 确实提供了一些很有用的特性(例如配置设置 CURSOR_SHARING = FORCE 和 CURSOR_SHARING = SIMILAR)以阻止此类查询的负面影响。这不足以说服我让我继续。最好是看到采用正确的方法,不需要让数据库好像在地毯之下除尘一样解决此类问题。令 人感到欣慰的是,Drupal 凭借其干净、规范的方法脱颖而出。

数据库模型只会变得更好。用于该 Oracle 实现的初步拟定的数据库模型的确是很基本的。因此有大量的空间进行细化,以减少浪费并提高数据库性能。还没有人试图通过策略方式将表组织为特定的表空间、 检查索引、执行分区或者诸如此类的工作。主要目标是看是否可通过函数方式更正 Drupal 实现。假设之后的阶段中,有大量的空间进行全面的审核、数据管理和调整。

性能增强应该予以调查。关于容易实现的性能增强,PHP 中间代码缓存和优化器的帮助极大。所有开发都没有使用任何此类软件。此类工具可以将 Drupal 的性能提高到什么程度,目前还是个未知数。应该说明的是,Drupal 本身在性能增强方面正在经受进一步的开发。例如,即将推出的 Drupal 5.0 版承诺将改善会话处理和访问检查,以减少他们加之于数据库的负载。它还以配置选项的形式提供了“聚合”缓存策略。的确有大量的选项要调查。

另一个应该在性能增强的通用领域下解决的是连接管理。每个 HTTP 请求使用一个支持开放和关闭连接的连接池有很大的好处。最近,众所周知,Oracle 的未来版本将提供数据库驻留连接池。这会显著提高基于 Web 的应用程序的性能。有一些方法值得探究,但是这些方法适用于其他项目。

动态查询重写机制因灵活性而被采用,尤其适用于简化站点管理员的维护。理想的方式是,引入新的数据库平台时,Drupal 管理无需修补或替换核心文件。通过安装模块达到同样的效果似乎是更受欢迎的方法。采用该方法的另一个原因是,参与 Drupal 开发的人可能只熟悉 MySQL,因此无法在需要时提供 Oracle 兼容性“调整”。该方法允许创建独立创建的临时“模块兼容性包”。尽管采用了查询重写方法,但是人们公认查询不需要即时更正,而应该在源代码中有效。毫无 疑问,这是最好的方法,可以避免在字符串处理上丢失许多 CPU 循环。也许对于 Drupal 的数据库 API 来说,下一个发展步骤是能够执行供应商特定的 SQL?

结论

人们仍然认为,项目的成果包括少量构成数据库 API 的临时文件以及直接挂钩到系统中的 Drupal 模块。

那么,既然 Drupal 和 Oracle 已经可以协作工作了,这可以达到什么效果呢?这仍有待观察。这只是梯子的第一阶,我的希望是激发起大家足够的兴趣,为该项目提供充足的动力进入下一个级 别。假设这种技术组合最终是可能的,那么只要一点雄心加上一些想象力,要不了多久就可以产生十分有趣的结果。

OraDrup 项目的网志和源代码可在 bitfine.com/oradrup 上找到。


Gavriel Pedros

是一名信息技术专家,主要钻研新兴技术和开源软件。他有很强的设计、开发和管理背景。Gavriel 的兴趣包括性能增强、Java、C++、web 技术以及音乐创作。从 Oracle 5 开始,他就一直使用数据库技术。

将您的意见发送给我们

 

Reserved by www.17LAMP.net