Discuz!NT中集成Memcached分佈式緩存

        大約在兩年前我寫過一篇關於 Discuz!NT緩存架構的文章,在那篇文章的結尾介紹了在IIS中若是開啓多個應用程序池會形成多個緩存實例之間數據同步的問題。雖然給出了一個解決方案,但無形中卻把壓力轉移到了磁盤I/O上(多個進程併發訪問cache.config文件)。其實從那時起我就開始關注有什麼更好的方案,固然今天本文中所說的Memcached,以及Velocity等這類的分佈式緩存方案以前都考慮過,但一直未能決定該使用那個。起碼Velocity要在.net 4.0以後纔會提供,雖然是原生態,但有些遠水解不了近火。

       我想真正等到Velocity能堪當重任還要等上一段時間。因而我就開始將注意力轉移到了Memcached,一定有Facebook這隻「超級小白鼠」使用它而且反響還不錯。因此就開始嘗試動手在產品中集成Memcached。

        其實在以前的那篇關於Discuz!NT緩存架構的文章中已提到過,使用了設計模式中的「策略模式」來構造。因此爲了與以往使用緩存的代碼格式相兼容,因此這裏採用新添加MemCachedStrategy(MemCached策略)來構造一個緩存策略類以便於當管理後臺開啓「MemCached」時以「MemCached策略模式」來作爲當前系統默認
的策略模式。

       其代碼段以下( Discuz.Cache/MemCached.cs):
    
///   <summary>
///  MemCache緩存策略類
///   </summary>
public   class  MemCachedStrategy : Discuz.Cache.ICacheStrategy
{

    
///   <summary>
    
///  添加指定ID的對象
    
///   </summary>
    
///   <param name="objId"></param>
    
///   <param name="o"></param>
     public   void  AddObject( string  objId,  object  o)
    {
        RemoveObject(objId);
        
if  (TimeOut  >   0 )
        {
            MemCachedManager.CacheClient.Set(objId, o, System.DateTime.Now.AddMinutes(TimeOut));
        }
        
else
        {
            MemCachedManager.CacheClient.Set(objId, o);
        }
    }

    
///   <summary>
    
///  添加指定ID的對象(關聯指定文件組)
    
///   </summary>
    
///   <param name="objId"></param>
    
///   <param name="o"></param>
    
///   <param name="files"></param>
     public   void  AddObjectWithFileChange( string  objId,  object  o,  string [] files)
    {
        ;
    }

    
///   <summary>
    
///  添加指定ID的對象(關聯指定鍵值組)
    
///   </summary>
    
///   <param name="objId"></param>
    
///   <param name="o"></param>
    
///   <param name="dependKey"></param>
     public   void  AddObjectWithDepend( string  objId,  object  o,  string [] dependKey)
    {
        ;
    }

    
///   <summary>
    
///  移除指定ID的對象
    
///   </summary>
    
///   <param name="objId"></param>
     public   void  RemoveObject( string  objId)
    {
        
if  (MemCachedManager.CacheClient.KeyExists(objId))
            MemCachedManager.CacheClient.Delete(objId);
    }

    
///   <summary>
    
///  返回指定ID的對象
    
///   </summary>
    
///   <param name="objId"></param>
    
///   <returns></returns>
     public   object  RetrieveObject( string  objId)
    {
        
return  MemCachedManager.CacheClient.Get(objId);
    }

    
///   <summary>
    
///  到期時間
    
///   </summary>
     public   int  TimeOut {  set get ; }
}

    

    上面類實現的接口 Discuz.Cache.ICacheStrategy定義以下:
    
 
///   <summary>
 
///  公共緩存策略接口
 
///   </summary>
  public   interface  ICacheStrategy
 {
     
///   <summary>
     
///  添加指定ID的對象
     
///   </summary>
     
///   <param name="objId"></param>
     
///   <param name="o"></param>
      void  AddObject( string  objId,  object  o);

     
///   <summary>
     
///  添加指定ID的對象(關聯指定文件組)
     
///   </summary>
     
///   <param name="objId"></param>
     
///   <param name="o"></param>
     
///   <param name="files"></param>
      void  AddObjectWithFileChange( string  objId,  object  o,  string [] files);

     
///   <summary>
     
///  添加指定ID的對象(關聯指定鍵值組)
     
///   </summary>
     
///   <param name="objId"></param>
     
///   <param name="o"></param>
     
///   <param name="dependKey"></param>
      void  AddObjectWithDepend( string  objId,  object  o,  string [] dependKey);

     
///   <summary>
     
///  移除指定ID的對象
     
///   </summary>
     
///   <param name="objId"></param>
      void  RemoveObject( string  objId);

     
///   <summary>
     
///  返回指定ID的對象
     
///   </summary>
     
///   <param name="objId"></param>
     
///   <returns></returns>
      object  RetrieveObject( string  objId);

     
///   <summary>
     
///  到期時間
     
///   </summary>
      int  TimeOut {  set ; get ;}
}


        固然在MemCachedStrategy類中還有一個對象要加以說明,就是MemCachedManager,該類主要是對Memcached一些常操做和相關初始化實例調用的「封裝」,下面是是其變量定義和初始化構造方法的代碼:

///   <summary>
///  MemCache管理操做類
///   </summary>
public   sealed   class  MemCachedManager
{
    
#region  靜態方法和屬性
    
private   static  MemcachedClient mc  =   null ;

    
private   static  SockIOPool pool  =   null ;

    
private   static  MemCachedConfigInfo memCachedConfigInfo  =  MemCachedConfigs.GetConfig();

    
private   static   string  [] serverList  =   null ;

    
static  MemCachedManager()
    {
        CreateManager();
    }

    
private   static   void  CreateManager()
    {
        serverList 
=  Utils.SplitString(memCachedConfigInfo.ServerList,  "" r " n " );

        pool 
=  SockIOPool.GetInstance(memCachedConfigInfo.PoolName);
        pool.SetServers(serverList);
        pool.InitConnections 
=  memCachedConfigInfo.IntConnections; // 初始化連接數
        pool.MinConnections  =  memCachedConfigInfo.MinConnections; // 最少連接數
        pool.MaxConnections  =  memCachedConfigInfo.MaxConnections; // 最大鏈接數
        pool.SocketConnectTimeout  =  memCachedConfigInfo.SocketConnectTimeout; // Socket連接超時時間
        pool.SocketTimeout  =  memCachedConfigInfo.SocketTimeout; //  Socket超時時間
        pool.MaintenanceSleep  =  memCachedConfigInfo.MaintenanceSleep; // 維護線程休息時間
        pool.Failover  =  memCachedConfigInfo.FailOver;  // 失效轉移(一種備份操做模式)
        pool.Nagle  =  memCachedConfigInfo.Nagle; // 是否用nagle算法啓動socket
        pool.HashingAlgorithm  =  HashingAlgorithm.NewCompatibleHash;
        pool.Initialize();
        

        mc 
=   new  MemcachedClient();
        mc.PoolName 
=  memCachedConfigInfo.PoolName;
        mc.EnableCompression 
=   false ;
    }

    
///   <summary>
    
///  緩存服務器地址列表
    
///   </summary>
     public   static   string [] ServerList
    {
        
set
        {
            
if  (value  !=   null )
                serverList 
=  value;
        }
        
get  {  return  serverList; }
    }

    
///   <summary>
    
///  客戶端緩存操做對象
    
///   </summary>
     public   static  MemcachedClient CacheClient
    {
        
get
        {
            
if  (mc  ==   null )
                CreateManager();

            
return  mc;
        }
    }

    
public   static   void  Dispose()
    {
        
if  (pool  !=   null )
            pool.Shutdown();
    }
    

    
    上面代碼中構造方法會初始化一個池來管理執行Socket連接,並提供靜態屬性CacheClient以便MemCachedStrategy
來調用。


    固然我還在這個管理操做類中添加了幾個方法分別用於檢測當前有效的分佈式緩存服務器的列表,向指定(或所有)
緩存服務器發送特定stats命令來獲取當前緩存服務器上的數據信息和內存分配信息等,相應的方法以下(詳情見註釋):

///   <summary>
///  獲取當前緩存鍵值所存儲在的服務器
///   </summary>
///   <param name="key"> 當前緩存鍵 </param>
///   <returns> 當前緩存鍵值所存儲在的服務器 </returns>
public   static   string  GetSocketHost( string  key)
{
    
string  hostName  =   "" ;
    SockIO sock 
=   null ;
    
try
    {
        sock 
=  SockIOPool.GetInstance(memCachedConfigInfo.PoolName).GetSock(key);
        
if  (sock  !=   null )
        {
            hostName 
=  sock.Host;
        }
    }
    
finally
    {
        
if  (sock  !=   null )
            sock.Close();
    }
    
return  hostName;
}


///   <summary>
///  獲取有效的服務器地址
///   </summary>
///   <returns> 有效的服務器地 </returns>
public   static   string [] GetConnectedSocketHost()
{
    SockIO sock 
=   null ;
    
string  connectedHost  =   null ;
    
foreach  ( string  hostName  in  serverList)
    {
        
if  ( ! Discuz.Common.Utils.StrIsNullOrEmpty(hostName))
        {
            
try
            {
                sock 
=  SockIOPool.GetInstance(memCachedConfigInfo.PoolName).GetConnection(hostName);
                
if  (sock  !=   null )
                {
                    connectedHost 
=  Discuz.Common.Utils.MergeString(hostName, connectedHost);
                }
            }
            
finally
            {
                
if  (sock  !=   null )
                    sock.Close();
            }
        }
    }
    
return  Discuz.Common.Utils.SplitString(connectedHost,  " , " );
}

///   <summary>
///  獲取服務器端緩存的數據信息
///   </summary>
///   <returns> 返回信息 </returns>
public   static  ArrayList GetStats()
{
    ArrayList arrayList 
=   new  ArrayList();
    
foreach  ( string  server  in  serverList)
    {
        arrayList.Add(server);
    }
    
return  GetStats(arrayList, Stats.Default,  null );
}

///   <summary>
///  獲取服務器端緩存的數據信息
///   </summary>
///   <param name="serverArrayList"> 要訪問的服務列表 </param>
///   <returns> 返回信息 </returns>
public   static  ArrayList GetStats(ArrayList serverArrayList, Stats statsCommand,  string  param)
{
    ArrayList statsArray 
=   new  ArrayList();
    param 
=   Utils.StrIsNullOrEmpty(param)  ?   ""  : param.Trim().ToLower();

    
string  commandstr  =   " stats " ;
    
// 轉換stats命令參數
     switch  (statsCommand)
    {
        
case  Stats.Reset: { commandstr  =   " stats reset " break ; }
        
case  Stats.Malloc: { commandstr  =   " stats malloc " break ; }
        
case  Stats.Maps: { commandstr  =   " stats maps " break ; }
        
case  Stats.Sizes: { commandstr  =   " stats sizes " break ; }
        
case  Stats.Slabs: { commandstr  =   " stats slabs " break ; }
        
case  Stats.Items: { commandstr  =   " stats " break ; }
        
case  Stats.CachedDump:
        {
            
string [] statsparams  =  Utils.SplitString(param,  "   " );
            
if (statsparams.Length  ==   2 )
                
if (Utils.IsNumericArray(statsparams))
                    commandstr 
=   " stats cachedump   "   +  param;

            
break ;                     
        }
        
case  Stats.Detail:
            {
                
if ( string .Equals(param,  " on " ||   string .Equals(param,  " off " ||   string .Equals(param,  " dump " ))
                    commandstr 
=   " stats detail  "   +  param.Trim();

                
break ;
            }
        
default : { commandstr  =   " stats " break ; }
    }
    
// 加載返回值
    Hashtable stats  =  MemCachedManager.CacheClient.Stats(serverArrayList, commandstr);
    
foreach  ( string  key  in  stats.Keys)
    {
        statsArray.Add(key);
        Hashtable values 
=  (Hashtable)stats[key];
        
foreach  ( string  key2  in  values.Keys)
        {
            statsArray.Add(key2 
+   " : "   +  values[key2]);
        }
    }
    
return  statsArray;
}

///   <summary>
///  Stats命令行參數
///   </summary>
public   enum  Stats
{
    
///   <summary>
    
///  stats : 顯示服務器信息, 統計數據等
    
///   </summary>
    Default  =   0 ,
    
///   <summary>
    
///  stats reset : 清空統計數據
    
///   </summary>
    Reset  =   1 ,
    
///   <summary>
    
///  stats malloc : 顯示內存分配數據
    
///   </summary>
    Malloc  =   2 ,
    
///   <summary>
    
///  stats maps : 顯示"/proc/self/maps"數據
    
///   </summary>
    Maps  = 3 ,
    
///   <summary>
    
///  stats sizes
    
///   </summary>
    Sizes  =   4 ,
    
///   <summary>
    
///  stats slabs : 顯示各個slab的信息,包括chunk的大小,數目,使用狀況等
    
///   </summary>
    Slabs  =   5 ,
    
///   <summary>
    
///  stats items : 顯示各個slab中item的數目和最老item的年齡(最後一次訪問距離如今的秒數)
    
///   </summary>
    Items  =   6 ,
    
///   <summary>
    
///  stats cachedump slab_id limit_num : 顯示某個slab中的前 limit_num 個 key 列表
    
///   </summary>
    CachedDump  = 7 ,
    
///   <summary>
    
///  stats detail [on|off|dump] : 設置或者顯示詳細操做記錄   on:打開詳細操做記錄  off:關閉詳細操做記錄 dump: 顯示詳細操做記錄(每個鍵值get,set,hit,del的次數)
    
///   </summary>
    Detail  =   8
}



    固然在配置初始化緩存連接池時使用了配置文件方式(memcached.config)來管理相關參數,其info信息
類說明以下( Discuz.Config/MemCachedConfigInfo.cs):

///   <summary>
///  MemCached配置信息類文件
///   </summary>
public   class  MemCachedConfigInfo : IConfigInfo
{
    
private   bool  _applyMemCached;
    
///   <summary>
    
///  是否應用MemCached
    
///   </summary>
     public   bool  ApplyMemCached
    {
        
get
        {
            
return  _applyMemCached;
        }
        
set
        {
            _applyMemCached 
=  value;
        }
    }

    
private   string  _serverList;
    
///   <summary>
    
///  連接地址
    
///   </summary>
     public   string  ServerList
    {
        
get
        {
            
return  _serverList;
        }
        
set
        {
            _serverList 
=  value;
        }
    }

    
private   string  _poolName;
    
///   <summary>
    
///  連接池名稱
    
///   </summary>
     public   string  PoolName
    {
        
get
        {
            
return  Utils.StrIsNullOrEmpty(_poolName)  ?   " DiscuzNT_MemCache "  : _poolName;
        }
        
set
        {
            _poolName 
=  value;
        }
    }

    
private   int  _intConnections;
    
///   <summary>
    
///  初始化連接數
    
///   </summary>
     public   int  IntConnections
    {
        
get
        {
            
return  _intConnections  >   0   ?  _intConnections :  3 ;
        }
        
set
        {
            _intConnections 
=  value;
        }
    }

    
private   int  _minConnections;
    
///   <summary>
    
///  最少連接數
    
///   </summary>
     public   int  MinConnections
    {
        
get
        {
            
return  _minConnections  >   0   ?  _minConnections :  3 ;
        }
        
set
        {
            _minConnections 
=  value;
        }
    }

    
private   int  _maxConnections;
    
///   <summary>
    
///  最大鏈接數
    
///   </summary>
     public   int  MaxConnections
    {
        
get
        {
            
return  _maxConnections  >   0   ? _maxConnections :  5 ;
        }
        
set
        {
            _maxConnections 
=  value;
        }
    }

    
private   int  _socketConnectTimeout;
    
///   <summary>
    
///  Socket連接超時時間
    
///   </summary>
     public   int  SocketConnectTimeout
    {
        
get
        {
            
return  _socketConnectTimeout  >   1000   ?  _socketConnectTimeout :  1000 ;
        }
        
set
        {
            _socketConnectTimeout 
=  value;
        }
    }

    
private   int  _socketTimeout;
    
///   <summary>
    
///  socket超時時間
    
///   </summary>
     public   int  SocketTimeout
    {
        
get
        {
            
return  _socketTimeout  >   1000   ?  _maintenanceSleep :  3000 ;
        }
        
set
        {
            _socketTimeout 
=  value;
        }
    }

    
private   int  _maintenanceSleep;
    
///   <summary>
    
///  維護線程休息時間
    
///   </summary>
     public   int  MaintenanceSleep
    {
        
get
        {
            
return  _maintenanceSleep  >   0   ?  _maintenanceSleep :  30 ;
        }
        
set
        {
            _maintenanceSleep 
=  value;
        }
    }

    
private   bool  _failOver;
    
///   <summary>
    
///  連接失敗後是否重啓,詳情參見[url]http://baike.baidu.com/view/1084309.htm[/url]
    
///   </summary>
     public   bool  FailOver
    {
        
get
        {
            
return  _failOver;
        }
        
set
        {
            _failOver 
=  value;
        }
    }

    
private   bool  _nagle;
    
///   <summary>
    
///  是否用nagle算法啓動socket
    
///   </summary>
     public   bool  Nagle
    {
        
get
        {
            
return  _nagle;
        }
        
set
        {
            _nagle 
=  value;
        }
    }
}     



      這些參數咱們經過註釋應該有一些瞭解,能夠說memcached的主要性能都是經過這些參數來決定的,你們
應根據本身公司產品和應用的實際狀況配置相應的數值。

     固然,作完這一步以後就是對調用「緩存策略」的主體類進行修改來,使其根據對管理後臺的設計來決定
加載什麼樣的緩存策略,以下:

///   <summary>
///  Discuz!NT緩存類
///  對Discuz!NT論壇緩存進行全局控制管理
///   </summary>
public   class  DNTCache
{
    .
    
    
// 經過該變量決定是否啓用MemCached
     private   static   bool  applyMemCached  =  MemCachedConfigs.GetConfig().ApplyMemCached;     

    
///   <summary>
    
///  構造函數
    
///   </summary>
     private  DNTCache()
    {
        
if  (applyMemCached)
            cs 
=   new  MemCachedStrategy();
        
else
        {
            cs 
=   new  DefaultCacheStrategy();

            objectXmlMap 
=  rootXml.CreateElement( " Cache " );
            
// 創建內部XML文檔.
            rootXml.AppendChild(objectXmlMap);

            
// LogVisitor clv = new CacheLogVisitor();
            
// cs.Accept(clv);

            cacheConfigTimer.AutoReset 
=   true ;
            cacheConfigTimer.Enabled 
=   true ;
            cacheConfigTimer.Elapsed 
+=   new  System.Timers.ElapsedEventHandler(Timer_Elapsed);
            cacheConfigTimer.Start();
        }
    }
    
    

    
         到這裏,主要的開發和修改基本上就告一段落了。下面開始介紹一下若是使用Stats命令來查看緩存的分配和使用等狀況。以前在枚舉類型Stats中看到該命令有幾個主要的參數,分別是:
 
    stats
    stats reset
    stats malloc
    stats maps
    stats sizes
    stats slabs
    stats items
    stats cachedump slab_id limit_num
    stats detail [on|off|dump]
    
    而JAVAEYE的 robbin 寫過一篇文章: 貼一段遍歷memcached緩存對象的小腳本,來介紹如何使用其中的   「stats cachedump」來獲取信息。受這篇文章的啓發,我將MemCachedClient.cs文件中的Stats方法加以修改,添加了一個command參數(字符串型),這樣就能夠向緩存服務器發送上面所說的那幾種類型的命令了。

    測試代碼以下:
    
protected   void  Submit_Click( object  sender, EventArgs e)
{
    ArrayList arrayList 
=   new  ArrayList();
    arrayList.Add(
" 10.0.1.52:11211 " ); // 緩存服務器的地址

    StateResult.DataSource 
=  MemCachedManager.GetStats(arrayList, (MemCachedManager.Stats)         
                                     Utils.StrToInt(StatsParam.SelectedValue, 
0 ), Param.Text);
    StateResult.DataBind();            
}


    
     頁面代碼以下:
    
   
   
    
     我這樣作的目的有兩個,一個是避免每次都使用telnet協議遠程登錄緩存服務器並輸入相應的命令行參數(我記憶力很差,參數多了以後就愛忘)。二是未來會把這個頁面功能內置到管理後臺上,以便後臺管理員能夠動態監測每臺緩存服務器上的數據。

     好了,到這裏今天的內容就差很少了。在本文中咱們看到了使用設計模式的好處,經過它咱們可讓本身寫的代碼支持「變化」。這裏不妨再多說幾句,你們看到了velocity在使用上也是很方便,若是能夠的話,將來能夠也會將velocity作成一個「緩存策略」,這樣站長或管理員就能夠根據本身公司的實際情
況來加以靈活配置了。
    
    
      相關資料:   
      memcached 全面剖析.pdf   
      memcached 深度分析    
相關文章
相關標籤/搜索