Mnesia 一个用于电信应用系统的健壮的分布式DBMS
Mnesia 一个用于电信应用系统的健壮的分布式DBMS
2010年11月30日
IT老枪新浪专栏
IT老枪百度专栏
IT老枪搜狐专栏
IT老枪博客中国专栏
我搜企业
原文:http://www.erlang.se/publications/mnesia_overview. pdf Mnesia DBMS和拥有数据的应用系统运行在同一地址空间,然而应用系统不能销毁数据库的内容。Mnesia同时提供了快速存取的特性和很好的容错性,通常这两个需求是相互矛盾的。Mnesia的实现是基于Erlang编程语言的特性,Mnesia也内嵌到Erlang中了。
电信系统中数据的管理在许多方面(但也不是全部)与传统的商业DBMS(Database Manager System)相同。尤其是对许多"不停歇系统(nonstop system)"来说,在容错性上有非常高的要求,再加上需要有一个和应用系统运行在同一地址空间内的需求,导致我们设计了一个全新的DBMS。本文描述了这个新的被称为Mnesia的DBMS的动机和相关设计。Mnesia由Erlang语言实现,与Erlang的关系非常紧密,Erlang为实现容错性的电信系统提供了必要的功能。Mnesia是一个为了实现工业级电信应用系统而用Erlang编写的多用户的分布式DBMS,Erlang也是操作 Mnesia的理想语言。Mnesia试图涵盖所有的关于电信系统数据管理方面的问题,它有许多通常中传统数据库中不常见的特性。
电信应用中有许多不同于传统DBMS的特性需求。我们现在的用Erlang语言实现的应用系统需要有许多特性,这些特性是传统DBMS不能满足的,Mnesia根据如下需求设计:
快速实时的键/值查找;
复杂的非实时性查询,主要是为了操作和维护;
由于分布式的应用导致的分布式的数据;
高容错性;
动态重配置(Dynamic reconfiguration);
复杂的对象。
使Mnesia不同于其它DBMS的是,它是为电信应用系统中的数据管理问题设计的。Mnesia还将传统数据库中的许多概念与电信应用中的数据管理上的概念结合在一起,前者包括事务和查询,后者包括极快速实时操作、容错性的可配置度(指复制)configurable degree of fault tolerance(by means of replication)、不停机或挂机而重新配置系统的能力。Mnesia与Erlang语言的紧耦合也使它看上去很有意思,它使得Erlang语言变成了一门数据库编程语言。这带来很多好处,最主要的是通常由于DBMS中的数据格式与编程语言中的数据格式的不同带来的阻抗不匹配问题现在不存在了。
当前,Mnesia在Erisson中几乎所有的基于Erlang的工程中得到应用,从小规模的原型系统到大型交换机项目。
本文剩下部分如下组织:第2节是DBMS的简要概述,第3节列出了典型的DMBS功能,讨论了电信方面的功能以及Mnesia是如何提供这些功能的,第4节包括一些性能方面的测量,最后第5节总结。
Mnesia即是编程语言Erlang的扩展,也是一个Erlang应用程序。DBMS的组件,例如锁管理器、事务管理器、复制管理器、日志、主存储和二级存储(primary and secondary memory storage)、备份系统等等,这些都是由Erlang程序所实现的。然而,查询语言则是Erlang语法的一部分。Mnesia的数据模型是一种混合类型:数据由record表组织,record表类似关系数据库中的关系(relation),但是record的属性(包括主键key)可以是任意复杂的组合数据结构(如树、函数、闭包、代码等等)。这样,Mnesia也可以看做是所谓的对象关系DBMS。例如,我们定义人的record:
-record(person, {name, %% atomic,唯一性主键
data, %% 未指定的组合结构数据
married_to, %% 伴侣的名字,可以不指定(undefined)
children}). %% 孩子
有了这个定义,我们就可以用接下来的Erlang语法创建一个人的记录:
X = #person{name = klacke,
data = {male, 36, 971191},
married_to = eva,
children = [marten, maja, klara]}.
将变量X绑定到这个人的record。data域绑定到一个tuple:{male,36,971191}。这是一个复杂对象的例子,Mnesia对属性的复杂性没有任何限制,我们甚至可以将函数对象作为属性值。变量X只是一个Erlang 项式(term),通过如下语句可以把它插入到数据库中:
mnesia:write(X)
一系列Mnesia操作可以组织起来作为一个原子性的事务一起执行。为了让Mnesia执行一个事务,程序员必须首先构建一个函数对象,然后将其递交给Mnesia系统。我们通过一个例子解释,假设我们想写一个Erlang函数divorce(Name) ,它接受一个人名,从数据库中查找这个人,将这个人和这个人的配偶的married_to 域设为undefined值:
divorce(Name) ->
F = fun() ->
case mnesia:read(Name) of
[] ->
mnesia:abort(no_such_person);
Pers ->
Partner = mnesia:read(Pers#person.married_to),
mnesia:write(Pers#person{married_to = undefined}),
mnesia:write(Partner#person{married_to = undefined})
end
end,
mnesia:transaction(F).
divorce/1 函数由两条语句组成,第一条语句是 F = ...,用于创建一个函数对象,它什么都没执行,只是构建了一个匿名函数。第二条语句将这个函数交给Mnesia系统,它负责在一个事务的上行文中执行此函数,相当于传统的事务语法。
实际上函数F第一次执行一个读操作,用于查找给定名字Name的人,然后它执行第二个读操作以找到前者的配偶,最后执行两个写操作,将两条修改后的新记录(married_to 都已设为undefined)插入到数据库中,数据库中的旧值将新值被覆盖。函数divorce/1 将事务的值作为返回值,事务的值要么是{aborted, Reason} ,要么是{atomic, Value} ,这取决于事务是放弃了还是成功执行了。
Mnesia中的查询由表理解(list comprehension)语法表达[15]。一个用于查找所有生了超过X个孩子的人的名字的查询如下所示:
query [P.name || P X]
end
这被读作:组建一个P.name的列表,这里的P从person表中得到,而且每个P的children列表的长度超过X。将用户自定义的谓词混合中一个查询中也是可行而且自然的。例如有如下谓词
maturep({Sex, Age, Phone}) when Age > 30 ->
true;
maturep({Sex, Age, Phone}) ->
false;
查询可以是:
query [P.name || P X]
end