一些項目整理出的項目中引入緩存的架構設計方案,但願能幫助你更好地管理項目緩存,做者水平有限,若有不足還望指點。node
1、基礎結構介紹redis
項目中對外提供方法的是CacheProvider和MQProvider兩個類,一切緩存或隊列應用都從這裏作入口,後期更換緩存或隊列只須要更改後面的提供者便可數據庫
主要結構設計分爲三部分:數組
一、Key管理(用於管理緩存Key、過時時間、是否啓用、調用識別Key等)緩存
Configs -> Cache -> KeyConfigList.xml(配置Key的具體信息)安全
Cache -> Key -> KeyEntity.cs(XML的序列化對象)服務器
Cache -> Key -> KeyManager.cs(讀取XML並監聽XML文件的變動,若是變動從新讀取)架構
Cache -> Key -> KeyNames.cs(Key名稱的枚舉,控制Key從這裏集中管理,不會處處都是)ide
二、內部操做(對接的多個緩存實際提供技術好比Redis、Memcached、LocalCache等)函數
Cache -> Redis -> RedisManager.cs(Redis的鏈接對象及基本配置)
三、對外提供(對項目中應用緩存提供支持函數,如更改緩存提供技術只需從這裏調整代碼,不影響項目主體代碼)
Cache -> CacheProvider.cs(項目中的緩存操做提供函數類)
MQ -> MQProvider.cs(項目中的隊列操做提供函數類)
2、代碼詳細介紹
一、KeyConfigList.xml
用於存儲緩存中數據的Key、有效時間、是否啓用此緩存等配置信息
name:用來尋找此條Key信息的標識
key:緩存中存的Key
validTime:便於計算此緩存的有效時間,好比只緩存5分鐘
enabled:是否啓用此緩存,不啓用則每次都讀庫
{0}、{1}、{2}:緩存Key的佔位符用於區分某個類型的緩存其中的一個,好比商品緩存格式爲Goods:{0},可能實際存儲Key是Goods:一、Goods:二、Goods:3,這個一、二、3是商品Id來區分具體某個,若是量大禁用時會致使緩存雪崩,能夠考慮再根據類型或其餘來細分
<?xml version="1.0" encoding="utf-8" ?> <configuration> <!-- name:程序key、key:緩存key(用:冒號來對緩存數據進行分級)、 validTime:有效時間(單位:分)、enabled:是否有效、{0}:佔位符替換相關id等標識 --> <list> <!-- 一個佔位符key --> <item name="Admin_User_Session" key="Admin:User:Session:{0}" validTime="60" enabled="true"></item> <!-- 無佔位符key --> <item name="Admin_User_List" key="Admin:User:List" validTime="30" enabled="true"></item> <!-- 多個佔位符key --> <item name="Admin_User_Search" key="Admin:User:Search:{0}:{1}:{2}" validTime="5" enabled="true"></item> </list> </configuration>
二、KeyEntity.cs
這個比較簡單,就是把xml的內容讀取出來序列化爲對象,只是爲了便於檢索,name和key都小寫化了
/// <summary> /// Key配置對象(公開) /// Author:taiyonghai /// CreateTime:2017-08-28 /// </summary> public sealed class KeyEntity { private string name; /// <summary> /// Cache Name(Use for search cache key) /// </summary> public string Name { get { return name; } set { name = value.Trim().ToLower(); } } private string key; /// <summary> /// Cache Key /// </summary> public string Key { get { return key; } set { key = value.Trim().ToLower(); } } /// <summary> /// Valid Time (Unit:minute) /// </summary> public int ValidTime { get; set; } /// <summary> /// Enaled /// </summary> public bool Enabled { get; set; } }
三、 KeyManager.cs
負責訪問Key配置的XML文件,並將其緩存到靜態Hashtable中,使用時直接從中檢索到要用的信息,設置監聽程序FileSystemWatcher若是文件發生變更則重置Hashtable使其從新讀取,配置文件及名稱能夠自行變動或配置
還要提供根據KeyName獲取Key配置對象的方法,這樣就可使用Key存到實際的緩存中,若是Key須要進行構造還能夠傳送Key的標識數組,今後方法中自動整合返回
/// <summary> /// 緩存Key管理 /// Author:taiyonghai /// CreateTime:2017-08-28 /// </summary> public static class KeyManager { //KeyName集合 private static Hashtable keyNameList; //鎖對象 private static object objLock = new object(); //監控文件對象 private static FileSystemWatcher watcher; //緩存Key配置文件路徑 private static readonly string configFilePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "Configs\\Cache\\"; //緩存Key配置文件名 private static readonly string configFileName = "KeyConfigList.xml"; /// <summary> /// 靜態構造只執行一次 /// </summary> static KeyManager() { //建立對配置文件夾的監聽,若是遇到文件更改則清空KeyNameList,從新讀取 watcher = new FileSystemWatcher(); watcher.Path = configFilePath;//監聽路徑 watcher.Filter = configFileName;//監聽文件名 watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.Size;//僅監聽文件建立時間、文件變動時間、文件大小 watcher.Changed += new FileSystemEventHandler(OnChanged); watcher.EnableRaisingEvents = true;//最後開啓監聽 } /// <summary> /// 讀取KeyName文件 /// </summary> private static void ReaderKeyFile() { if (keyNameList == null || keyNameList.Count == 0) { //鎖定讀取xml操做 lock (objLock) { //獲取配置文件 string configFile = String.Concat(configFilePath, configFileName); //檢查文件 if (!File.Exists(configFile)) { throw new FileNotFoundException(String.Concat("file not exists:", configFile)); } //讀取xml文件 XmlReaderSettings xmlSetting = new XmlReaderSettings(); xmlSetting.IgnoreComments = true;//忽略註釋 XmlReader xmlReader = XmlReader.Create(configFile, xmlSetting); //一次讀完整個文檔 XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(xmlReader); xmlReader.Close();//關閉讀取對象 //獲取指定節點下的全部子節點 XmlNodeList nodeList = xmlDoc.SelectSingleNode("//configuration//list").ChildNodes; //得到一個線程安全的Hashtable對象 keyNameList = Hashtable.Synchronized(new Hashtable()); //將xml中的屬性賦值給Hashtable foreach (XmlNode node in nodeList) { XmlElement element = (XmlElement)node;//轉爲元素獲取屬性 KeyEntity entity = new KeyEntity(); entity.Name = element.GetAttribute("name"); entity.Key = element.GetAttribute("key"); entity.ValidTime = Convert.ToInt32(element.GetAttribute("validTime")); entity.Enabled = Convert.ToBoolean(element.GetAttribute("enabled")); keyNameList.Add(entity.Name, entity); } } } } /// <summary> /// 變動事件會觸發兩次是正常狀況,是系統保存文件機制致使 /// </summary> /// <param name="source"></param> /// <param name="e"></param> private static void OnChanged(object source, FileSystemEventArgs e) { if (e.ChangeType == WatcherChangeTypes.Changed) { if (e.Name.ToLower() == configFileName.ToLower()) { keyNameList = null; //由於此事件會被調用兩次,因此裏面的代碼要有幕等性,若是沒法實現幕等性, //則應該在Init()中綁定事件 //watcher.Changed += new FileSystemEventHandler(OnChanged); //在OnChanged()事件中解綁事件 //watcher.Changed -= new FileSystemEventHandler(OnChanged); } } } /// <summary> /// 根據KeyName獲取Key配置對象 /// </summary> /// <param name="name">Key名稱</param> /// <returns></returns> public static KeyEntity Get(KeyNames name) { return Get(name, null); } /// <summary> /// 根據KeyName獲取Key配置對象 /// </summary> /// <param name="name">Key名稱</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns></returns> public static KeyEntity Get(KeyNames name, params string[] identities) { //檢查Hash中是否有值 if (keyNameList == null || keyNameList.Count == 0) KeyManager.ReaderKeyFile(); //檢查Hash中是否有此Key string tmpName = name.ToString().ToLower(); if (!keyNameList.ContainsKey(tmpName)) throw new ArgumentException("keyNameList中不存在此KeyName", "name"); var entity = keyNameList[tmpName] as KeyEntity; //檢查Key是否須要含有佔位符 if (entity.Key.IndexOf('{') > 0) { //檢查參數數組是否有值 if (identities != null && identities.Length > 0) entity.Key = String.Format(entity.Key, identities); else throw new ArgumentException("須要此參數identities標識字段,但並未傳遞", "identities"); } return entity; } }
四、KeyNames.cs
用枚舉類型是爲了控制傳遞的KeyName可以被限制,不會隨便傳個string過來致使出錯,實際仍是使用了KeyNames.Admin_User_Session.ToString()來識別的,此處是根據枚舉名查找KeyConfigList.xml中的name屬性
/// <summary> /// KeyName枚舉(公開) /// Author:taiyonghai /// CreateTime:2017-08-28 /// </summary> public enum KeyNames { /// <summary> /// 後臺用戶會話key /// </summary> Admin_User_Session, Admin_User_List, Admin_User_Search }
五、RedisManager.cs
這裏能夠是Redis也能夠是Memcached主要就是提供緩存技術的管理,熱門的dll有ServiceStack.Redis和StackExchange.Redis,可前者已經收費(無償使用有使用限額),無限額免費只能用4.0以前的版本,因此採用了後者
IConnectionMultiplexer是核心對象,此處使用單例模式建立鏈接對象,由於建立鏈接的資源消耗較高,後面有測試結果能夠證實
在靜態構造中綁定了幾個異常事件,若是發生了錯誤能夠寫日誌便於咱們調試使用,GetDatabase()方法很輕量能夠放心直接調用,配置文件能夠採用其餘方式
/// <summary> /// Redis緩存管理類 /// Author:taiyonghai /// CreateTime:2017-08-28 /// </summary> public static class RedisManager { //Redis鏈接對象 private static IConnectionMultiplexer redisMultiplexer; //程序鎖 private static object objLock = new object(); //Redis鏈接串(多個服務器用逗號隔開)"10.11.12.237:6379, password='',keepalive=300,connecttimeout=5000,synctimeout=1000" private static readonly string connectStr = "10.11.12.237:6379"; /// <summary> /// 靜態構造用於註冊監聽事件 /// </summary> static RedisManager() { //註冊事件 GetMultiplexer().ConnectionFailed += ConnectionFailed; GetMultiplexer().InternalError += InternalError; GetMultiplexer().ErrorMessage += ErrorMessage; } /// <summary> /// 鏈接失敗 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private static void ConnectionFailed(object sender, ConnectionFailedEventArgs e) { //e.Exception } /// <summary> /// 內部錯誤 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private static void InternalError(object sender, InternalErrorEventArgs e) { //e.Exception } /// <summary> /// 發生錯誤 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private static void ErrorMessage(object sender, RedisErrorEventArgs e) { //e.Message } /// <summary> /// 得到鏈接對象 /// </summary> /// <returns></returns> private static IConnectionMultiplexer GetMultiplexer() { if (redisMultiplexer == null || !redisMultiplexer.IsConnected) { lock (objLock) { //建立Redis鏈接對象 redisMultiplexer = ConnectionMultiplexer.Connect(connectStr); } } return redisMultiplexer; } /// <summary> /// 得到客戶端對象 /// </summary> /// <param name="db">選填指明使用那個數據庫0-16</param> /// <returns></returns> public static IDatabase GetClient(int db = -1) { return GetMultiplexer().GetDatabase(db); } }
若是每次都ConnectionMultiplexer.Connect()一個鏈接對象的測試結果以下:
採用單例模式處理鏈接對象的測試結果以下:
六、CacheProvider.cs
對項目中提供的緩存操做類,提供多個方法,我只提供了String類型和Hash類型,Set集合類型我用不到就沒有提供,須要的朋友能夠本身添加
/// <summary> /// 緩存提供類 /// Author:taiyonghai /// CreateTime:2017-08-28 /// </summary> public class CacheProvider { #region Cache 刪除 /// <summary> /// 刪除緩存 /// </summary> /// <param name="name">Key名稱</param> /// <returns>True成功,Flase失敗</returns> public bool DelKey(KeyNames name) { return DelKey(name, null); } /// <summary> /// 刪除緩存[核心] /// </summary> /// <param name="name">Key名稱</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns>True成功,Flase失敗</returns> public bool DelKey(KeyNames name, params string[] identities) { var entity = KeyManager.Get(name, identities); return RedisManager.GetClient().KeyDelete(entity.Key); } #endregion #region Cache String 存取 #region 添加緩存 /// <summary> /// 添加緩存,有過時時間(如Key存在則更新值) /// </summary> /// <param name="name">Key名稱</param> /// <param name="value">不可爲NULL,應對緩存穿透請用空</param> /// <returns>True成功,Flase失敗</returns> public bool SetString<T>(KeyNames name, T value) where T : class, new() { return SetString<T>(name, value, null); } /// <summary> /// 添加緩存,有過時時間(如Key存在則更新值) /// </summary> /// <param name="name">Key名稱</param> /// <param name="value">不可爲NULL,應對緩存穿透請用空</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns>True成功,Flase失敗</returns> public bool SetString<T>(KeyNames name, T value, params string[] identities) where T : class, new() { //若是value爲null直接緩存無需序列化 string tmpStr = null == value ? null : JsonSerializer.SerializeToString<T>(value); return SetString(name, tmpStr, identities); } /// <summary> /// 添加緩存,有過時時間(如Key存在則更新值) /// </summary> /// <param name="name">Key名稱</param> /// <param name="value">不可爲NULL,應對緩存穿透請用空</param> /// <returns>True成功,Flase失敗</returns> public bool SetString(KeyNames name, string value) { return SetString(name, value, null); } /// <summary> /// 添加緩存,有過時時間[核心](如Key存在則更新值) /// </summary> /// <param name="name">Key名稱</param> /// <param name="value">不可爲NULL,應對緩存穿透請用空</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns>True成功,Flase失敗</returns> public bool SetString(KeyNames name, string value, params string[] identities) { //不可傳值爲NULL,應對緩存穿透請用空,NULL用來判斷是否緩存有值 if (null == value) return false; var entity = KeyManager.Get(name, identities); //有效時間的TimeSpan=(最小時間+有效時間)-最小時間 TimeSpan timeSpan = DateTime.MinValue.AddMinutes(entity.ValidTime) - DateTime.MinValue; return RedisManager.GetClient().StringSet(entity.Key, value, timeSpan); } #endregion #region 獲取緩存 /// <summary> /// 獲取緩存 /// </summary> /// <param name="name">Key名稱</param> /// <returns>無緩存或禁用緩存均返回NULL</returns> public T GetString<T>(KeyNames name) where T : class, new() { return GetString<T>(name, null); } /// <summary> /// 獲取緩存 /// </summary> /// <param name="name">Key名稱</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns>無緩存或禁用緩存均返回NULL</returns> public T GetString<T>(KeyNames name, params string[] identities) where T : class, new() { string tmpStr = GetString(name, identities); //若是tmpStr爲null直接返回無需反序列化 return null == tmpStr ? default(T) : JsonSerializer.DeserializeFromString<T>(tmpStr); } /// <summary> /// 獲取緩存 /// </summary> /// <param name="name">Key名稱</param> /// <returns>無緩存或禁用緩存均返回NULL</returns> public string GetString(KeyNames name) { return GetString(name, null); } /// <summary> /// 獲取緩存[核心] /// </summary> /// <param name="name">Key名稱</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns>無緩存或禁用緩存均返回NULL</returns> public string GetString(KeyNames name, params string[] identities) { var entity = KeyManager.Get(name, identities); //檢查緩存是否啓用,不然返回NULL if (entity.Enabled) { return RedisManager.GetClient().StringGet(entity.Key); } else { return null; } } #endregion #endregion #region Cache Hash 存取 #region 添加緩存 /// <summary> /// 添加緩存,無過時時間(如Key存在則更新值) /// </summary> /// <param name="name">Key名稱</param> /// <param name="dict">不可爲NULL,應對緩存穿透請用空對象</param> public bool SetHash<T>(KeyNames name, Dictionary<string, T> dict) where T : class, new() { return SetHash<T>(name, dict, null); } /// <summary> /// 添加緩存,無過時時間(如Key存在則更新值) /// </summary> /// <param name="name">Key名稱</param> /// <param name="dict">不可爲NULL,應對緩存穿透請用空對象</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> public bool SetHash<T>(KeyNames name, Dictionary<string, T> dict, params string[] identities) where T : class, new() { var tmpDict = new Dictionary<string, string>(); foreach (var item in dict) tmpDict.Add(item.Key, null == item.Value ? null : JsonSerializer.SerializeToString<T>(item.Value)); return SetHash(name, tmpDict, identities); } /// <summary> /// 添加緩存,無過時時間(如Key存在則更新值) /// </summary> /// <param name="name">Key名稱</param> /// <param name="dict">不可爲NULL,應對緩存穿透請用空對象</param> public bool SetHash(KeyNames name, Dictionary<string, string> dict) { return SetHash(name, dict, null); } /// <summary> /// 添加緩存,無過時時間[核心](如Key存在則更新值) /// </summary> /// <param name="name">Key名稱</param> /// <param name="dict">不可爲NULL,應對緩存穿透請用空對象</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> public bool SetHash(KeyNames name, Dictionary<string, string> dict, params string[] identities) { //不可傳值爲NULL,應對緩存穿透請用空對象,NULL用來判斷是否緩存有值 if (null == dict) return false; var entity = KeyManager.Get(name, identities); var hashEntryList = new List<HashEntry>(); foreach (var item in dict) hashEntryList.Add(new HashEntry(item.Key, item.Value)); RedisManager.GetClient().HashSet(entity.Key, hashEntryList.ToArray()); return true; } #endregion #region 獲取緩存 /// <summary> /// 獲取緩存 /// </summary> /// <param name="name">Key名稱</param> /// <returns>無緩存或禁用緩存均返回NULL</returns> public Dictionary<string, T> GetHash<T>(KeyNames name) where T : class, new() { return GetHash<T>(name, null); } /// <summary> /// 獲取緩存 /// </summary> /// <param name="name">Key名稱</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns>無緩存或禁用緩存均返回NULL</returns> public Dictionary<string, T> GetHash<T>(KeyNames name, params string[] identities) where T : class, new() { var dict = GetHash(name, identities); var tmpDict = new Dictionary<string, T>(); foreach (var item in dict) tmpDict.Add(item.Key, null == item.Value ? default(T) : JsonSerializer.DeserializeFromString<T>(item.Value)); return tmpDict; } /// <summary> /// 獲取緩存 /// </summary> /// <param name="name">Key名稱</param> /// <returns>無緩存或禁用緩存均返回NULL</returns> public Dictionary<string, string> GetHash(KeyNames name) { return GetHash(name, null); } /// <summary> /// 獲取緩存[核心] /// </summary> /// <param name="name">Key名稱</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns>無緩存或禁用緩存均返回NULL</returns> public Dictionary<string, string> GetHash(KeyNames name, params string[] identities) { var entity = KeyManager.Get(name, identities); //檢查緩存是否啓用,不然返回NULL if (entity.Enabled) { var hashEntry = RedisManager.GetClient().HashGetAll(entity.Key); var dict = new Dictionary<string, string>(); foreach (var item in hashEntry) dict.Add(item.Name, item.Value); return dict; } else { return null; } } #endregion #endregion }
七、MQProvider.cs
對項目中提供的消息隊列操做類,我偷懶應用了Redis的List類型來提供消息隊列的操做,少數據量的狀況下好比msg在10k如下性能很好,大數據量時性能降低嚴重,有興趣能夠百度一下看看測試,但他沒有事務級的能力因此小規模使用能夠,需求高仍是須要更專業的隊列好比RabbitMQ等
/// <summary> /// 隊列提供類 /// Author:taiyonghai /// CreateTime:2017-08-31 /// </summary> public class MQProvider { #region MQ 添加 /// <summary> /// 添加一條消息到隊列 /// </summary> /// <param name="name">Key名稱</param> /// <param name="msg">內容</param> /// <returns></returns> public long SetMsg<T>(KeyNames name, T msg) where T : class, new() { return SetMsg<T>(name, msg, null); } /// <summary> /// 添加一條消息到隊列 /// </summary> /// <param name="name">Key名稱</param> /// <param name="msg">內容</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns></returns> public long SetMsg<T>(KeyNames name, T msg, params string[] identities) where T : class, new() { //若是value爲null直接緩存無需序列化 string tmpMsg = null == msg ? null : JsonSerializer.SerializeToString<T>(msg); return SetMsg(name, tmpMsg, identities); } /// <summary> /// 添加一條消息到隊列 /// </summary> /// <param name="name">Key名稱</param> /// <param name="msg">內容</param> /// <returns></returns> public long SetMsg(KeyNames name, string msg) { return SetMsg(name, msg, null); } /// <summary> /// 添加一條消息到隊列[核心] /// </summary> /// <param name="name">Key名稱</param> /// <param name="msg">內容</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns>添加消息後的隊列長度</returns> public long SetMsg(KeyNames name, string msg, params string[] identities) { var entity = KeyManager.Get(name, identities); //向隊列右側插入新的消息 return RedisManager.GetClient().ListRightPush(entity.Key, msg); } #endregion #region MQ 獲取 /// <summary> /// 從隊列中獲取一條消息,並將其在隊列中移除 /// </summary> /// <param name="name">Key名稱</param> /// <returns>沒有消息返回NULL</returns> public T GetMsg<T>(KeyNames name) where T : class, new() { return GetMsg<T>(name, null); } /// <summary> /// 從隊列中獲取一條消息,並將其在隊列中移除 /// </summary> /// <param name="name">Key名稱</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns>沒有消息返回NULL</returns> public T GetMsg<T>(KeyNames name, params string[] identities) where T : class, new() { string tmpStr = GetMsg(name, identities); return null == tmpStr ? default(T) : JsonSerializer.DeserializeFromString<T>(tmpStr); } /// <summary> /// 從隊列中獲取一條消息,並將其在隊列中移除 /// </summary> /// <param name="name">Key名稱</param> /// <returns>沒有消息返回NULL</returns> public string GetMsg(KeyNames name) { return GetMsg(name, null); } /// <summary> /// 從隊列中獲取一條消息,並將其在隊列中移除[核心] /// </summary> /// <param name="name">Key名稱</param> /// <param name="identities">Key標識(用於替換Key中的{0}佔位符)</param> /// <returns>沒有消息返回NULL</returns> public string GetMsg(KeyNames name, params string[] identities) { var entity = KeyManager.Get(name, identities); //從隊列左側隊列頭部取出消息 return RedisManager.GetClient().ListLeftPop(entity.Key); } #endregion }
3、項目調用代碼
Redis若是遇到一樣Key且同類型(String、Hash、List)時是直接覆蓋值,若是不一樣類型的話就會報錯了,我偷懶使用了同一個KeyNames就使用加前綴的方式來區分同類型不重複
CacheProvider cache = new CacheProvider(); MQProvider mq = new MQProvider(); //基礎類型 cache.SetString(KeyNames.Cache_Admin_User_Session, "taiyonghai", "100"); var str = cache.GetString(KeyNames.Cache_Admin_User_Session); //Hash類型 var dict = new Dictionary<string, string>(); dict.Add("1", "待處理"); dict.Add("2", "處理中"); dict.Add("3", "處理完成"); cache.SetHash(KeyNames.Cache_Hash_Admin_User_List, dict); var tmpDict = cache.GetHash(KeyNames.Cache_Hash_Admin_User_List); //List隊列 mq.SetMsg(KeyNames.Msg_Admin_User_Search, "Hello"); mq.GetMsg(KeyNames.Msg_Admin_User_Search);
附錄:配置參數解析