在Discuz!NT中進行緩存分層(本地緩存+memcached)

       在之前的兩篇文章( Discuz!NT 緩存設計簡析, Discuz!NT中集成Memcached分佈式緩存)中,介紹了Discuz!NT中的緩存設計思路以及如何引入Memcached,固然前者是IIS進程的緩存(本地緩存),後者是分佈式內存對象緩存系統。
      二者經過Discuz!NT中的memcached.config文件中的 ApplyMemCached結點的值來決定使用哪種緩存方式。不過在以後,有朋友反映當使用Memcached時,特別是在大併發來時,效率會打折扣,甚至有不少時間會消耗在socket套接字(建立和傳輸方面)上。而事實上也的確如此,儘管Memcached在使用池化的方式初始化必定數量的套接字資源(以前測試時實始化爲128個連接),在小併發(100左右)時,可能問題不大,但併發上了1000-2000時,其效率要比本地化緩存機制低1/3(loadrunner測試場景),好比loadrunner測試1000併發時,若是showtopic(顯示主題),本地緩存處理時間爲15秒,而使用memcached可能會達到25-35秒。
      顯然這是用戶所不能忍受的,因此要想解決方案。也就有了今天的文章。
      其實要解決這個問題的原理很簡單,就是將以前的兩種緩存方案(本地緩存和memcached)進行整合,原理以下:
      首先在iis進程中會將要緩存的數據緩存一份,同時也將該數據放入memcached一份,固然本地緩存的數據生命週期要比memcached少。這就形成本地緩存數據到期後,當再次訪問其則將memcached中的數據加載到本地緩存中並返回給應用程序。當緩存的數據更新時,則要更新memcached中的數據和本地緩存的數據(固然若是你要將應用程序佈署的到多個站點時,由於不一樣的站點運行在不一樣的web園或主機上,這時你就不能夠用最簡單的方式來更新其它進程和主機上的應用程序了,由於當前緩存的數據只保存在當前web園進程中),這也就是爲何要給本地緩存數據設置到期時間這個值,讓其在到期後來自動從memcached獲取數據。
     原理解釋完了以後,咱們來看看如何實現這個方案.
     首先,咱們要看一下默認的本地緩存策略文件,其功能也就是兩年前所說的那個本地緩存策略功能,以下:
    
     ///   <summary>
    
///  默認緩存管理類
    
///   </summary>
     public   class   DefaultCacheStrategy : ICacheStrategy
    {
        
private   static   readonly  DefaultCacheStrategy instance  =   new  DefaultCacheStrategy(); 

        
protected   static   volatile  System.Web.Caching.Cache webCache  =  System.Web.HttpRuntime.Cache; 

        
///   <summary>
        
///  默認緩存存活期爲3600秒(1小時)
        
///   </summary>
         protected   int  _timeOut  =   3600

        
private   static   object  syncObj  =   new   object (); 

        
///   <summary>
        
///  構造函數
        
///   </summary>
         static  DefaultCacheStrategy()
        {}


        
///   <summary>
        
///  設置到期相對時間[單位: 秒] 
        
///   </summary>
         public   virtual   int  TimeOut
        {
            
set  { _timeOut  =  value  >   0   ?  value :  3600 ; }
            
get  {  return  _timeOut  >   0   ?  _timeOut :  3600 ; }
        } 

        
public   static  System.Web.Caching.Cache GetWebCacheObj
        {
            
get  {  return  webCache; }
        }


        
///   <summary>
        
///  加入當前對象到緩存中
        
///   </summary>
        
///   <param name="objId"> 對象的鍵值 </param>
        
///   <param name="o"> 緩存的對象 </param>
         public   virtual   void  AddObject( string  objId,  object  o)
        {    
            
if  (objId  ==   null   ||  objId.Length  ==   0   ||  o  ==   null )
            {
                
return ;
            } 

            CacheItemRemovedCallback callBack 
=   new  CacheItemRemovedCallback(onRemove); 

            
if  (TimeOut  ==   7200 )
            {
                webCache.Insert(objId, o, 
null , DateTime.MaxValue, TimeSpan.Zero, System.Web.Caching.CacheItemPriority.High, callBack);
            }
            
else
            {
                webCache.Insert(objId, o, 
null , DateTime.Now.AddSeconds(TimeOut), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, callBack);
            }
        } 

        
///   <summary>
        
///  加入當前對象到緩存中
        
///   </summary>
        
///   <param name="objId"> 對象的鍵值 </param>
        
///   <param name="o"> 緩存的對象 </param>
         public   virtual   void  AddObjectWith( string  objId,  object  o)
        {
            
if  (objId  ==   null   ||  objId.Length  ==   0   ||  o  ==   null )
            {
                
return ;
            } 

            CacheItemRemovedCallback callBack 
=   new  CacheItemRemovedCallback(onRemove); 

            webCache.Insert(objId, o, 
null , System.DateTime.Now.AddSeconds(TimeOut), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, callBack);
        } 

        
///   <summary>
        
///  加入當前對象到緩存中,並對相關文件創建依賴
        
///   </summary>
        
///   <param name="objId"> 對象的鍵值 </param>
        
///   <param name="o"> 緩存的對象 </param>
        
///   <param name="files"> 監視的路徑文件 </param>
         public   virtual   void  AddObjectWithFileChange( string  objId,  object  o,  string [] files)
        {
            
if  (objId  ==   null   ||  objId.Length  ==   0   ||  o  ==   null )
            {
                
return ;
            } 

            CacheItemRemovedCallback callBack 
=   new  CacheItemRemovedCallback(onRemove); 

               CacheDependency dep 
=   new  CacheDependency(files, DateTime.Now); 

            webCache.Insert(objId, o, dep, System.DateTime.Now.AddSeconds(TimeOut), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, callBack);
        } 

        
///   <summary>
        
///  加入當前對象到緩存中,並使用依賴鍵
        
///   </summary>
        
///   <param name="objId"> 對象的鍵值 </param>
        
///   <param name="o"> 緩存的對象 </param>
        
///   <param name="dependKey"> 依賴關聯的鍵值 </param>
         public   virtual   void  AddObjectWithDepend( string  objId,  object  o,  string [] dependKey)
        {
            
if  (objId  ==   null   ||  objId.Length  ==   0   ||  o  ==   null )
            {
                
return ;
            } 

            CacheItemRemovedCallback callBack 
=   new  CacheItemRemovedCallback(onRemove); 

            CacheDependency dep 
=   new  CacheDependency( null , dependKey, DateTime.Now); 

            webCache.Insert(objId, o, dep, System.DateTime.Now.AddSeconds(TimeOut), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, callBack);
        } 

        
///   <summary>
        
///  創建回調委託的一個實例
        
///   </summary>
        
///   <param name="key"></param>
        
///   <param name="val"></param>
        
///   <param name="reason"></param>
         public   void  onRemove( string  key,  object  val, CacheItemRemovedReason reason)
        {
            
switch  (reason)
            {
                
case  CacheItemRemovedReason.DependencyChanged:
                    
break ;
                
case  CacheItemRemovedReason.Expired:
                    {
                        
// CacheItemRemovedCallback callBack = new CacheItemRemovedCallback(this.onRemove); 

                        
// webCache.Insert(key, val, null, System.DateTime.Now.AddMinutes(TimeOut),
                        
//     System.Web.Caching.Cache.NoSlidingExpiration,
                        
//     System.Web.Caching.CacheItemPriority.High,
                        
//     callBack);
                         break ;
                    }
                
case  CacheItemRemovedReason.Removed:
                    {
                        
break ;
                    }
                
case  CacheItemRemovedReason.Underused:
                    {
                        
break ;
                    }
                
default break ;
            }
        } 

        
///   <summary>
        
///  刪除緩存對象
        
///   </summary>
        
///   <param name="objId"> 對象的關鍵字 </param>
         public   virtual   void  RemoveObject( string  objId)
        {
            
if  (objId  ==   null   ||  objId.Length  ==   0 )
            {
                
return ;
            }
            webCache.Remove(objId);
        } 

        
///   <summary>
        
///  返回一個指定的對象
        
///   </summary>
        
///   <param name="objId"> 對象的關鍵字 </param>
        
///   <returns> 對象 </returns>
         public   virtual   object  RetrieveObject( string  objId)
        {
            
if  (objId  ==   null   ||  objId.Length  ==   0 )
            {
                
return   null ;
            }            
            
return  webCache.Get(objId);
        } 
}
       由於在一開始設計Discuz!NT緩存方案時,就使用了Strategy(策略)模式,因此這裏咱們只要將上面所說的改動方案以繼承的方式繼承自上面的
DefaultCacheStrategy 以後,就能夠在DNTCache中使用它了。由於 以前我已經將memcached引入到了discuznt產品中,因此這裏只要改動一下已有的那個MemCachedStrategy,使其支持上面所說的緩存分佈方案便可,請看下面的代碼:
  
    ///   <summary>
   
///  企業級MemCache緩存策略類,只能使用一個web園程序
   
///   </summary>
    public   class  MemCachedStrategy : DefaultCacheStrategy
   {
       
///   <summary>
       
///  添加指定ID的對象
       
///   </summary>
       
///   <param name="objId"></param>
       
///   <param name="o"></param>
        public   override   void  AddObject( string  objId,  object  o)
       {
           
// 先向本地cached加入,而後再加到memcached
           RemoveObject(objId); 

           
base .AddObject(objId, o); 

           MemCachedManager.CacheClient.Set(objId, o);
       } 

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

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

       
///   <summary>
       
///  移除指定ID的對象
       
///   </summary>
       
///   <param name="objId"></param>
        public   override   void  RemoveObject( string  objId)
       {
           
// 先移除本地cached,而後再移除memcached中的相應數據
            if  ( base .RetrieveObject(objId)  !=   null )
               
base .RemoveObject(objId); 

           
if  (MemCachedManager.CacheClient.KeyExists(objId))
               MemCachedManager.CacheClient.Delete(objId);
       } 

       
///   <summary>
       
///  返回指定ID的對象
       
///   </summary>
       
///   <param name="objId"></param>
       
///   <returns></returns>
        public   override   object  RetrieveObject( string  objId)
       {
           
object  obj  =   base .RetrieveObject(objId);
           
if  (obj  ==   null )
           {
               obj 
=  MemCachedManager.CacheClient.Get(objId);
               
if  (obj  !=   null )
                   
base .AddObject(objId, obj);
           } 

           
return  obj;
       } 

       
///   <summary>
       
///  到期時間
       
///   </summary>
        public   override   int  TimeOut 
       { 
           
get  
           { 
               
return  MemCachedConfigs.GetConfig().LocalCacheTime; 
           } 
       }
   }
   
      注:MemCachedStrategy 原來已實現了ICacheStrategy接口,參見 這篇文章
      這樣,咱們仍是能夠經過memcached.config中的ApplyMemCached來判斷是否使用本地緩存方案仍是當前的緩存分層方案。固然原有的memcache.config中還有添加一下屬性用於記錄當使用緩存分層方案以後的本地緩存的緩存數據時間,以向上面的類屬性TimeOut注入相應參數信息。
這樣memcached.config的內容就會變成這個樣子(本地測試配置):
 
<? xml version="1.0" ?>
< MemCachedConfigInfo  xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance%22 xmlns:xsd=" http://www.w3.org/2001/XMLSchema%22 >
    
< ApplyMemCached > true </ ApplyMemCached >   
    
< ServerList > 10.0.2.137:11211 </ ServerList >
    
< PoolName > DiscuzNT_MemCache </ PoolName >
    
< IntConnections > 128 </ IntConnections >
    
< MinConnections > 128 </ MinConnections >
    
< MaxConnections > 512 </ MaxConnections >
    
< SocketConnectTimeout > 1000 </ SocketConnectTimeout >
    
< SocketTimeout > 3000 </ SocketTimeout >
    
< MaintenanceSleep > 30 </ MaintenanceSleep >
    
< FailOver > true </ FailOver >
    
< Nagle > true </ Nagle >
    
< LocalCacheTime > 60 </ LocalCacheTime >
</ MemCachedConfigInfo >
    這樣,當使用Lr測試時,其在併發1000的狀況下與使用本地緩存方案的響應時間基本穩定在15秒左右,想一下你們就會明白了,由於在數據首次加載並進行緩存時(本地和memcached都會緩存一份,參見上面的實現代碼)。當再次訪問時,如在60秒的數據有效期內,僅訪問本地緩存,只有在數據過時時間,纔會運行再次加載數據的工做,而這種加載也只是從memcached中得到數據,這裏咱們能夠暫時將memcached中的數據想像是永不過時,這樣就能夠減小對database的訪問壓力,由於這時相對於本地緩存而言,memcached已經變成了一個‘緩存數據庫’了:
      
        public   override   object  RetrieveObject( string  objId)
       {
           
object  obj  =   base .RetrieveObject(objId);
           
if  (obj  ==   null )
           {
               obj 
=  MemCachedManager.CacheClient.Get(objId);
               
if  (obj  !=   null )
                   
base .AddObject(objId, obj);
           } 

           
return  obj;
       }
     如今用兩張圖再對比說明以前的memcached與如今的緩存分層方案:
        memcached
 
     改進後:
twotie-memcached
 
     總結:其實在大網站的數據緩存方案中,每每會將大量的數據(不常常變化或對時效性要求不強,但卻需頻繁訪問的數據)放入到緩存中,以此來下降數據庫的負載。本地緩存數據的時效性和穩定性受制於IIS進程中線程的運行狀況,資源的佔用等因素影響,能夠說數據的穩定性(不易丟失)遠不如memcached,因此這種分層方案能夠有效的解決這個問題,固然這種作法還有一些其它方面的好處,就不一一說明了。
 
 

0javascript

收藏css

daizhenjun

142篇文章,59W+人氣,2粉絲

Ctrl+Enter 發佈html

發佈java

取消jquery

掃一掃,領取大禮包linux

0git

分享
daizhenjun
相關文章
相關標籤/搜索