现在的位置: 首页 > 综合 > 正文

erlang ets tables

2013年03月09日 ⁄ 综合 ⁄ 共 7829字 ⁄ 字号 评论关闭

转自:http://www.cnblogs.com/me-sa/archive/2011/08/11/erlang0007.html 


不需要显示用锁,插入和查询时间不仅快而且控制为常量,这就是Erlang的ETS Table.

   为什么而设计?

   Erlang中可以用List表达集合数据,但是如果数据量特别大的话在List中访问元素就会变慢了;这种主要是由于List的绝大部分操作都是基于遍历完成的.

Erlang的设计目标是软实时(参考:http://en.wikipedia.org/wiki/Real-time_computing),在大量数据中检索的时间不仅要快而且要求是常量.为了解决快速查

询的问题,Erlang提供的机制就是ETS(Erlang Term Storage)和DETS(Disk Erlang Term Storage).本文只关注ETS.

 ETS基础     

  1. ETS查询时间是常量,例外是如果使用ordered_set查询时间与logN成正比(N为存储的数据量)
  2. ETS 存储数据的格式是Tuple,下面的测试代码中我们可以看到细节
  3. ETS Table由进程创建,进程销毁ETS Table也随着销毁,在使用Shell做ETS实验的时候要注意一下,Table的拥有关系可以give_away 转交给其它进程
  4. 一个Erlang节点的ETS表的数量是有限制的,默认是1400个表,在启动erlang节点之前修改 ERL_MAX_ETS_TABLES参数可以修改这个限制ejabberd社区站点上总结的性能调优中提到了这一点,点击这里查看: 

    http://www.ejabberd.im/tuning

  5. ETS表不在GC的管理范围内,除非拥有它的进程死掉它才会终止;可以通过delete删除数据 
  6. 目前版本,insert和lookup操作都会导致对象副本的创建,insert和lookup时间对于set bag duplicate_bag都是常量值与表大小无关.
  7. 并发控制:所有针对一个对象的更新都被保证是原子的、隔离的:修改要么全部成功要么失败。也没有其它的中间结果被其它的进程使用。有些方法可以在处理多个对象的时候保证这种原子性和隔离性。
    在数据库术语中隔离级别被称作序列化,就好像所有隔离的操作一个接一个严格按照顺序执行。
  8. 在遍历过程中,可以使用safe_fixtable来保证遍历过程中不出现错误,所有数据项只被访问一遍.用到逐一遍历的场景就很少,使用safe_fixtable的情景就更少。不过这个机制是非常有用的,
    还记得在.net中版本中很麻烦的一件事情就是遍历在线玩家用户列表.由于玩家登录退出的变化,这里的异常几乎是不可避免的.select match内部实现的时候都会使用safe_fixtable

查看ETS Table

     Erlang提供了一个可视化的ETS查看工具 The Table Visualizer,启动tv:start(),界面比较简单.值得一提的是,这个工具可以跨节点查看ETS信息,在File菜单里面有一个nodes选项,

打开会给出和当前节点互相连通的节点列表,点击节点会显示这个节点上的ETS Table信息.

 

在没有可视化工具的时候我们如何查看ETS的信息?而且这还是比较常见的情况,在文本模式操作服务器的情况下,Table Visualizer根本没法使用.下面的命令可以达到同样的效果:

    ets:all() %列出所有的ETS Table 

    ets:i() %给出一个ETS Table的清单 包含表的类型,数据量,使用内存,所有者信息

    ets:i(zen_ets) % 输出zen_ets表的数据,个人感觉这个非常方便比tv还要简单快捷,如果表数据量很大,它还提供了一个分页显示的功能

    ets:info(zen_ets) %单独查看一个ETS Table的详细信息也可以使用这个方法,如果怀疑这个表被锁了可以使用ets:info(zen_ets,fixed)查看,ets:info(zen_ets,safe_fixed) 可以 

    获得更多的信息,这样比较容易定位是哪个模块出了问题.

    ets:member(Tab, Key) -> true | false %看表里面是否存在键值为Key的数据项.

  

创建 删除ETS Table插入数据

  上面已经提到了ETS存储数据的格式是Tuples,我们动手写一些测试代码看一下ETS的常规操作:  

      %快速创建一个ETS Table 并填充数据

       T = ets:new(x,[ordered_set]).

       [ ets:insert(T,{N}) || N <- lists:seq(1,10) ].   

       TableID = ets:new(temp_table , []), %Create New ETS Table

        ets:insert(TableID,{1,2} ),                  % insert one Item to Table

        Result= ets:lookup(TableID ,1),

        io:format("ets:lookup(TableID ,1) Result: ~p ~n  " ,[ Result  ]),  

         ets:insert(TableID,{1,3} ),

         Result2 = ets:lookup(TableID, 1 ),

         io:format("ets:lookup(TableID ,1) Result2: ~p ~n  ", [ Result2 ]),

         ets:delete(TableID),

         BagTableID =  ets:new(temp_table, [bag]),

         ets:insert(BagTableID,{1,2} ),

         ets:insert(BagTableID,{1,3} ),

         ets:insert(BagTableID,{1,4} ),

       %Note that the time order of object insertions is preserved; 

       %The first object inserted with the given key will be first in the resulting list, and so on.

         Result3 = ets:lookup(BagTableID, 1 ),

         io:format("ets:lookup(BagTableID ,1) Result3: ~p ~n  ", [ Result3 ])

         %创建ETS表 注意参数named_table,我们可以通过countries原子来标识这个ETS Table

         ets:new(countries, [bag,named_table]), 

        %插入几条数据

         ets:insert(countries,{yves,france,cook}),  

         ets:insert(countries,{sean,ireland,bartender}),

         ets:insert(countries,{marco,italy,cook}),

         ets:insert(countries,{chris,ireland,tester}).

我不明白为什么宁愿相信别人的话,也不愿意自己动手写一段测试代码看看;别人说对了还好,如果说错了呢?快速的构建一个测试模型,去验证自己的想法,这种能力会在一次次实验中不断强化;比如有人问到"ETS INSERT是每次都新加一个还是会更新一个",在Erlang Shell中就可以验证它(下面默认创建的是Set类型):
复制代码
Eshell V5.9  (abort with ^G)
1> ets:new(test,[named_table]).
test
2> [ets:insert(test,{Item}) || Item <-[1,2,3,4,5,6]].
[true,true,true,true,true,true]
3> [ets:insert(test,{Item}) || Item <-[1,2,3,4,5,6]].
[true,true,true,true,true,true]
4> ets:i(test).
<1   > {5}
<2   > {3}
<3   > {2}
<4   > {1}
<5   > {4}
<6   > {6}
EOT  (q)uit (p)Digits (k)ill /Regexp -->q

ok               
复制代码

 

 

分页从ETS中提取数据

      有时候匹配的数据量很大,如果一次性把所有的数据都取出来,处理会非常慢;一个处理方法就是分批次处理,这也就要求我们能够分多次

从ETS Table中取数据.这和做网页分页很像.ets类库中提供了一系列方法来实现这个功能这里我们以match为例:

match(Tab, Pattern, Limit) -> {[Match],Continuation} | '$end_of_table'

参数Limit就是每一次查询的数量限制,如果实际匹配的数据量超过了Limit就会返回{[Match],Continuation}的结果,Match代表查询的结果集,可以推测

Continuation包含分页的信息,如果继续取下一页的结果集使用下面的方法:

match(Continuation) -> {[Match],Continuation} | '$end_of_table'

我们通过demo看一下分页查询的结果,特别是Continuation的数据结构,首先我们先填充一些测试数据:

  

复制代码
ets:new(zen_ets, [{keypos, #t.id}, named_table, public, set]),
     ets:insert(zen_ets,#t{id=2,item=2011,name="hello",iabn=1,age=24}),
    ets:insert(zen_ets,#t{id=3,item=2011,name="hello",iabn=1,age=20}),
    ets:insert(zen_ets,#t{id=4,item=2011,name="hello",iabn=1,age=34}),   
    ets:insert(zen_ets,#t{id=5,item=2011,name="hello",iabn=1,age=356}),
    ets:insert(zen_ets,#t{id=6,item=2011,name="hello",iabn=1,age=278}),
    ets:insert(zen_ets,#t{id=7,item=2011,name="hello",iabn=1,age=299}),
      ets:insert(zen_ets,#t{id=8,item=2011,name="hello",iabn=1,age=356}),
    ets:insert(zen_ets,#t{id=9,item=2011,name="hello",iabn=1,age=278}),
    ets:insert(zen_ets,#t{id=10,item=2011,name="hello",iabn=1,age=299}),
      ets:insert(zen_ets,#t{id=11,item=2011,name="hello",iabn=1,age=356}),
    ets:insert(zen_ets,#t{id=12,item=2011,name="hello",iabn=1,age=278}),
    ets:insert(zen_ets,#t{id=13,item=2011,name="hello",iabn=1,age=299}),
      ets:insert(zen_ets,#t{id=14,item=2011,name="hello",iabn=1,age=356}),
    ets:insert(zen_ets,#t{id=15,item=2011,name="hello",iabn=1,age=278}),
    ets:insert(zen_ets,#t{id=16,item=2011,name="hello",iabn=1,age=299}),
      ets:insert(zen_ets,#t{id=17,item=2011,name="hello",iabn=1,age=356}),
    ets:insert(zen_ets,#t{id=18,item=2011,name="hello",iabn=1,age=278}),
    ets:insert(zen_ets,#t{id=19,item=2011,name="hello",iabn=1,age=299}),
      ets:insert(zen_ets,#t{id=20,item=2011,name="hello",iabn=1,age=356}),
    ets:insert(zen_ets,#t{id=21,item=2011,name="hello",iabn=1,age=278}),
    ets:insert(zen_ets,#t{id=22,item=2011,name="hello",iabn=1,age=299}),
      ets:insert(zen_ets,#t{id=23,item=2011,name="hello",iabn=1,age=356}),
    ets:insert(zen_ets,#t{id=24,item=2011,name="hello",iabn=1,age=278}),
    ets:insert(zen_ets,#t{id=25 ,item=2011,name="hello",iabn=1,age=299}),
      ets:insert(zen_ets,#t{id=26,item=2011,name="hello",iabn=1,age=356}),
    ets:insert(zen_ets,#t{id=27,item=2011,name="hello",iabn=1,age=278}),
    ets:insert(zen_ets,#t{id=28,item=2011,name="hello",iabn=1,age=299}),
      ets:insert(zen_ets,#t{id=29,item=2011,name="hello",iabn=1,age=356}),
    ets:insert(zen_ets,#t{id=30,item=2011,name="hello",iabn=1,age=278}),
    ets:insert(zen_ets,#t{id=31,item=2011,name="hello",iabn=1,age=299}),
    ets:foldl(fun(A,AC)-> io:format("Data:~p~n",[A]) end ,0,zen_ets).
复制代码

  

我们每页10条数据,执行4次,代码如下:

 {M,C}=ets:match(zen_ets,'$1',10). %第一页

 {M2,C2} = ets:match(C).               %第二页

 {M3,C3} = ets:match(C2).             %第三页

 {M4,C4} = ets:match(C3).            %没有数据了看异常是什么?

展开下面的代码查看调用结果:

复制代码
(zen_latest@192.168.1.188)5> {M,C}=ets:match(zen_ets,'$1',10).
{[[{t,2,2011,"hello",1,24}],
  [{t,3,2011,"hello",1,20}],
  [{t,26,2011,"hello",1,356}],
  [{t,30,2011,"hello",1,278}],
  [{t,14,2011,"hello",1,356}],
  [{t,5,2011,"hello",1,356}],
  [{t,29,2011,"hello",1,356}],
  [{t,22,2011,"hello",1,299}],
  [{t,12,2011,"hello",1,278}],
  [{t,23,2011,"hello",1,356}]],
 {zen_ets,196,10,<<>>,[],0}}
(zen_latest@192.168.1.188)6> {M2,C2} = ets:match(C).
{[[{t,11,2011,"hello",1,356}],
  [{t,31,2011,"hello",1,299}],
  [{t,19,2011,"hello",1,299}],
  [{t,9,2011,"hello",1,278}],
  [{t,10,2011,"hello",1,299}],
  [{t,8,2011,"hello",1,356}],
  [{t,28,2011,"hello",1,299}],
  [{t,13,2011,"hello",1,299}],
  [{t,17,2011,"hello",1,356}],
  [{t,24,2011,"hello",1,278}]],
 {zen_ets,107,10,<<>>,[],0}}
(zen_latest@192.168.1.188)7> {M3,C3} = ets:match(C2).
{[[{t,20,2011,"hello",1,356}],
  [{t,21,2011,"hello",1,278}],
  [{t,27,2011,"hello",1,278}],
  [{t,25,2011,"hello",1,299}],
  [{t,16,2011,"hello",1,299}],
  [{t,7,2011,"hello",1,299}],
  [{t,18,2011,"hello",1,278}],
  [{t,6,2011,"hello",1,278}],
  [{t,15,2011,"hello",1,278}],
  [{t,4,2011,"hello",1,34}]],
 '$end_of_table'}
(zen_latest@192.168.1.188)8> {M4,C4} = ets:match(C3).
** exception error: no match of right hand side value '$end_of_table'
(zen_latest@192.168.1.188)9>
复制代码

类似的还有:

match_object(Tab, Pattern, Limit) -> {[Match],Continuation} | '$end_of_table'

match_object(Continuation) -> {[Match],Continuation} | '$end_of_table'

select(Tab, MatchSpec, Limit) -> {[Match],Continuation} | '$end_of_table'

select(Continuation) -> {[Match],Continuation} | '$end_of_table'

只获取匹配数据的数量: select_count(Tab, MatchSpec) -> NumMatched

ETS 使用Match specifications 查询

    match方法进行匹配最简单, '$数字'代表占位符,'_'代表通配符;'$数字'这种表示方式,数字的大小代表什么?

从下面

【上篇】
【下篇】

抱歉!评论已关闭.