Erlang ETS Table

 不須要顯示用鎖,插入和查詢時間不只快並且控制爲常量,這就是Erlang的ETS Table.php

   爲何而設計?html

   Erlang中能夠用List表達集合數據,可是若是數據量特別大的話在List中訪問元素就會變慢了;這種主要是因爲List的絕大部分操做都是基於遍歷完成的.node

Erlang的設計目標是軟實時(參考:http://en.wikipedia.org/wiki/Real-time_computing),在大量數據中檢索的時間不只要快並且要求是常量.爲了解決快速查git

詢的問題,Erlang提供的機制就是ETS(Erlang Term Storage)和DETS(Disk Erlang Term Storage).本文只關注ETS.shell

 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 Tableexpress

     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的數據結構,首先咱們先填充一些測試數據:

  

View Code

  

咱們每頁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).            %沒有數據了看異常是什麼?

展開下面的代碼查看調用結果:

執行結果

相似的還有:

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方法進行匹配最簡單, '$數字'表明佔位符,'_'表明通配符;'$數字'這種表示方式,數字的大小表明什麼?

從下面的代碼示例中能夠看出數字控制的是輸出結果順序,數字相對大小表明相對位置順序;

       

%'_' 通配符
A= ets:match(countries, {'$1','_','_' } ) ,
io:format(" ets:match(countries, {'$1','_','_' } ) Result : ~p ~n " ,[ A ]), 
B= ets:match(countries , {'$1', '$0' ,'_' } ),
io:format(" ets:match(countries , {'$1', '$0' ,'_' } ), Result : ~p ~n " ,[ B ]),
C= ets:match(countries , {'$11', '$9' ,'_' } ),
io:format(" C= ets:match(countries , {'$11', '$9' ,'_' } ), Result : ~p ~n " ,[ C ]),
D= ets:match(countries , {'$11', '$99' ,'_' } ),
io:format(" ets:match(countries , {'$11', '$99' ,'_' } ), Result : ~p ~n " ,[ D ]),
E= ets:match(countries , {'$101', '$9' ,'_' } ),
io:format("ets:match(countries , {'$101', '$9' ,'_' } ), Result : ~p ~n " ,[ E ]),
F= ets:match(countries,{'$2',ireland,'_'}),
G= ets:match(countries,{'_',ireland,'_'}), % [[],[]] 若是沒有數字佔位符 是沒有結果輸出的 只是空列表
H= ets:match(countries,{'$2',cook,'_'}),
I= ets:match(countries,{'$0','$1',cook}),
J= ets:match(countries,{'$0','$0',cook}),

  

     若是是須要全部字段,提取整個數據項,那就直接使用match_object,

            K= ets:match_object(countries,{'_',ireland,'_'}),

            io:format(" ets:match_object(countries,{'_',ireland,'_'}),  Result : ~p ~n " ,[ K ]),

         L= ets:match(countries ,'$1' ),

         io:format(" ets:match(countries ,'$1' ),  Result: ~p ~n " ,[ L ]),

        Result=ets:match_delete(countries,{'_','_',cook}),

         io:format("ets:match_delete(countries,{'_','_',cook}),   Result : ~p ~n " ,[ Result ]),

        上面的例子countries這個結構很簡單,可是若是是一個字段稍多寫的結構呢?很容易出現相似ets:match(zen_ets, {'$1','_','_','_','_','_' } )  .這樣的代碼,不只可讀性差,並且一旦字段順序發生

變化,這裏就容易出錯.解決方法在[Erlang 0006] Erlang中的record與宏  一文中已經提到過,使用record能夠規避掉tuple字段增減,順序的問題.

例如:    ets:match_delete(zen_ets, #t{age=24,iabn=1,_='_'}), 

有時候咱們須要表達更爲複雜的匹配條件,這就須要使用Match specifications了,ms的解析依賴ms_transform模塊,因此首先咱們在模塊頭添加

include_lib("stdlib/include/ms_transform.hrl").增長對ms_transform.hrl頭文件的引用.Match specifications的詳細說明參見這裏: http://www.erlang.org/doc/apps/erts/match_spec.html     

        

MS = ets:fun2ms(fun({ Name,Country , Position }  ) when Position /=cook -> [Country,Name ] end   ),

        MSResult = ets:select(countries, MS ),

        io:format("ets:fun2ms(fun({ Name,Country , Position }  ) when Position /=cook -> [Country,Name ] end   ), MSResult:~p~n " , [MSResult ]),

        

        MS2 =ets:fun2ms(fun(Data ={Name, Country ,Position } )  when Position /=cook -> Data end   ),

        MSResult2 = ets:select(countries , MS2),

        io:format("ets:fun2ms(fun(Data ={Name, Country ,Position } )  when Position /=cook -> Data end   ),   Result : ~p ~n " ,[ MSResult2 ]),

        

        %當咱們使用的是Tuple的時候這裏必須使用徹底匹配 

       MS3  = ets:fun2ms(fun(Data ={Name, Country ,Position } )  when Position /=cook -> Data end   ), 

       MSResult2 = ets:select(countries , MS3),

       

在實戰操做中,咱們遇到這樣一個問題,下面的MS MS2是等效的麼?  ets:fun2ms(fun(#t{id =ID , name =Name, _='_'  } ) when ID >30 -> Name  end ),亮點是紅色標記的部分.能夠運行一下下面的

代碼看,二者是生成的ms是同樣的.

         

    MS = ets:fun2ms(fun(#t{id =ID , name =Name  } ) when ID >30 -> Name  end ),

    io:format(" ets:fun2ms(fun(#t{id =ID , name =Name  } ) when ID >30 -> Name  end ),  MS:  ~p ~n   " , [ MS ]),  

        

    MS2 = ets:fun2ms(fun(#t{id =ID , name =Name, _='_'  } ) when ID >30 -> Name  end ),

    io:format(" ets:fun2ms(fun(#t{id =ID , name =Name, _='_'  } ) when ID >30 -> Name  end ), MS2: ~p ~n    " ,[  MS2 ]),

    

     io:format("MS==MS2 ? Result : ~p ~n  " , [ MS==MS2 ]),

    

    MSResult = ets:select(zen_ets , MS ),

在使用MS的過程當中,還有一個特殊的狀況,若是要返回完整的record應該怎麼寫呢?仔細閱讀ETS文檔,能夠看到這麼一句:The return value is constructed using the "match variables" bound in

 the MatchHead or using the special match variables '$_' (the whole matching object) and '$$' (all match variables in a list), so that the following ets:match/2 expression:

再翻看http://www.erlang.org/doc/apps/erts/match_spec.html,能夠看到下面的說明:

ExprMatchVariable ::= MatchVariable (bound in the MatchHead) | '$_' | '$$'

也就是說只要這樣'$_'就能夠了,試驗了一下MS3 = ets:fun2ms(fun(T=#t{id =ID , name =Name, _='_'  } ) when ID >30 -> T  end )生成的ms是:

,MS3: [{{t, '$1',  '_','$2', '_',  '_'},      [{'>', '$1', 30}],['$_']}]

拓展閱讀:

2003年的論文 <<Erlang ETS Table的實現與性能研究>>

Study of Erlang ETS Table Implementation and Performance. [點此下載]

Scott Lystig Fritchie. 
Second ACM SIGPLAN Erlang Workshop. 
Uppsala, Sweden, August 29, 2003.

詳見:ligaoren博園

相關文章
相關標籤/搜索