二者經過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與如今的緩存分層方案:
改進後:
總結:其實在大網站的數據緩存方案中,每每會將大量的數據(不常常變化或對時效性要求不強,但卻需頻繁訪問的數據)放入到緩存中,以此來下降數據庫的負載。本地緩存數據的時效性和穩定性受制於IIS進程中線程的運行狀況,資源的佔用等因素影響,能夠說數據的穩定性(不易丟失)遠不如memcached,因此這種分層方案能夠有效的解決這個問題,固然這種作法還有一些其它方面的好處,就不一一說明了。
©著做權歸做者全部:來自51CTO博客做者daizhenjun的原創做品,如需轉載,請註明出處,不然將追究法律責任
掃一掃,領取大禮包linux
Ctrl+Enter 發佈html
發佈java
取消jquery