Discuz!NT跨站緩存同步

參考文章:
      Discuz!NT 緩存設計簡析 
      Discuz!NT 中集成Memcached分佈式緩存   
      在 Discuz!NT中進行緩存分層(本地緩存+memcached)   

      在以前的文章中,提到了在Discuz!NT中進行緩存分層的概念。以前在產品中也實現了其中的構想,但該方案有一個問題,就是若是將產品進行分佈式佈署 以後,若是某一站點發生數據變化時,只能更新本地緩存和Memcached緩存信息,而其它分佈式佈署的站點則沒法收到緩存數據已修改的‘通知’,致使數 據不一樣步而成爲‘髒數據’。
      雖然在以前的文章中提到經過將本地緩存失效時間‘縮短’(好比15秒後即失效),以便在相對較短的時間內讓本地數據失效從而再次從Memcached讀取 最新的數據,但這一定不符合咱們設計的基本思路,而且致使程序的運行效率低,同時會形成過於頻繁的訪問Memcached,無形中增長了與 Memcached的socket開銷。因此纔有了今天的這篇文章。

      首先要說明的是,這個方案只有Discuz!NT的企業版(EntLib)中提供,因此在普通的版本中是找不到它的影子的,下面我就簡要說明一下其實現思 路。
      由於在傳統的WEB應用開發中,通常都是採用get的方式來得到所須要的數據,也就是經過從客戶端向服務端發送get請求來得到數據。而若是要實現下面的 流程:     html

     當本地緩存數據變化-->更新memcached-->(通知notify)其它分佈式應用   

      這裏我藉助主動發送的模式來實現,爲了便於理解,我這裏將memcached變成服務端,將分佈式佈署的應用當作是一個個‘客戶端’,而當‘客戶端’將數 據更新到memcached時,經過發送http通知的方式來通知其它‘客戶端’,因此咱們要實現的代碼包括兩部分,一部分實現上面流程中的‘將本地數據 變化告之到memcached’。這塊代碼已在以前的文章中被實現了,而咱們只要在相應的‘RemoveObject’方法後跟上一行‘通知其它分佈式應 用’便可(代碼位於Discuz.EntLib\Memcached\MemCachedStrategy.cs),以下面所示:  web

代碼緩存

         ///   <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);

            Discuz.EntLib.SyncCache.SyncRemoteCache(objId);
// 通知其它分佈式應用
        }    

 

      下面就是‘同步其它分佈式應用緩存數據’的代碼了。在介紹代碼以前,先要將‘發送緩存數據修改通知’的設計思想介紹一下:     安全

1.首先咱們須要一下記錄着分佈式 佈署應用的網站列表,它主要是一連接串,好比下面這個格式(用逗號分割):      服務器

< SiteUrl > http://10.0.2.137:8088/tools/,http://10.0.2.150:8088/tools/,http://10.0.2.136:8088/tools/ </ SiteUrl >

        咱們須要將上面的連接串分割以後加上相應的更新緩存工具頁面(稍後介紹)來實現移除(至關時同步)的功能。    多線程

2.爲了安全起見,在發送通知的請求時,需 要對請求進行加密,以避免該功能被其它惡意代碼利用,從而形成系統安全性和效率受到影響,因此我這裏提供了認證碼,即:mvc

< AuthCode > 123123 </ AuthCode >

       這樣,驗證碼加密的請求只有在被同步工具正確解析後,纔會更新相應的緩存數據。負載均衡

      瞭解這些內容以後,咱們看一下相應的實現代碼以驗證一下所說的設計思想(Discuz.EntLib\SyncLocalCache \SyncCache.cs):      框架

 

代碼
///   <summary>
///  同步緩存類
///   </summary>
public   class  SyncCache
{
    
///   <summary>
    
///  除本站 以外的負載均衡站點列表
    
///   </summary>
     static  List < string >  syncCacheUrlList  =   null ;

    
static  LoadBalanceConfigInfo loadBalanceConfigInfo  =  LoadBalanceConfigs.GetConfig();

    
static  SyncCache()
    {
        syncCacheUrlList 
=   new  List < string > ();
        syncCacheUrlList.AddRange(loadBalanceConfigInfo.SiteUrl.
            Replace(
" tools/ " " tools/SyncLocalCache.ashx " ).Split( ' , ' ));
     
        
int  port  =  HttpContext.Current.Request.Url.Port;
        
string  localUrl  =   string .Format( " {0}://{1}{2}{3} " ,
                                         HttpContext.Current.Request.Url.Scheme,
                                         HttpContext.Current.Request.Url.Host,
                                         (port 
==   80   ||  port  ==   0 ?   ""  :  " : "   +  port,
                                         BaseConfigs.GetForumPath);

        Predicate
< string >  matchUrl  =   new  Predicate < string >
        (
            
delegate ( string  webUrl)
            {
                
return  webUrl.IndexOf(localUrl)  >=   0 // 移除本地站點連接,由於當前站點緩存已被移除。
            }
        );

        syncCacheUrlList.RemoveAll(matchUrl);
    }

      首先咱們在靜態構造方法中讀取相應url連接列表(loadBalanceConfigInfo配置文件),而後將其中的本地應用連接去掉,這樣就不會造 成反覆更新本地緩存數據(從而形成死循環)的問題了。接着就是使用一個線程來發送相應的同步數據請求到各個分佈式應用上,以下(包括使用認證碼加密連接信 息):  asp.net

 代碼

   ///   <summary>
    
///  同步遠 程緩存信息
    
///   </summary>
    
///   <param name="cacheKey"></param>
     public   static   void  SyncRemoteCache( string  cacheKey)
    {
        
foreach  ( string  webSite  in  syncCacheUrlList)
        {
            
string  url  =   string .Format( " {0}?cacheKey={1}&passKey={2} " ,
                                       webSite,
                                       cacheKey,
                                       Discuz.Common.Utils.UrlEncode(Discuz.Common.DES.Encode(cacheKey, loadBalanceConfigInfo.AuthCode)));

            ThreadSyncRemoteCache src 
=   new  ThreadSyncRemoteCache(url);
            
new  Thread( new  ThreadStart(src.Send)).Start();
        }
    }

 

      這裏咱們使用線程方式來更新相應的分佈式應用,思路是: 

      對一個分佈式應用發送三次請求,若是其中某一次返回結果爲ok時,則再也不向其發送其他請求了。若是上一次請求不成功,則當前線程暫停五秒後再次發送請求, 直到三次請求用完爲止。這樣主要是考慮到遠程應用上的主機可能某一時刻處於忙碌狀態而沒法響應,因此採用發送三次(每次間隔五秒)的方式。

      下面就是它的主要實現代碼:       

 

代碼
     ///   <summary>
    
///  多線程 更新遠程緩存
    
///   </summary>
     public   class  ThreadSyncRemoteCache
    {
        
public   string  _url;

        
public  ThreadSyncRemoteCache( string  url)
        {
            _url 
=  url;
        }

        
public   void  Send()
        {
            
try
            {
                
// 設置循環三次,若是 某一次更新成功("OK"),則跳出循環
                 for  ( int  count  =   0 ; count  <   3 ; count ++ )
                {
                    
if  ( this .SendWebRequest(_url)  ==   " OK " )
                        
break ;
                    
else
                        Thread.Sleep(
5000 ); // 若是更新不成功,則暫停5秒後再次更新
                }
            }
            
catch  { }
            
finally
            {
                
if  (Thread.CurrentThread.IsAlive)
                    Thread.CurrentThread.Abort();                  
            }
       }

        
///   <summary>
        
///  發送 web請求
        
///   </summary>
        
///   <param name="url"></param>
        
///   <returns></returns>
         public   string  SendWebRequest( string  url)
        {
            StringBuilder builder 
=   new  StringBuilder();
            
try
            {
                WebRequest request 
=  WebRequest.Create( new  Uri(url));
                request.Method 
=   " GET " ;
                request.Timeout 
=   15000 ;
                request.ContentType 
=   " Text/XML " ;
                
using  (WebResponse response  =  request.GetResponse())
                {
                    
using  (StreamReader reader  =   new  StreamReader(response.GetResponseStream(), Encoding.UTF8))
                    {
                        builder.Append(reader.ReadToEnd());
                    }
                }
            }
            
catch
            {
                builder.Append(
" Process Failed! " );
            }
            
return  builder.ToString();
        }
    }
 

 

     如今發送請求的功能介紹完了,下面簡要介紹一下在‘分佈式應用’那一方如何對上面發送的請求進行解析操做的。請看下面的代碼段:    

 

代碼
///   <summary>
    
///  同步本 地緩存
    
///   </summary>
    [WebService(Namespace  =   " http://tempuri.org/ " )]
    [WebServiceBinding(ConformsTo 
=  WsiProfiles.BasicProfile1_1)]
    
public   class  SyncLocalCache : IHttpHandler
    {
        
public   void  Proce***equest(HttpContext context)
        {
            context.Response.ContentType 
=   " text/plain " ;
            
string  cacheKey  =  context.Request.QueryString[ " cacheKey " ];
            
string  passKey  =  context.Request.QueryString[ " passKey " ];

            
if  (Utils.StrIsNullOrEmpty(cacheKey))
            {
                context.Response.Write(
" CacheKey is not null! " );
                
return ;
            }
            
if  ( ! cacheKey.StartsWith( " /Forum " ))
            {
                context.Response.Write(
" CacheKey is not valid! " );
                
return ;
            }
            
if  (passKey  !=  Discuz.Common.DES.Encode(cacheKey, Discuz.Config.LoadBalanceConfigs.GetConfig().AuthCode))
            {
                context.Response.Write(
" AuthCode is not valid! " );
                
return ;
            }

            
// 更新本地緩存 (注:此處不可以使用MemCachedStrategy的RemoveObject方法,由於該方法中有SyncRemoteCache的調用,會形成循 環調用)
            Discuz.Cache.DNTCache cache  =  Discuz.Cache.DNTCache.GetCacheService();
            cache.LoadCacheStrategy(
new  DefaultCacheStrategy());
            cache.RemoveObject(cacheKey);
            cache.LoadDefaultCacheStrategy();    

            context.Response.Write(
" OK " );
        }

        
public   bool  IsReusable
        {
            
get
            {
                
return   false ;
            }
        }
    }
 

          上面代碼首先會獲取請求過來的緩存鍵值和passKey(即認證碼加密後的連接),而後在本地進行數據有效性校驗,若是認證經過的 話,就能夠對其要移除的緩存數據進行操做了,並在操做成功以後返回ok信息。該頁面採用synclocalcache.ashx文件進行聲明,

如 下: 

 

< %@ WebHandler  Language ="C#"   Class ="Discuz.EntLib.SyncLocalCache"  % >

 

     到這裏,只要將該ashx文件放到站點的tools/文件夾下,就能夠實現跨站同步緩存數據的功能了。目前考慮的場景仍是比較單一的,因此實現的 代碼也相對簡單,不排除隨着業務邏輯複雜度不斷提高而作從新設計的可能性。

     爲了便於購買咱們商業服務的客戶進行管理操做,咱們還提供了一個企業級的監控管理工具,該工具基本asp.net mvc框架開發,提供了監視負 載均衡,同步緩存,讀寫分離檢查和遠程服務器運行狀態(CPU,內存等使用狀況)。下面是該工具所提供的同步緩存數據的功能界面: 

      該工具的開發思想和實現原理會在後面章節中加以詳細說明,敬請關注:)     

      原文連接: http://www.cnblogs.com/daizhj/archive/2010/06/18/discuznt_memcache_syncdata.html

      做者: daizhj, 代震軍

      Tags: discuz!nt,memcached,分層

      網址: http://daizhj.cnblogs.com/

相關文章
相關標籤/搜索