大約在兩年前我寫過一篇關於
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 深度分析