文章目录

前言

基于EMQ2.3.11。Mnesia是分布式电信数据库管理系统,有以下特性:

  • DBMS查询语言
  • 数据持久性:表是持久化到存储的
  • 集群复制:表可以在多个节点上复制
  • 原子事务:支持事务
  • 透明:对编程来说是透明的
  • 实时数据搜索:查询速度很快

一、Mnesia表的创建参数

1、type 表类型

【取值】

  • set:唯一键值,1:1,一个键一条记录
  • ordered_set:唯一键值,1:1,带排序
  • bag:1:n,一个键可以映射很多条记录

2、record_name 记录名称

【取值】记录名

所有表中的记录都必须是同一个记录的实例

3、ram_copies 内存复制

【取值】节点列表

可以指定要备份到哪些节点的内存中去,不保证事务更新。可以定时刷盘。

4、disc_copies 磁盘复制

【取值】节点列表  

指定要备份到哪些节点的内存和磁盘中去,内存操作表格,磁盘追加操作日志。

5、disc_only_copies 强制磁盘复制

【取值】节点列表  

指定备份到哪些节点的磁盘中去,操作都是基于磁盘操作,速度慢。

6、index 索引

【取值】属性名或整数列表

指定额外维护的索引表的元组位置

7、local_content 本地内容

【取值】true/false,默认false

表名对其他节点是已知的,但数据只在自己节点上。

8、majority 

【取值】true/false,默认false

  • true:大多数表的副本都必须同步成功更新(保证数据一致性)
  • false:不需要立刻同步,副本数据可能某段时间不一致

9、snmp

【取值】SNMP键类型

将基于集合的表自动变为简单网络管理协议(SNMP)有序表。

10、attributes

【取值】Record的属性列表

指定要插入哪些属性到表中。

EMQ的表都是内存复制,而且是用的自行开发的ekka_mnesia库。

EMQ路由表是bag类型,因为一个Topic可能有多个Node都会订阅,是一个类似Map的表:

-record(mqtt_route,
        { topic :: binary(),
          node  :: node()
        }).

    ok = ekka_mnesia:create_table(mqtt_route, [
                {type, bag},
                {ram_copies, [node()]},
                {record_name, mqtt_route},
                {attributes, record_info(fields, mqtt_route)}]);

EMQ的session是set类型,因为必须要保证clientId唯一性:

-record(mqtt_session,
        { client_id  :: binary(),
          sess_pid   :: pid(),
          clean_sess :: boolean()
        }).

    %% Global Session Table
    ok = ekka_mnesia:create_table(mqtt_session, [
                {type, set},
                {ram_copies, [node()]},
                {record_name, mqtt_session},
                {attributes, record_info(fields, mqtt_session)}]);

二、Mnesia表操作

这里只简单的记录EMQ用到过的表格操作是什么含义。

1、select 按条件读取表格

【原型】

select(Tab, MatchSpec [, Lock]) -> transaction abort | [Object]

  • Tab:表名
  • MatchSpec:匹配参数
  • Lock:是否加锁读取

【示例】

mnesia:select(mqtt_session, [{#mqtt_session{client_id = '$1', sess_pid = '$2', _ = '_'},[{'==', {node, '$2'}, Node}], ['$1']}])

2、write 写记录

【原型】

write(Record) -> transaction abort | ok
write(Tab, Record, LockKind) -> transaction abort | ok
  • Tab:表名
  • Record:记录值
  • LockKind:write写入锁,sticky_write粘滞写入锁

默认的write/1是本表+记录值+写入锁。

sticky_write粘滞写入锁是一种用于优化锁获取的机制。如果您使用复制表的目的主要是为了容错(而不是为了快速读取访问),则粘滞锁可能是最佳选择。当获取粘滞写入锁时,将通知所有节点哪个节点被锁定。然后,来自同一节点的粘滞锁定请求将作为本地操作执行,而不与其他节点进行任何通信。即使在事务结束后,粘滞锁仍会留在节点上。  

【示例】

mnesia:write(Route).

3、delete 删除记录

【原型】

delete({Tab, Key}) -> transaction abort | ok
delete(Tab, Key, LockKind) -> transaction abort | ok

和前面一致,默认用write写入锁,删除一行数据。

【示例】

mnesia:delete({mqtt_trie_node, Topic})

4、delete_object 删除对象

【原型】

delete_object(Record) -> transaction abort | ok
delete_object(Tab, Record, LockKind) -> transaction abort | ok

如果表是bag类型,一对多,可以用这个来删除所有数据,默认写入锁。

【示例】

mnesia:delete_object(mqtt_route, R, write)

5、wread

【原型】

wread({Tab, Key}) -> transaction abort | RecordList
read(Tab, Key, LockKind) -> transaction abort | RecordList

wread调用的read,使用写入锁(不可写)。

read的锁可以是:read、write、sticky_write。

【示例】

mnesia:wread({mqtt_route, Topic})

6、ets

【原型】

ets(Fun, [, Args]) -> ResultOfFun | exit(Reason)

调用一个非事务的函数,会去内存取本地ets表,速度很快,相当于取缓存。

【示例】EMQ只有一处用到了,在订阅树匹配的时候,因为只是读操作,但是操作次数太多,可以容忍脏读,所以用这个直接读内存:

Matched = mnesia:ets(fun emqttd_trie:match/1, [Topic])

7、transaction 事务

【原型】

transaction(Fun [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}

以事务的方式执行Fun函数。

【示例】

mnesia:transaction(Clean)

8、abort 中止事务

【原型】

abort(Reason) -> transaction abort

主动表明事务执行失败,数据库恢复到事务之前的状态。

【示例】在进行delete_path的时候,递归到最后发现找不到指定节点,需要主动表明自己执行失败(因为是业务逻辑错误),所以使用了abort:

mnesia:abort({node_not_found, NodeId})

9、dirty_read 脏读

【原型】

dirty_read(Tab,Key)->ValueList | exit({aborted,Reason})

读取当前的数据,不需要事务。

【示例】

mnesia:dirty_read(mqtt_session, ClientId)

10、dirty_delete_object 脏删

【原型】

dirty_delete_object(Tab, Record)

删除当前数据,不需要事务。

【示例】

remove_session(Session) ->
    mnesia:dirty_delete_object(Session).

11、async_dirty 异步执行

【原型】

async_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)

不要事务地执行Fun函数。

【示例】只在路由表相关操作中使用到,因为增加路由表无需加锁:

add_direct_route(Route) ->
    mnesia:async_dirty(fun mnesia:write/1, [Route]).

12、dirty_all_keys 

【原型】

dirty_all_keys(Tab) -> KeyList | exit({aborted, Reason})

注意这里的dirty不是动词,而是只all_keys的脏操作版本,获取表的所有键值。

【示例】只在一处用到,路由表:

topics() ->
    mnesia:dirty_all_keys(mqtt_route).


参考资料

1、《Erlang官方英文Mnesia手册》

2、《腾讯云开发者文档:mnesia》(相当于手册的中文翻译,基本是机器翻译,很多错误)


转载请注明出处http://www.bewindoweb.com/260.html | 三颗豆子
分享许可方式知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
重大发现:转载注明原文网址的同学刚买了彩票就中奖,刚写完代码就跑通,刚转身就遇到了真爱。
你可能还会喜欢
具体问题具体杠