Erlang 的 Mnesia——为高伸缩性应用准备的数据库管理系统

  • Gavin Terrill
  • 岳立东

2007 年 8 月 22 日

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

看来我们很可以称 2007 为“数据库正规化的神秘面纱被揭穿的一年”。我们已经看到了一些有意思的讨论,一些大型网站为了能够处理每小时上千万的访问请求,而放弃了传统关系数据库的路子。根据Joe Gregorio 最近的观察,出现了这样一些常见方案(这里强调的是我的观点):

如果你想伸展到拍字节(Petabyte,2 的 50 次方,约千万亿)的级别,或者每天数十亿请求,你就需要:
  • 分布式部署。数据必须分布到多台机器上去。
  • 无表连接(Joinless)。至少在数据存储层次上,不能使用表连接(Join),也不要定义关联完整性约束。
  • 去正规化。虽然没有人直说,但我想如果你要避免使用表连接,就会有很多去正规化工作。
  • 无事务。不能按事务方式操作。
这些约束从根本上区别于(传统)关系数据库。

这些特征可以用来描述Mnesia,Erlang 的分布式数据库管理系统,通过复制方式支持高伸缩性和容错性,可以不需要传统关系数据库的表连接就检索记录。Mnesia 属于Erlang/OTP项目,是为运行于 Erlang 虚拟机上用 Erlang 语言开发的应用设计的(有用于 C/C++ 和 Java 的接口)。

在 1999 年发布的白皮书《Mnesia——适用于电信应用的分布式健壮数据库管理系统》中,作者(Håkan Mattsson、Hans Nilsson 和 Claes Wikstrom)介绍了这个数据库的概念和关键区别,这些概念和区别所具备的预见能力很是令人惊叹。以下来自摘要:

Mnesia 数据库管理系统运行于数据拥有者应用的同一个地址空间中,但是应用不可能破坏数据库内容。这保障了快速访问的同时能够有效容错,而这二者一般是相互冲突的需求。Mnesia 植根于 Erlang 编程语言,基于 Erlang 的诸多特性而得以实现。

论文第一节解释了当初建立这么一个数据库管理系统的动机是电信应用苛刻的容错和高可靠性需求。这些要求是:

  1. 实时快速的键 / 值检索;
  2. 非实时的复杂查询主要在运营和维护时进行;
  3. 分布式的应用,从而数据也必须分布;
  4. 高容错性;
  5. 动态重新配置;
  6. 复杂对象。

论文第二节概览了 Mnesia 的组件。Mnesia 由多个 Erlang 应用组成,它们提供数据库管理系统的核心服务,比如锁定、事务管理和复制。作者们指出 Erlang 非常适合用来实现这个系统,它包括了大概 2 万行代码。查询语法是 Erlang 的一部分,数据模型类似于对象 - 关系数据库管理系统。

Mnesia 另一个有趣的地方是与 Erlang 编程语言的严密耦合,基本上把 Erlang 变成了数据库编程语言。这样好处很多,最大的好处是数据库使用的数据格式和编程语言用来操作数据的数据格式之间完全不会出现阻抗失配。

其数据模型支持表的概念,一条记录等价于一行,其中每一列都可以存储“任意复杂的组合数据结构,例如树、函数、闭包和代码等等”。一个 Erlang 复杂记录的例子如下:

X = #person{

name = klacke,

data = {male, 36, 971191},

married_to = eva,

children = [marten, maja, klara]

}.

Mnesia 也支持一个和视图(View)类似的概念,这个概念被称为规则(Rules)。

复制是使 Mnesia 成为一个容错数据库管理系统的机制之一。数据表可以被复制到混合网络中的多个节点,而对应用来说却是透明的。被复制的数据表是对等的,也就是说没有主 - 从结构。

论文第三节详细描述了 Mnesia 的一些独有特性:

  • 复杂值。Mnesia 以自然有效的方式支持复杂值的操作(换言之,通过一次检索操作)。“以第三(甚至是第一)范式组织电信数据通常不太可能。”列里可以存储复杂值就意味着不再需要表连接。
  • 数据格式和地址空间。此数据库管理系统运行在应用的地址空间里。这就允许检索返回对象指针,而不需要在不同数据格式之间暗地转换对象,或者通过连线去操作对象。论文提及到 Erlang 中一个应用即使崩溃也不会影响到数据库,这克服了这种方式下应用的崩溃有可能影响数据库的常见诟病。“Erlang 进程的效能有一个优势,运行在同一个地址空间但是不可能直接读取或者写入其他进程的存储空间。”
  • 容错。数据表可以被复制到多个计算节点中。事务上下文中的写操作会被应用到所有副本,通过恢复时更新,当时宕机的副本也能够被更新。“这个机制使得多个地理上分开的系统联合提供一个连续运行的不停机系统设计成为可能。”
  • 分布和位置透明。应用开发者能够透明访问数据表——无论远程的还是本地的,还是数据表副本。不过在性能关键的地方,也有办法获知数据表的位置,并且让代码在尽量靠近事发数据的位置执行。
  • 事务和 ACID。Mnesia 支持 ACID(A= 原子性,C= 一致性,I= 隔离性,D= 耐久性),不过也提供仅限于内存的数据表操作能力(以牺牲耐久性为代价)。
  • 绕开事务管理器的能力。“脏接口(dirty interface)”的概念被作为一个轻量级加锁机制引入,从而避免使用事务管理器的额外开销。这个能力对性能关键的操作比较有用,比如检索一条记录。“这些脏操作 是真正的实时数据库操作:无论数据库是大是小,他们都消耗可以预测的执行时间。”
  • 查询。查询通过被称为“列表领悟(List Comprehension)语法”的方式表达。例如,要找到子女多于 X 个的人,表达式可以写成:query [P.name || P < table(person), length(P.children) > X] end.
  • Schema 的修改。Erlang 语言自身既支持不停止进程情况下修改运行中进程代码的能力。这就允许 Mnesia 数据库动态的修改 Schema。“Mnesia 既然是为不停机应用设计的,所有系统活动比如备份、修改 Schema、导出表数据到二级存储, 还有副本复制都必须在后台进行,同时允许应用和平常一样访问和修改数据表。”

在论文第四节中,作者们探索了具体实现的各方面,比如持久、锁管理、查询实现,以及分布应用的各节点可能会运行于不同字节顺序的计算机的情况,进而使得这样一个系统能够工作于混杂环境中。

第五节讨论了性能问题,发现“脏接口”比事务控制下的对应部分快得相当多。如预期一样,同步复制的代价使事务执行时间明显延长。完成一个事务的时间(以毫秒计)在单节点上是:常规锁——1877,显式写入锁——1225,而使用“脏接口”的话是 181。如果是 3 个节点的话,相同事务类型测量到的时间依次为:13372、12185 和 1121。

论文得出如下结论:

Mnesia 系统现在正被爱立信用来构建实际的产品,也就是说它不再只是一个原型系统,它已经成熟到可以贴上产品的标签。

Mnesia 的开发从该论文后一直在继续,加入了如分片表(fragmented tables,类似 shards,但在数据库层次上处理)等更多特性,也被用在开源项目如YAWS(Erlang Web 服务器)和ejabberd(XMPP 服务器)中。

对于横向分片数据,Mnesia 在伸缩性和低延迟事务上表现突出, 接下来的一个挑战可能是对于超大规模数据集它如何伸展。有人提到了超过 6 千万行的例子。然而如Bill de hÓra 最近写到的,增长的数据容量将迫使我们重新思考我们的数据库策略:

我想在对日常编程工作的影响上,增长的数据容量要远远超过多核。我过去几年所做的工作,一个不变的主题就是处理越来越大的数据集。被 Joe Gregorio 称为“Megadata(兆数据)”(但现在希望他没这么说过)。大数据集已不再是几家大公司的深奥议题,而是变得非常常见。

然后得出结论:

大容量意味着你需要不用管数据的去向而只是写入。而且对于关键字检索方式的读取,你需要基于文件系统,而不是关系数据库管理系统(在没有表连接、约束或触发器的前提下,关系数据库管理系统就是一个索引了的文件系统)。那样最终就是像HadoopMogilefsS3一样的东西——并行数据架构。

随着对 Erlang/OTP 的兴趣提升(包括最近的 InfoQ 文章),这个项目最终也应该是像 Mnesia 一样的东西。

查看英文原文:Erlang's Mnesia - a distributed DBMS for highly scalable apps
译者简介:岳立东,Ableverse 创始人,技术推广大使。开源项目WoW(内含 ToB 对象数据库)和SecureJSH创建者。目前致力于东道组件接合(Hosting Based Interfacing)理论研究,在此基础上的软件并行分布架构及对象数据库应用,技术博客http://complystill.javaeye.com/。欲参与 InfoQ 中文站内容建设,请邮件至china-editorial[at]infoq.com
数据库Erlang架构语言 & 开发