原文链接:http://hideto.iteye.com/blog/235413
本章描述了构建分布式、容错的Mnesia数据库相关的高级特性:
1)索引
2)分布和容错
3)表分片
4)本地内容表
5)无盘节点
6)更多的schema管理
7)Mnesia事件处理
8)Mnesia应用调试
9)Mnesia里的并发进程
10)原型
1,索引
如果我们知道record的key,那么数据获取和匹配在执行起来都很高效
相反如果不知道record的key,那么表里所有的record都必须搜索
当表越来越大时,表的搜索就越来越耗时
Mnesia的索引就是用来解决这个问题的
下面的两个方法对已有的表操作索引:
1. mnesia:add_table_index(Tab, AttributeName) -> {aborted, R} | {atomic, ok}
2. mnesia:del_table_index(Tab, AttributeName) -> {aborted, R} | {atomic, ok}
这两个方法对AttributeName定义的域加索引和删除索引:
1. mnesia:add_table_index(employee, salary)
Mnesia的索引用于以下3个方法:
1)mnesia:index_read(Tab, SecondaryKey, AttributeName) -> transaction abort | RecordList
通过在索引里查询SecondaryKey来找到primary key,这样就能避免对整张表穷举搜索
2)mnesia:index_match_object(Pattern, AttributeName) -> transaction abort | RecordList
通过Pattern里的AttributeName域查找secondary key,然后找到primary key
3)mnesia:match_object(Pattern) -> transaction abort | RecordList
该方法可以使用任何索引
2,分布和容错
Mnesia是分布式、容错的DBMS,可以以多种方式在Erlang节点上备份表
Mnesia程序员不需要了解不同的表位于哪里,只用在程序里指定表的名字
这就是“位置透明”:
1)数据位于本地节点还是远程节点对程序员没有影响,只不过远程节点会慢些
2)数据库可以重新配置,表可以在节点之间移动,这些操作不影响用户程序
每张表有许多系统属性,如index和type
在表创建之时表属性就指定了,例如创建拥有两个RAM备份的新表:
1. mnesia:create_table(foo,
2. [{ramp_copies, [N1, N2]},
3. {attribtues, record_info(fields, foo)}]).
表可以有如下属性,每个属性使用一个Erlang节点list
1)ram_copies
表的RAM备份会存在于Erlang节点list中的每个节点上
对于RAM备份,写操作不会写到硬盘里
但是如果RAM备份需要持久化时可以这样做:
i)mnesia:dump_tables/1方法用来将RAM表备份导入到硬盘
ii)表副本可以备份
2)disc_copies
表会位于RAM中,而且表的副本会存在于Erlang节点list中的每个节点的硬盘上
对该表的写操作会同时写入到RAM和硬盘备份里
3)disc_only_copies
表的副本只会位于Erlang节点list中每个节点的硬盘上
这种类型的表副本的缺点是访问速度,优点主要是不占内存
简单的说,ram_copies表示本地节点和list中节点都会存RAM表
disc_copies则本地存RAM表,list中存硬盘表
disc_only_copies则只会list中存硬盘表
使用表副本有两个原因:容错和速度
值得注意的是,表备份对这两个系统需求都提供了解决方案
如果有两个表副本,则一旦一个表崩溃了,还有另一个可以工作
如果有两个节点上的表副本,则两个节点上的应用可以直接从本地读数据而不用访问网络
对于读频繁而写很少的分布式应用,表副本就会大大加速读的速度,因为直接在本地节点读取数据
而这样做的缺点写速度减慢了,因为执行一个写操作时要花更多代价来更新表副本
3,表分片
为了处理超大型的表,表分片的概念引入,基本原理是将表分成多个可以管理的片断
每个片断都实现为一等Mnesia表,它们可以像其他表一样备份,可以拥有索引等等,但是不能有local_content和snmp连接
为了从片断表里访问数据,Mnesia必须决定该record属于哪张表,这通过mnesia_frag模块来实现mnesia_access callback行为
(略)
4,本地内容表
所有节点上的表副本的内容一样,但是有时候不同节点的内容不同有优点
如果我们创建表时指定{local_content, true}属性,则写操作只在本地副本上执行
而且,当在启动时初始化表,则表只会在本地初始化而表内容不会复制到其他节点
5,无盘节点
可以在无盘的节点上运行Mnesia,当然在这些节点上不可能拥有disc_copies或disc_only_copies类型的备份
最麻烦的是schema表,因为Mnesia需要schema来初始化自己
schema表可以位于一个或多个节点上
schema表的存储类型可以为disc_copies或ram_copies(不能是disc_only_copies)
Mnesia启动时使用schema表来决定应该和哪些节点建立联系
如果其他节点已经启动,则启动节点将其他节点的表定义和自己的表定义合并
参数extra_db_nodes包含一个节点list,Mnesia除了schema里的节点,还要和该参数的节点建立联系,默认值为[]
因此,当无盘节点需要从网络上的一个远程节点找到schema定义,则我们需要从-mnesia extra_db_nodes参数节点列表支持该信息
如果没有这个配置参数,Mnesia会以一个单节点系统启动
可以使用mnesia:change_config/2来给'extra_db_node'赋值并且强制建立一个连接,即mnesia:change_config(extra_db_nodes, NodeList)
应用参数schema_location控制Mnesia在哪里搜索schema:
1)disc
强制硬盘,schema假设位于Mnesia目录,如果找不到,则Mnesia拒绝启动
2)ram
强制ram,schema只位于ram中,启动时会生成一个很小的新schema
这个默认schema只包含schema表的定义并且只位于本地节点
3)opt_disc
可选的硬盘,schema可能只位于硬盘或ram
如果硬盘上找不到schema,Mnesia启动一个无盘节点(schema表的存储类型为ram_copies)
如果schema_location设置为opt_disc,则方法mnesia:change_table_copy_type/3可以用来改变schema的存储类型:
1. 1> mneisa:start().
2. ok
3. 2> mnesia:change_table_copy_type(schema, node(), disc_copies).
4. {atomic, ok}
6,更多的Schema管理
可以从Mnesia添加和删除节点,这可以通过添加schema副本到这些节点来完成
mnesia:add_table_copy/3和mnesia:del_table_copy/2可以用来添加和删除schema表副本
添加一个节点会影响两点:1,允许其他表备份到该节点;2,它会在启动时连接硬盘节点
mnesia:del_table_copy(schema, mynode@host)从Mnesia系统删除'mynode@host'节点
mnesia:system_info(schema_location)和mnesia:system_info(extra_db_notes)用来决定schema_location和extra_db_nodes的值
mnesia:info/0用来打印出系统信息,可以在Mnesia启动之前就运行此方法
7,Mnesia事件处理
Mnesia可能生成系统事件和表事件这两种事件
用户进程可以订阅这些事件:
mneisa:subscribe(Event-Category)保证符合Event-Category类型的事件副本会发送给调用进程
mnesia:unsubscribe(Event-Category)对符合Event-Category类型的事件删除订阅
Event-Category可以为system或{table, Tab, simple}/{table, Tab, detailed}
系统事件语法为{mnesia_system_event, Event},表事件语法为{mnesia_table_event, Event}
所有的系统事件有Mnesia的gen_event handler来订阅,默认为mnesia_event
mnesia:system_info(subscribers)和mnesia:table_info(Tab, subscribers)用来决定哪个进程订阅了事件
系统事件
{mnesia_up, Node}
{mnesia_down, Node}
{mnesia_checkpoint_activated, Checkpoint}
{mnesia_checkpoint_deactivated, Checkpoint}
{mnesia_overload, Details}
{inconsistent_database, Context, Node}
{mnesia_fatal, Format, Args, BinaryCode}
{mnesia_info, Format, Args}
{mnesia_error, Format, Args}
{mnesia_user, Event}
表事件
{write, NewRecord, ActivityId}
{delete_object, OldRecord, ActivityId}
{delete, {Tab, Key}, ActivityId}
{write, Table, NewRecord, [OldRecords], ActivityId}
{delete, Table, What, [OldRecords], ActivityId}
9,Mnesia应用调试
Mnesia应用调试比较麻烦,因为理解事务和表加载工作机制很难,而且嵌套事务的语义也比较令人混淆
我们可以设置Mnesia的debug level:
1. mnesia:set_debug_level(Level)
参数Level为none、verbose、debug、trace、false、true
也可以作为应用参数,在启动Erlang系统时指定:
1. % erl -mnesia debug verbose
9,Mnesia里的并发进程
Mnesia里允许并发的事务提交,程序里不用显式的控制同步的进程
而且可以在用户继续使用表时移动、删除或重新配置表
详细参考四,事务和其他访问上下文
10,原型
如果我们决定使用Mnesia,通常会先将定义和数据写在纯文本里,这样比较简单
这样在构建原型时我们把定义和数据写在纯文本文件里,然后使用下面方法来处理:
mnesia:load_textfile(Filename)
mnesia:dump_to_textfile(Filename)
文本文件的格式为:
1. {tables, [{Typename, [Options]}, {Typename2 ...}]}.
2. {Typename, Attribute1, Attribute2 ...}.
3. {Typename, Attribute1, Attribute2 ...}.
Options是{Key,Value}list,和mnesia:create_table/2的options一致
例如我们有一个healthy foods的数据库,有下面的文件FRUITS:
1. {tables,
2. [{fruit, [{attributes, [name, color, taste]}]},
3. {vegetable, [{attributes, [name, color, taste, price]}]}
4. ]}.
5. {fruit, orange, orange, sweet}.
6. {fruit, apple, green, sweet}.
7. {vegetable, carrot, orange, carrotish, 2.55}.
8. {vegetable, potato, yellow, none, 0.45}.
我们可以这样来加载fruits数据库:
1. 1> mnesia:load_textfile("FRUITS").
2. New table fruit
3. New table vegetable
4. {atomic, ok}
5. 2> mnesia:info().
6. ---> Processes holding locks <---
7. ---> Processes waiting for locks <---
8. ---> Pending (remote) transactions <---
9. ---> Active (local) transactions <---
10. ---> Uncertain transactions <---
11. ---> Active tables <---
12. vegetable : with 2 records occuping 299 words of mem
13. fruit : with 2 records occuping 291 words of mem
14. schema : with 3 records occuping 401 words of mem
15. ===> System info in version "1.1", debug level = none <===
16. opt disc. Directory "/var/tmp/Mnesia.nonode@nohost" is used.
17. use fallback at restart = false
18. running db nodes = [nonode@nohost]
19. stopped db nodes = []
20. remote = []
21. ram copies = [fruit,vegetable]
22. disc copies = [schema]
23. disc only copies = []
24. [fnonode@nohost,disc copiesg] = [schema]
25. [fnonode@nohost,ram copiesg] = [fruit,vegetable]
26. 3 transactions committed, 0 aborted, 0 restarted, 2 logged to disc
27. 0 held locks, 0 in queue; 0 local transactions, 0 remote
28. 0 transactions waits for other nodes: []
29. ok
-mnesia dc_dump_limit 400
-mnesia dump_log_time_threshold 90000
-mnesia dump_log_write_threshold 150000
http://www.tuicool.com/articles/rIBbqa
当我们启动Mnesia的时候,一个名为LATEST.LOG的文件被创建并且放在数据库目录内。
这个文件被Mnesia用来对基于磁盘的事务做日志。这包括所有在存储类型为disc_copies或disc_only_copies的表中至少写入一条记录的事务。还包括对模式本身所作的全部操作,如创建新表等。
Mnesia的不同实现的日志格式可能有变化。当前实现的Mnesia是标准库模块disc_log。
日志文件会持续增长,因此需要定期转储。对于Mnesia“ ” 转储日志文件 意味着执行在日志中列出的所有操作并且将记录存放到对应的.DAT、.DCD和.DCL “ 文件中。例如,如果 写记录{foo, 4,elvis, 6}” 操作被列入日志,Mnesia插入此操作到foo.DCL中,随后在Mnesia认为.DCL文件已经变得太大时,再将数据移入.DCD文件。如果日志很大,转储操作可能非常耗时。因此,理解Mesia系统在日志转储期间要持续运转是很重要的。
在默认状态下,只要日志中写入了100条记录或者过去了3分钟这两种情况之一出现,Mnesia即转储日志。
可用两个应用程序参数-mnesia dump_log_write_threshold WriteOperations和-mnesiadump_log_time_threshold MilliSecs来对此进行控制。
在日志被转储之前,文件LATEST.LOG改名为PREVIOUS.LOG,并且创建一个新的LATEST.LOG文件。日志转储成功后,文件PREVIOUS.LOG被删除。在启动时以及每当一个模式操作被执行时,也要转储日志。
- 1.学习
- 1.1安装与运行环境
- 1.2编辑器、集成开发环境与其它工具
- 1.3代码编译运行
- 2.基础
- 2.1 Erlang终端
- 2.2 基础语法
- 2.2.1 异常处理
- 2.3 数据类型
- 2.4 操作符
- 2.5 模块属性
- 3.库函数
- 3.1 常用模块
- 3.2 OTP模块
- 3.2.1 函数
- 3.2.2 receive
- 3.2.3 .app.src文件
- 3.2.4 _app.erl
- 3.2.5 _sup.erl
- 3.2.6 gen_server
- 3.2.7 gen_fsm
- 3.3 erts
- 3.3.1 init
- 3.3.2 BIF
- 3.3.3 NIF
- 3.4 kernel
- 3.4.1 code_server
- 3.4.2 inet
- 3.4.3 net_kernel
- 3.4.4 net_adm
- 3.4.5 error_logger
- 3.4.6 global
- 3.4.7 application
- 3.5 stdlib
- 3.5.2 array
- 3.5.4 base64
- 3.5.5 binary
- 3.5.6 c
- 3.5.8 calendar
- 3.5.9 code
- 3.5.11 dict
- 3.5.12 erl_
- 3.5.13 file
- 3.5.14 filelib
- 3.5.15 gb_trees
- 3.5.16 gb_sets
- 3.5.17 gen_tcp
- 3.5.18 gen_server
- 3.5.19 httpc
- 3.5.20 init_parse
- 3.5.21 init
- 3.5.22 inet
- 3.5.23 io
- 3.5.24 lists
- 3.5.25 maps
- 3.5.26 os
- 3.5.27 ordsets
- 3.5.28 proplists
- 3.5.29 queue
- 3.5.30 qlc
- 3.5.31 re
- 3.5.32 random
- 3.5.33 rfc4627-json
- 3.5.34 string
- 3.5.35 ssh
- 3.5.36 soft
- 3.5.37 sets
- 3.5.38 supervisor
- 3.5.39 tuple
- 3.5.40 timer
- 3.5.41 unicode
- 3.5.42 cpu
- 3.5.43 math
- 3.5.44 zip
- 3.5.45 shell
- 3.6 SASL
- 3.7 asn1
- 3.8 compiler
- 3.9 tools
- 3.10 OS_Mon
- 3.11 crypto
- 3.12 Port
- 4.工具
- 4.1 Erlang预处理器
- 4.2 Erlang节点
- 4.3 Erlang多节点
- 4.3.1主从节点
- 4.4 Epmd
- 4.5 断点工具
- 4.6 dialyzer
- 4.7 dbg-debug 模块
- 4.7.1 dbg
- 4.8 Erlang跟踪工具
- 4.9 etop
- 4.10 profiling
- 4.10.1 fprof
- 4.10.2 eprof
- 4.10.3 cprof
- 5.进阶
- 5.1 TCP粘包、大小端
- 5.2 rebar发布系统
- 5.3 ErlangVM 心跳
- 5.4 Erlang GC
- 5.5 Erlang Time
- 5.6 Erlang 启动
- 5.6.1 SASL配置
- 5.7 Erlang系统限制
- 6.项目
- 6.1 the_seed
- 6.2 network
- 6.3 parse_tool
- 6.4 cache
- 7.项目研究
- 7.1 Mnesia
- 7.1.1 Mnesia模式
- 7.1.2 Mnesia操作
- 7.1.3 Mnesia增删改查
- 7.1.4 Mnesia过载分析
- 7.1.5 Mnesia高级特性
- 7.1.6 分布式
- 7.1.7 Mnesia表分片
- 7.1.8 Mnesia锁
- 7.1.9 dets
- 7.1.10 ets
- 7.2 Ejabberd
- 7.2.1 mod_echo.erl
- 7.2.2 hooks for module developers
- 7.2.3 Events list
- 7.3 cowboy
- 7.4 rebar
- 7.4.1 rebar Wiki
- 7.4.2 rebar.config.script
- 7.5 RIAK CS
- 7.6 Leofs
- 7.6.1 简介
- 8.资料整理
- 8.1 资料
- 8.1.1 Erlang的调度原理
- 8.1.2 虚拟机代码执行原理
- 8.1.3 SMP
- 8.2 杂记
- 8.2.1 设计