大約在兩年前我寫過一篇關(guān)于
Discuz!NT緩存架構(gòu)的文章,在那篇文章的結(jié)尾介紹了在IIS中如果開啟多個(gè)
應(yīng)用程序池會(huì)造成多個(gè)緩存實(shí)例之間數(shù)據(jù)同步的問題。雖然給出了一個(gè)解決方案,但無形中卻把壓力轉(zhuǎn)移到了
磁盤I/O上(多個(gè)進(jìn)程并發(fā)訪問cache.config文件)。其實(shí)從那時(shí)起我就開始關(guān)注有什么更好的方案,當(dāng)然今
天本文中所說的Memcached,以及Velocity等這類的分布式緩存方案之前都考慮過,但一直未能決定該使用那
個(gè)。起碼Velocity要在.net 4.0之后才會(huì)提供,雖然是原生態(tài),但有些遠(yuǎn)水解不了近火。
我想真正等到Velocity能堪當(dāng)重任還要等上一段時(shí)間。于是我就開始將注意力轉(zhuǎn)移到了Memcached,必定
有Facebook這只“超級(jí)小白鼠”使用它并且反響還不錯(cuò)。所以就開始嘗試動(dòng)手在產(chǎn)品中集成Memcached。
其實(shí)在之前的那篇關(guān)于Discuz!NT緩存架構(gòu)的文章中已提到過,使用了設(shè)計(jì)模式中的“策略模式”來構(gòu)造。
所以為了與以往使用緩存的代碼格式相兼容,所以這里采用新添加MemCachedStrategy(MemCached策略)
來構(gòu)造一個(gè)緩存策略類以便于當(dāng)管理后臺(tái)開啟“MemCached”時(shí)以“MemCached策略模式”來做為當(dāng)前系統(tǒng)默認(rèn)
的策略模式。
其代碼段如下(
Discuz.Cache/MemCached.cs):
/// <summary>
/// MemCache緩存策略類
/// </summary>
public class MemCachedStrategy : Discuz.Cache.ICacheStrategy
{
/// <summary>
/// 添加指定ID的對(duì)象
/// </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的對(duì)象(關(guān)聯(lián)指定文件組)
/// </summary>
/// <param name="objId"></param>
/// <param name="o"></param>
/// <param name="files"></param>
public void AddObjectWithFileChange(string objId, object o, string[] files)
{
;
}
/// <summary>
/// 添加指定ID的對(duì)象(關(guān)聯(lián)指定鍵值組)
/// </summary>
/// <param name="objId"></param>
/// <param name="o"></param>
/// <param name="dependKey"></param>
public void AddObjectWithDepend(string objId, object o, string[] dependKey)
{
;
}
/// <summary>
/// 移除指定ID的對(duì)象
/// </summary>
/// <param name="objId"></param>
public void RemoveObject(string objId)
{
if (MemCachedManager.CacheClient.KeyExists(objId))
MemCachedManager.CacheClient.Delete(objId);
}
/// <summary>
/// 返回指定ID的對(duì)象
/// </summary>
/// <param name="objId"></param>
/// <returns></returns>
public object RetrieveObject(string objId)
{
return MemCachedManager.CacheClient.Get(objId);
}
/// <summary>
/// 到期時(shí)間
/// </summary>
public int TimeOut { set; get; }
}
上面類實(shí)現(xiàn)的接口
Discuz.Cache.ICacheStrategy定義如下:
/// <summary>
/// 公共緩存策略接口
/// </summary>
public interface ICacheStrategy
{
/// <summary>
/// 添加指定ID的對(duì)象
/// </summary>
/// <param name="objId"></param>
/// <param name="o"></param>
void AddObject(string objId, object o);
/// <summary>
/// 添加指定ID的對(duì)象(關(guān)聯(lián)指定文件組)
/// </summary>
/// <param name="objId"></param>
/// <param name="o"></param>
/// <param name="files"></param>
void AddObjectWithFileChange(string objId, object o, string[] files);
/// <summary>
/// 添加指定ID的對(duì)象(關(guān)聯(lián)指定鍵值組)
/// </summary>
/// <param name="objId"></param>
/// <param name="o"></param>
/// <param name="dependKey"></param>
void AddObjectWithDepend(string objId, object o, string[] dependKey);
/// <summary>
/// 移除指定ID的對(duì)象
/// </summary>
/// <param name="objId"></param>
void RemoveObject(string objId);
/// <summary>
/// 返回指定ID的對(duì)象
/// </summary>
/// <param name="objId"></param>
/// <returns></returns>
object RetrieveObject(string objId);
/// <summary>
/// 到期時(shí)間
/// </summary>
int TimeOut { set;get;}
}
當(dāng)然在MemCachedStrategy類中還有一個(gè)對(duì)象要加以說明,就是MemCachedManager,該類主要是對(duì)
Memcached一些常操作和相關(guān)初始化實(shí)例調(diào)用的“封裝”,下面是是其變量定義和初始化構(gòu)造方法的代碼:
/// <summary>
/// MemCache管理操作類
/// </summary>
public sealed class MemCachedManager
{
#region 靜態(tài)方法和屬性
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;//初始化鏈接數(shù)
pool.MinConnections = memCachedConfigInfo.MinConnections;//最少鏈接數(shù)
pool.MaxConnections = memCachedConfigInfo.MaxConnections;//最大連接數(shù)
pool.SocketConnectTimeout = memCachedConfigInfo.SocketConnectTimeout;//Socket鏈接超時(shí)時(shí)間
pool.SocketTimeout = memCachedConfigInfo.SocketTimeout;// Socket超時(shí)時(shí)間
pool.MaintenanceSleep = memCachedConfigInfo.MaintenanceSleep;//維護(hù)線程休息時(shí)間
pool.Failover = memCachedConfigInfo.FailOver; //失效轉(zhuǎn)移(一種備份操作模式)
pool.Nagle = memCachedConfigInfo.Nagle;//是否用nagle算法啟動(dòng)socket
pool.HashingAlgorithm = HashingAlgorithm.NewCompatibleHash;
pool.Initialize();
mc = new MemcachedClient();
mc.PoolName = memCachedConfigInfo.PoolName;
mc.EnableCompression = false;
}
/// <summary>
/// 緩存服務(wù)器地址列表
/// </summary>
public static string[] ServerList
{
set
{
if (value != null)
serverList = value;
}
get { return serverList; }
}
/// <summary>
/// 客戶端緩存操作對(duì)象
/// </summary>
public static MemcachedClient CacheClient
{
get
{
if (mc == null)
CreateManager();
return mc;
}
}
public static void Dispose()
{
if (pool != null)
pool.Shutdown();
}


上面代碼中構(gòu)造方法會(huì)初始化一個(gè)池來管理執(zhí)行Socket鏈接,并提供靜態(tài)屬性CacheClient以便MemCachedStrategy
來調(diào)用。
當(dāng)然我還在這個(gè)管理操作類中添加了幾個(gè)方法分別用于檢測當(dāng)前有效的分布式緩存服務(wù)器的列表,向指定(或全部)
緩存服務(wù)器發(fā)送特定stats命令來獲取當(dāng)前緩存服務(wù)器上的數(shù)據(jù)信息和內(nèi)存分配信息等,相應(yīng)的方法如下(詳情見注釋):
/// <summary>
/// 獲取當(dāng)前緩存鍵值所存儲(chǔ)在的服務(wù)器
/// </summary>
/// <param name="key">當(dāng)前緩存鍵</param>
/// <returns>當(dāng)前緩存鍵值所存儲(chǔ)在的服務(wù)器</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>
/// 獲取有效的服務(wù)器地址
/// </summary>
/// <returns>有效的服務(wù)器地</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>
/// 獲取服務(wù)器端緩存的數(shù)據(jù)信息
/// </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>
/// 獲取服務(wù)器端緩存的數(shù)據(jù)信息
/// </summary>
/// <param name="serverArrayList">要訪問的服務(wù)列表</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";
//轉(zhuǎn)換stats命令參數(shù)
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命令行參數(shù)
/// </summary>
public enum Stats
{
/// <summary>
/// stats : 顯示服務(wù)器信息, 統(tǒng)計(jì)數(shù)據(jù)等
/// </summary>
Default = 0,
/// <summary>
/// stats reset : 清空統(tǒng)計(jì)數(shù)據(jù)
/// </summary>
Reset = 1,
/// <summary>
/// stats malloc : 顯示內(nèi)存分配數(shù)據(jù)
/// </summary>
Malloc = 2,
/// <summary>
/// stats maps : 顯示"/proc/self/maps"數(shù)據(jù)
/// </summary>
Maps =3,
/// <summary>
/// stats sizes
/// </summary>
Sizes = 4,
/// <summary>
/// stats slabs : 顯示各個(gè)slab的信息,包括chunk的大小,數(shù)目,使用情況等
/// </summary>
Slabs = 5,
/// <summary>
/// stats items : 顯示各個(gè)slab中item的數(shù)目和最老item的年齡(最后一次訪問距離現(xiàn)在的秒數(shù))
/// </summary>
Items = 6,
/// <summary>
/// stats cachedump slab_id limit_num : 顯示某個(gè)slab中的前 limit_num 個(gè) key 列表
/// </summary>
CachedDump =7,
/// <summary>
/// stats detail [on|off|dump] : 設(shè)置或者顯示詳細(xì)操作記錄 on:打開詳細(xì)操作記錄 off:關(guān)閉詳細(xì)操作記錄 dump: 顯示詳細(xì)操作記錄(每一個(gè)鍵值get,set,hit,del的次數(shù))
/// </summary>
Detail = 8
}
當(dāng)然在配置初始化緩存鏈接池時(shí)使用了配置文件方式(memcached.config)來管理相關(guān)參數(shù),其info信息
類說明如下(
Discuz.Config/MemCachedConfigInfo.cs):
/// <summary>
/// MemCached配置信息類文件
/// </summary>
public class MemCachedConfigInfo : IConfigInfo
{
private bool _applyMemCached;
/// <summary>
/// 是否應(yīng)用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>
/// 初始化鏈接數(shù)
/// </summary>
public int IntConnections
{
get
{
return _intConnections > 0 ? _intConnections : 3;
}
set
{
_intConnections = value;
}
}
private int _minConnections;
/// <summary>
/// 最少鏈接數(shù)
/// </summary>
public int MinConnections
{
get
{
return _minConnections > 0 ? _minConnections : 3;
}
set
{
_minConnections = value;
}
}
private int _maxConnections;
/// <summary>
/// 最大連接數(shù)
/// </summary>
public int MaxConnections
{
get
{
return _maxConnections > 0 ?_maxConnections : 5;
}
set
{
_maxConnections = value;
}
}
private int _socketConnectTimeout;
/// <summary>
/// Socket鏈接超時(shí)時(shí)間
/// </summary>
public int SocketConnectTimeout
{
get
{
return _socketConnectTimeout > 1000 ? _socketConnectTimeout : 1000;
}
set
{
_socketConnectTimeout = value;
}
}
private int _socketTimeout;
/// <summary>
/// socket超時(shí)時(shí)間
/// </summary>
public int SocketTimeout
{
get
{
return _socketTimeout > 1000 ? _maintenanceSleep : 3000;
}
set
{
_socketTimeout = value;
}
}
private int _maintenanceSleep;
/// <summary>
/// 維護(hù)線程休息時(shí)間
/// </summary>
public int MaintenanceSleep
{
get
{
return _maintenanceSleep > 0 ? _maintenanceSleep : 30;
}
set
{
_maintenanceSleep = value;
}
}
private bool _failOver;
/// <summary>
/// 鏈接失敗后是否重啟,詳情參見http://baike.baidu.com/view/1084309.htm
/// </summary>
public bool FailOver
{
get
{
return _failOver;
}
set
{
_failOver = value;
}
}
private bool _nagle;
/// <summary>
/// 是否用nagle算法啟動(dòng)socket
/// </summary>
public bool Nagle
{
get
{
return _nagle;
}
set
{
_nagle = value;
}
}
}
這些參數(shù)我們通過注釋應(yīng)該有一些了解,可以說memcached的主要性能都是通過這些參數(shù)來決定的,大家
應(yīng)根據(jù)自己公司產(chǎn)品和應(yīng)用的實(shí)際情況配置相應(yīng)的數(shù)值。
當(dāng)然,做完這一步之后就是對(duì)調(diào)用“緩存策略”的主體類進(jìn)行修改來,使其根據(jù)對(duì)管理后臺(tái)的設(shè)計(jì)來決定
加載什么樣的緩存策略,如下:
/// <summary>
/// Discuz!NT緩存類
/// 對(duì)Discuz!NT論壇緩存進(jìn)行全局控制管理
/// </summary>
public class DNTCache
{

.
//通過該變量決定是否啟用MemCached
private static bool applyMemCached = MemCachedConfigs.GetConfig().ApplyMemCached;
/// <summary>
/// 構(gòu)造函數(shù)
/// </summary>
private DNTCache()
{
if (applyMemCached)
cs = new MemCachedStrategy();
else
{
cs = new DefaultCacheStrategy();
objectXmlMap = rootXml.CreateElement("Cache");
//建立內(nèi)部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();
}
}


到這里,主要的開發(fā)和修改基本上就告一段落了。下面開始介紹一下如果使用Stats命令來查看緩存的
分配和使用等情況。之前在枚舉類型Stats中看到該命令有幾個(gè)主要的參數(shù),分別是:
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緩存對(duì)象的小腳本,來介紹如何使用其中的
“stats cachedump”來獲取信息。受這篇文章的啟發(fā),我將MemCachedClient.cs文件中的Stats方法加以修
改,添加了一個(gè)command參數(shù)(字符串型),這樣就可以向緩存服務(wù)器發(fā)送上面所說的那幾種類型的命令了。
測試代碼如下:
protected void Submit_Click(object sender, EventArgs e)
{
ArrayList arrayList = new ArrayList();
arrayList.Add("10.0.1.52:11211");//緩存服務(wù)器的地址
StateResult.DataSource = MemCachedManager.GetStats(arrayList, (MemCachedManager.Stats)
Utils.StrToInt(StatsParam.SelectedValue, 0), Param.Text);
StateResult.DataBind();
}
頁面代碼如下:

我這樣做的目的有兩個(gè),一個(gè)是避免每次都使用telnet協(xié)議遠(yuǎn)程登陸緩存服務(wù)器并輸入相應(yīng)的命令行
參數(shù)(我記憶力不好,參數(shù)多了之后就愛忘)。二是將來會(huì)把這個(gè)頁面功能內(nèi)置到管理后臺(tái)上,以便后臺(tái)
管理員可以動(dòng)態(tài)監(jiān)測每臺(tái)緩存服務(wù)器上的數(shù)據(jù)。
好了,到這里今天的內(nèi)容就差不多了。在本文中我們看到了使用設(shè)計(jì)模式的好處,通過它我們可以讓
自己寫的代碼支持“變化”。這里不妨再多說幾句,大家看到了velocity在使用上也是很方便,如果可以
的話,未來可以也會(huì)將velocity做成一個(gè)“緩存策略”,這樣站長或管理員就可以根據(jù)自己公司的實(shí)際情
況來加以靈活配置了。