發佈一個TokyoTyrant的C#開源項目

       目前在網上關於TokyoCabinet(如下簡稱TC)和TokyoTyrant(如下簡稱TT)的資料已相對豐富了,但在.NET平臺上的客戶端軟件卻相對匱乏,由於作Discuz!NT企業版的關係,兩個月前開始接觸TC和TT,開始寫相關的客戶端代碼。
      這裏開放的是客戶端主要功能代碼,開源的目的一方面是但願更多的人來學習研究TC和TT,同時你們能夠下載本C#源碼繼續優化提高性能,同時查找BUG,一定本人精力能力有限,而Discuz!NT企業版的功能點又太多(抽空會多寫文章進行介紹)實在有些力不從心了,呵呵:)html

       好了,爲了便於使用,下面先對源碼中的項目文件進行說明:
 
      源碼包中包括三個項目:
      1.Discuz.EntLib.TokyoTyrant 核心功能代碼(目前名空間暫以產品命名)
      2.TTSample 主要用於加載測試數據,並對比SQLSERVER數據庫的建立查詢功能的速度。
      3.TTSampleConsole 使用核心功能代碼的例子(本文中會介紹其中主要功能)前端

 

       其中Discuz.EntLib.TokyoTyrant中類圖以下:sql

    
  
該客戶端有以下特色:     mongodb

  • 支持TcpClient鏈接池
  • 支持UTF-8編碼
  • 支持初始化連接數,連接過時時間,最大空閒時間,最長工做時間等設置

     

下面介紹一下如何使用數據庫

  1.初始化連接池:centos

            pool  =  TcpClientIOPool.GetInstance( " dnt_online " ); // 連接池名稱(即DNT在線表)
            pool.SetServers( new   string [] {  " 10.0.4.66:11211 " });
            pool.InitConnections 
=   8 ;
            pool.MinConnections 
=   8 ;
            pool.MaxConnections 
=   8 ;
            pool.MaxIdle 
=   30000 ;
            pool.MaxBusy 
=   50000 ;
            pool.MaintenanceSleep 
=   300000 ;
            pool.TcpClientTimeout 
=   3000 ;
            pool.TcpClientConnectTimeout 
=   30000 ;
            pool.Initialize();

 

 

    2.CRUD操做服務器

建立一條記錄(以DISCUZ!NT在線表字段爲例):  
 架構

IDictionary < string string >  columns  =   new  System.Collections.Generic.Dictionary < string string > ();
                columns.Add(
" olid " , i.ToString());
                columns.Add(
" userid " , i.ToString());
                columns.Add(
" ip " " 10.0.7. "   +  i);
                columns.Add(
" username " " 用戶 "   +  i);
                columns.Add(
" nickname " " 用戶 "   +  i);
                columns.Add(
" password " "" );
                columns.Add(
" groupid " " 5 " );
                columns.Add(
" olimg " "" );
                columns.Add(
" adminid " " 0 " );
                columns.Add(
" invisible " " 0 " );
                columns.Add(
" action " " 0 " );
                columns.Add(
" lastactivity " " 1 " );
                columns.Add(
" lastposttime " , DateTime.Now.ToString());
                columns.Add(
" lastpostpmtime " , DateTime.Now.ToString());
                columns.Add(
" lastsearchtime " , DateTime.Now.ToString());
                columns.Add(
" lastupdatetime " , DateTime.Now.ToString());
                columns.Add(
" forumid " " 0 " );
                columns.Add(
" forumname " "" );
                columns.Add(
" titleid " " 0 " );
                columns.Add(
" title " "" );
                columns.Add(
" verifycode " "" );
                columns.Add(
" newpms " " 0 " );
                columns.Add(
" newnotices " " 0 " );
    TokyoTyrantService.PutColumns(TTPool.GetInstance(), i.ToString(), columns, 
true ); // true表示如tc中有記錄則覆蓋,沒有則建立該記錄

 

 

  • 查詢操做:  

         首先構程過一個查詢(條件)對象,好比查詢字段olid = 1的在線用戶信息,則該對象定義以下:併發

new  Query().NumberEquals( " olid " 1 )

  

        而後將其放入TokyoTyrantService的QueryRecords方法中(注意綁定連接池),以下: 分佈式

var qrecords  =  TokyoTyrantService.QueryRecords(TTPool.GetInstance(),  new  Query().NumberEquals( " olid " 1 ));
// 遍歷當前結果集
foreach  (var k  in  qrecords.Keys)
{
    var column 
=  qrecords[k];
  ...數據綁定操做    
}

  

        更復雜的查詢,以下(查詢forumid = 16 and userid<1000 ,同時按userid字段倒序排列的前三條記錄):
 

 qrecords  =  TokyoTyrantService.QueryRecords(pool,  new  Query().NumberGreaterThanOrEqual( " forumid " 16 ).
                  NumberLessThan(
" userid " 1000 ).OrderBy( " userid " , QueryOrder.NUMDESC).LimitTo( 3 0 ));

       
        這裏的比較運行符能夠參見源碼中的枚舉類型,以下:
 

public   enum  QueryOperation
{
 STREQ 
=   0 //  # 查詢條件: 表示與操做對象的文字內容徹底相同(=)
 STRINC  =   1 //  # 查詢條件: 表示含有操做對象文字的內容(LIKE ‘%文字%’)
 STRBW  =   2 //  # 查詢條件: 表示以操做對象的文字行列開始(LIKE ‘文字%’)
 STREW  =   3 //  # 查詢條件: 表示到操做對象的文字行列結束(LIKE ‘%文字’)
 STRAND  =   4 //  # 查詢條件: 表示包含操做對象的文字行列中右逗號分開部分的字段的所有(name LIKE ‘%文字㈠%’ AND name LIKE ‘%文字㈡%’)
 STROR  =   5 //  # 查詢條件: 表示包含操做對象文字段中逗號分開部分的其中一部分(name LIKE ‘%文字㈠%’ OR name LIKE ‘%文字㈡%’)
 STROREQ  =   6 //  # 查詢條件: 表示與操做對象文字段中逗號分開部分的其中某部分徹底相同( name = ‘文字㈠’ OR name =‘文字㈡’)
 STRRX  =   7 //  # 查詢條件: 表與與常規表達式匹配
 NUMEQ  =   8 //  # 查詢條件: 表示等於操做對象的數值(=)
 NUMGT  =   9 //  # 查詢條件: 表示比操做對象的數值要大(>)
 NUMGE  =   10 //  # 查詢條件: 表大於或等於操做對象的數值(>=)
 NUMLT  =   11 //  # 查詢條件: 表示比操做對象的數值要小(<)
 NUMLE  =   12 //  # 查詢條件: 表示小於或等於操做對象的數值(<=)
 NUMBT  =   13 //  # 查詢條件: 表示其大小處於操做對象文字段中被逗號分開的兩個數值的中間(between 100 and 200)
 NUMOREQ  =   14 //  # 查詢條件: 表示其大小處於操做對象文字段中被逗號分開的兩個數值的中間(between 100 and 200)
 NEGATE  =   1   <<   24 //  # 查詢條件: 負標誌negation flag
 NOIDX  =   1   <<   25   //  # 查詢條件: 非索引標誌
}

    
       查詢指定主鍵(如本例中的olid,效率最高) 

var qrecords  =  TokyoTyrantService.GetColumns(pool,  new   string []{ " 1 " " 2 " " 3 " });
foreach  ( string  key  in  qrecords.Keys)
{
    var column 
=  qrecords[key];           
}

       

  • 更新操做:
          由於TC的TCT結構沒有提供直接更新記錄中某一字段的功能,因此只能所有取出相關記錄的全部字段,而後再更新所有字段(這種作法的效率不高,但在MONGODB中是能夠更新部分字段)。因此要組合使用查詢和建立操做中的語法,即選查出相應記錄,而後再使用PutColumns方法更新該記錄,形式以下:
     
    var qrecords  =  TokyoTyrantService.QueryRecords(TTPool.GetInstance(),  new  Query().NumberEquals( " olid " 1 ));
    foreach  (var k  in  qrecords.Keys)
    {
        var column 
    =  qrecords[k];
      ...數據綁定操做 
      TokyoTyrantService.PutColumns(TTPool.GetInstance(), column[
    " olid " ], columns,  true ); // column["olid"]爲主鍵,相似數據庫裏的主鍵,以其爲查詢條件,速度最快   
    }

      

  • 刪除操做

           該操做有兩種執行方法,一種是選查詢出符合條件的記錄,而後再刪除(依次刪除),一種是直接給定要刪除的主鍵直接刪除(效率比前者高)。第一種(能夠針對不用字段進行查詢,並將相應結果的主鍵作了刪除依據)
 

   var qrecords  =  TokyoTyrantService.QueryRecords(TTPool.GetInstance(),  new  Query().NumberEquals( " userid " 1 ));
         
foreach  (var k  in  qrecords.Keys)
         {
             var column 
=  qrecords[k];
              ...數據綁定操做 
             TokyoTyrantService.Delete(TTPool.GetInstance(), column[
" olid " ]); // column["olid"]爲主鍵,相似數據庫裏的主鍵   
         }

 

 
          第二種(刪除olid爲1或2或3或4的鍵值記錄,只能刪除以主鍵爲條件的記錄):   

TokyoTyrantService.DeleteMultiple(pool,  new   string [] {  " 1 " " 2 " " 3 " " 4 "  });

  

  • 建立索引

    TC中支持幾種類型的字段索引以下(常常用的是數值型和字符型):   

     ///   <summary>
    
///  索引類型
    
///   </summary>
     public   enum  IndexOption :  int
    {
        LEXICAL 
=   0 //  # 文本型索引
        DECIMAL  =   1 //  # 數值型索引
        TOKEN  =   2 //  # 標記倒排索引.   
        QGRAM  =   3 //  #QGram倒排索引.
        OPT  =   9998 //  # 9998, 對索引優化
        VOID  =   9999 //  # 9999, 移除索引.
        KEEP  =   1   <<   24   //  # 16777216, 保持已有索引.  
    }

 

 

        好比在線表中常常用的字段索引設置以下: 

    TokyoTyrantService.SetIndex(pool,  " olid " , IndexOption.DECIMAL);
    TokyoTyrantService.SetIndex(pool, 
" userid " , IndexOption.DECIMAL);
    TokyoTyrantService.SetIndex(pool, 
" password " , IndexOption.LEXICAL);
    TokyoTyrantService.SetIndex(pool, 
" ip " , IndexOption.LEXICAL);
    TokyoTyrantService.SetIndex(pool, 
" forumid " , IndexOption.DECIMAL);
    TokyoTyrantService.SetIndex(pool, 
" lastupdatetime " , IndexOption.DECIMAL); 

 

 
   3.其它經常使用操做 


 

 LimitTo(int max, int skip):相似於MYSQL中的LIMIT方法,其中max如同mssql中的TOP,而skip則表示跳過多少條記錄(相似LINQ中的那個Skip方法)
 Vanish(TcpClientIOPool pool);清空全部記錄
 QueryRecordsCount(TcpClientIOPool pool, Query query)//查詢指定條件的記錄數
 GetRecordCount(TcpClientIOPool pool)//返回當前表中的記錄總數
 GetDatabaseSize(TcpClientIOPool pool);//獲取數據庫(表)信息
 IteratorNext(TcpClientIOPool pool)//一個迭代器,用於遍歷全部記錄

 

  

    4.由於其兼容Memcached,因此提供方法支持(鍵/值對)
 

  Put(TcpClientIOPool pool, string key, string value, bool overwrite)//該操做方法將不像Put那樣獲取服務器端返回的信息
  PutFast(TcpClientIOPool pool, string key, string value)//快速存儲鍵值對(再也不獲取服務端返回信息). 如鍵值已存在則將被覆蓋
  PutMultiple(TcpClientIOPool pool, IDictionary
< string , string >  items) //一次添加多值

  Delete(TcpClientIOPool pool, string key)//刪除指定鍵的記錄
  DeleteMultiple(TcpClientIOPool pool, string[] keys)//刪除指定鍵組的記錄

  Get(TcpClientIOPool pool, string key)//獲取指定鍵的記錄(單條)
  GetSize(TcpClientIOPool pool, string key)//獲取指定鍵的大小
  GetColumns(TcpClientIOPool pool, string[] keys)//獲取指定鍵組的記錄(多條)

   

   5.排序
   

public   enum  QueryOrder
{
       STRASC 
=   0 //  # 排序類型: 表示按照文本型字段內的文本內容在字典中排列順序的升序
        STRDESC  =   1 //  # 排序類型: 表示按照文本型字段內的文本內容在字典中排列順序的降序
        NUMASC  =   2 //  # 排序類型: 表示按照數值大小的升序
        NUMDESC  =   3   //  # 排序類型: 表示按照數值大小的降序
}

  

       用法(如降序並取前16條記錄):
 

qrecords  =  TokyoTyrantService.QueryRecords(pool,  new  Query().OrderBy( " userid " , QueryOrder.NUMDESC).LimitTo( 16 0 ));

       注意儘可能避免對大數據集(如100w條記錄)進行排序,那樣耗時會很嚴重。因此儘可能在OrderBy以前指定查詢條件,從而縮減查詢結果集的尺寸

 

其它說明:

       TT的啓動參數(這裏以TCT類型爲例):

       注:網上有一些關於TC+TT與MONGODB,Redis的速度測試,因此這裏我想有必要對TT的啓動參數作一下介紹,由於這會關係到最終的測試結果。
 
        由於二者都使用了MMAP模式,而TC+TT要使用MMAP,就要使用下面參數:
 

    xmsiz:指定了TCHDB的擴展MMAP內存大小,默認值爲 67108864,也就是64M,若是數據庫文件超過64M,則只有前部分會映射在內存中,因此寫入性能會降低。
    bnum: 指定了bucket array的數量。推薦設置bnum爲預計存儲總記錄數的0.5~4倍,使key的哈希分佈更均勻,減小在 bucket內二分查找的時間複雜度。

        

       好比有100w條記錄,這裏可使用下面命令行啓動ttserver: 

 ttserver -host 10.0.4.66 -port 11211 -thnum 1024 -dmn -pid /ttserver/ttserver.pid -log /ttserver/ttserver.log -le -ulog /ttserver/ -ulim 256m -sid 1 -rts /ttserver/ttserver.rts /ttserver/database.tct#bnum=1000000#rcnum=1000000#xmsiz=1073741824   (注:1073741824=1G)

 

         固然TTServer中針對不一樣的數據庫(TC中支持6種),都有相應的參數進行啓動配置(有重複),這會致使的查詢和插入數據的結果上有很大的差別,更多的內容能夠參見這個連接
 

        下面我將本身對TC+TT(僅使用TCT文件類型,其它5種類型都比這個類型快許多)與MONGODB的測試結果作一下說明:
        機器是一個廣泛臺式機:1.5g內存+1.5gCPU,64位的centos機器,150g硬盤。

 

     mongodb (centos 64bit) :
    插入1000000 條記錄,耗時:250377毫秒 
    對1000000條記錄,查詢10000 次記錄,耗時:8100毫秒 (偶爾出現7500毫秒)  (查詢"_id"主鍵速度在6995毫秒上下)
    對1000000條記錄,查詢100000 次記錄,耗時:77101毫秒   

 

    ttcache(centos 64bit,使用上面的啓動參數):
    建立 1000000 條數據,耗時 589472毫秒
    對1000000條記錄,查詢 10000 次數據,耗時 4843毫秒
    對1000000條記錄,查詢 100000 次數據,耗時 47903毫秒

 

    注:查詢條件動態變化,以模擬實際生產環境。
    比較發現MONGODB插入速度要比TTCACHE快至少一倍(MONGODB在WINDOWS下也是如此),但10000次查詢速度會慢大約40%-50%。這裏的查詢和插入操做都是每作一次操做就Connect一次服務器,操做結束時則將當前連接放到連接池中,而不是開啓一個長連接來作批量操做。其中TTSERVER所使用的客戶端分別是本文的這個工具, MONGODB則使用的是MongoDB.Driver。
 
    下面是MSSQL數據庫操做結果:
    批量建立 1000000 條數據,耗時 9020196毫秒
    批量查詢 10000 條數據,耗時 106040毫秒
    批量查詢 100000 條數據,耗時 773867毫秒
 
 
      我想進行這類測試,仍是不要使用什麼WINDOWS(儘可能MONGODB在WINDOW下插入數據的速度已很快)或其它操做系統。而應該使用LINUX(儘可能是64位)。固然內存要儘可能的大,由於儘管TC+TT已很省內存(一定符合日本的國情,資源少還要多辦事),但若是要提高查詢和插入速度,仍是建議4g以上的內存作測
試。而MONGODB原本對內存要求很高(包括CPU)。
      由於mongodb的插入速度很是快,且在數據庫大量能夠新建文件來存儲新的數據(不像TCT使用一個數據文件),因此在更大級別的數據量插入上依然性能穩定。看來將它視爲海量數據存儲的分佈解決方案仍是頗有可行性的。固然我目前正在考慮一個架構,就是將MongoDb和TC/TT組合起來,實現讀寫分離(即將TC做爲讀數據庫slavedb,併發性和查詢速度快),而將MongoDb做爲寫數據庫masterdb(更新和插入速度快)。將分佈式的MongoDb數據文件與前端TC中的文件依次對應(使用C#代碼實現二者之間的數據同步和邏輯調用),這樣融合二者各自的優點的結果。固然目前這只是想法,且離文本的內容愈來愈遠了,呵呵。

        好了,今天的內容就選到這裏了。

 

       下載連接:http://tokyotyrantclient.codeplex.com/

相關文章
相關標籤/搜索