using ServiceStack.Redis; using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Text; using TalentCloud.Common; using TalentCloud.Base.Utils; namespace TalentCloud.Base.RedisHelper { /// <summary> /// 配置管理和初始化 /// </summary> internal class Managers { /// <summary> /// 最大讀寫數量 /// </summary> const int writeReadCount = 100000; //private static string hostString = TCConfigManager.GetConfig("RedisHost").ToString(); private static string hostString = ConfigurationManager.AppSettings["RedisHost"].TryString(); private static string[] writeHosts = hostString.Split(','); private static string[] readHosts = hostString.Split(','); private static int poolConnectTimeout = 1000 * 60 * 2; //private static string[] writeHosts = "".Split(','); //private static string[] readHosts = "".Split(','); //Singleton保證只有一個對象 private static Managers PoolManagers = null; private static Dictionary<int,PooledRedisClientManager> ClientManagerList=new Dictionary<int,PooledRedisClientManager>(); private static PooledRedisClientManager ClientManagers { get; set; } internal PooledRedisClientManager GetClientManagers() { return ClientManagers; } public static Managers Instance() { if (PoolManagers == null) { PoolManagers = new Managers(); } return PoolManagers; } //Singleton private Managers() { //Init(); } public void Init() { //初始化時就建立好0~8的數據庫鏈接 for (int i = 0; i <= 8; i++) { GetManagers(i); } } public PooledRedisClientManager GetManagers(int db) { if (ClientManagerList.ContainsKey(db)) { return ClientManagerList[db]; } else { PooledRedisClientManager dbClientManagers = new PooledRedisClientManager(writeHosts, readHosts, new RedisClientManagerConfig { MaxWritePoolSize = writeReadCount,//「寫」連接池連接數 MaxReadPoolSize = writeReadCount,//「讀」連接池連接數 AutoStart = true, DefaultDb = db //默認數據庫 }); dbClientManagers.ConnectTimeout = poolConnectTimeout; ClientManagerList[db]=dbClientManagers; return dbClientManagers; } } } }
using ServiceStack.Redis; using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Text; using System.Threading; using System.Linq.Expressions; using TalentCloud.Base.Utils; using TalentCloud.Common; namespace TalentCloud.Base.RedisHelper { /// <summary> /// redis客戶端調用類 /// </summary> public class RedisClient { #region //初始化連接 public static void InitRedisPool() { Managers.Instance().Init(); } #endregion #region 寫對象 private string GetEntryId<T>() { return typeof(T).FullName; } #region string 類型存儲及操做 /// <summary> /// 寫數據 永久保存 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="expireIn"></param> /// <returns></returns> public bool SetKeyValue<T>(string key, T value,int db) { Func<IRedisClient, bool> fun = (IRedisClient client) => { client.SetEntry(key, value.ToJsonSerialize()); // client.Save();//持久化到硬盤 return true; }; return TryRedisWrite(fun, db); } /// <summary> /// 寫數據 永久保存 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="expireIn"></param> /// <returns></returns> public List<T> GetListKeyValue<T>(string key, int db) { Func<IRedisClient, List<T>> fun = (IRedisClient client) => { return client.Get<List<T>>(key); }; return TryRedisRead(fun, db); } /// <summary> /// 寫數據 永久保存 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="expireIn"></param> /// <returns></returns> public T GetKeyValue<T>(string key, int db) { Func<IRedisClient, T> fun = (IRedisClient client) => { return client.Get<T>(key); }; return TryRedisRead(fun, db); } /// <summary> /// 寫數據 須要傳過時時間 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="expireIn">TimeSpan.FromMinutes(30)</param> /// <returns></returns> public bool SetKeyValue<T>(string key, T value, int db, TimeSpan expireIn) { Func<IRedisClient, bool> fun = (IRedisClient client) => { client.Db = db; client.SetEntry(key, value.ToJsonSerialize(), expireIn); return true; }; return TryRedisWrite(fun, db); } /// <summary> /// 寫數據 須要傳過時時間 (只能寫string類型數據) /// </summary> /// <param name="typeName"></param> /// <param name="key"></param> /// <param name="value"></param> /// <param name="expireIn"></param> public void SetKeyValue_String(string key, string value, TimeSpan expireIn, int db) { Func<IRedisClient, bool> fun = (IRedisClient client) => { if (expireIn == System.TimeSpan.MinValue) { client.SetEntry(key, value, TimeSpan.FromMinutes(30));//默認30分鐘 } else { client.SetEntry(key, value, expireIn); } return false; }; TryRedisWrite(fun, db); } /// <summary> /// 以Key/Value的形式存儲對象到緩存中 /// </summary> /// <typeparam name="T">對象類別</typeparam> /// <param name="value">要寫入的集合</param> public void KSet<T>(Dictionary<string, T> value,int db) { Func<IRedisClient, bool> fun = (IRedisClient client) => { client.SetAll<T>(value); return true; }; TryRedisWrite(fun, db); } #endregion #region LIST 方式存儲及操做 /// <summary> /// 獲取key包含的全部數據集合T 分頁獲取 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="pageIndex">開始位置</param> /// <param name="pageSize">大小</param> /// <returns></returns> public List<T> GetPageListT<T>(string key, int pageIndex, int pageSize,int db) { int start = pageSize * (pageIndex - 1); return List_GetRange<T>(key, start, pageSize, db); } private List<T> List_GetRange<T>(string key, int start, int count, int db) { Func<IRedisClient, List<T>> fun = (IRedisClient client) => { // client.GetRangeFromSortedSet("") var c = client.GetTypedClient<T>(); return c.Lists[key].GetRange(start, start + count - 1); }; return TryRedisRead(fun, db); } /// <summary> /// 獲取key包含的全部數據集合T /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public List<T> Get<T>(string key, Expression<Func<T, bool>> expression, int db) { Func<IRedisClient, List<T>> fun = (IRedisClient client) => { var c = client.GetTypedClient<T>(); if (expression != null) { return c.Lists[key].GetAll().AsQueryable<T>().Where(expression).ToList(); } else { return c.Lists[key].GetAll(); } }; return TryRedisRead(fun, db); } /// <summary> /// 經過多個key包含的全部數據集合T /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public List<T> Get<T>(List<string> keys, Expression<Func<T, bool>> expression, int db) { List<T> list = new List<T>(); foreach(string item in keys) { Func<IRedisClient, List<T>> fun = (IRedisClient client) => { var c = client.GetTypedClient<T>(); if (expression != null) { return c.Lists[item].GetAll().AsQueryable<T>().Where(expression).ToList(); } else { return c.Lists[item].GetAll(); } }; list.AddRange(TryRedisRead(fun, db)); } return list; } /// <summary> /// 獲取key包含的全部數據集合T /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public List<T> GetListT<T>(string key,int db) { Func<IRedisClient, List<T>> fun = (IRedisClient client) => { var c = client.GetTypedClient<T>(); return c.Lists[key].GetAll(); }; return TryRedisRead(fun,db); } /// <summary> /// 從左側向list中添加值 T /// </summary> /// <typeparam name="T"><peparam> /// <param name="key"></param> /// <param name="t"></param> public void LPushInList<T>(string key, T t, int db) { Func<IRedisClient, bool> fun = (IRedisClient client) => { var redisTypedClient = client.GetTypedClient<T>(); redisTypedClient.PushItemToList(redisTypedClient.Lists[key], t); return true; }; TryRedisRead(fun, db); } /// <summary> /// 入隊 /// </summary> /// <typeparam name="T"><peparam> /// <param name="key"></param> /// <param name="t"></param> public bool EnqueueItemOnList<T>(string key, T t) { Func<IRedisClient, bool> fun = (IRedisClient client) => { var redisTypedClient = client.GetTypedClient<T>(); redisTypedClient.EnqueueItemOnList(redisTypedClient.Lists[key], t); return true; }; return TryRedisWrite(fun,0); } /// <summary> /// 出對 /// </summary> /// <typeparam name="T"><peparam> /// <param name="key"></param> /// <param name="t"></param> public T DequeueItemFromList<T>(string key) { Func<IRedisClient, T> fun = (IRedisClient client) => { var redisTypedClient = client.GetTypedClient<T>(); return redisTypedClient.DequeueItemFromList(redisTypedClient.Lists[key]); }; return TryRedisRead(fun,0); } /// <summary> /// 獲取隊列總數 /// </summary> /// <typeparam name="T"><peparam> /// <param name="key"></param> /// <param name="t"></param> public int GetListCount<T>(string key,int db) { Func<IRedisClient, int> fun = (IRedisClient client) => { var redisTypedClient = client.GetTypedClient<T>(); return redisTypedClient.GetListCount(redisTypedClient.Lists[key]); }; return TryRedisRead(fun, db); } /// <summary> /// 經過key移除list中某一個集合 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="t"></param> /// <returns></returns> public bool List_Remove<T>(string key, T t, int db) { Func<IRedisClient, bool> fun = (IRedisClient client) => { var redisTypedClient = client.GetTypedClient<T>(); return redisTypedClient.RemoveItemFromList(redisTypedClient.Lists[key], t) > 0; }; return TryRedisRead(fun, db); } /// <summary> /// 經過key 移除list集合 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public bool List_RemoveAll<T>(string key, int db) { Func<IRedisClient, bool> fun = (IRedisClient client) => { var redisTypedClient = client.GetTypedClient<T>(); redisTypedClient.Lists[key].RemoveAll(); return true; }; return TryRedisRead(fun,db); } /// <summary> /// key中是否包含 t /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="t"></param> /// <returns></returns> public bool Set_Contains<T>(string key, T t) { Func<IRedisClient, bool> fun = (IRedisClient client) => { var redisTypedClient = client.GetTypedClient<T>(); return redisTypedClient.Sets[key].Contains(t); }; return TryRedisRead(fun,0); } /// <summary> /// 經過key移除list /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="t"></param> /// <returns></returns> public bool Set_Remove<T>(string key, T t,int db) { Func<IRedisClient, bool> fun = (IRedisClient client) => { var redisTypedClient = client.GetTypedClient<T>(); return redisTypedClient.Sets[key].Remove(t); }; return TryRedisRead(fun,db); } #endregion list #region SortedSet 方式存儲及操做 /// <summary> /// 添加數據到 SortedSet /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key">key</param> /// <param name="t">values</param> /// <param name="score">排序</param> public bool SortedSet_Add<T>(string key, T t, double score) { Func<IRedisClient, bool> fun = (IRedisClient client) => { string value = t.ToJsonSerialize(); return client.AddItemToSortedSet(key, value, score); }; return TryRedisWrite(fun,0); } /// <summary> /// 移除數據從SortedSet /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="t"></param> /// <returns></returns> public bool SortedSet_Remove<T>(string key, T t) { Func<IRedisClient, bool> fun = (IRedisClient client) => { string value = t.ToJsonSerialize(); return client.RemoveItemFromSortedSet(key, value); }; return TryRedisWrite(fun,0); } /// <summary> /// 修剪SortedSet /// </summary> /// <param name="key"></param> /// <param name="size">保留的條數</param> /// <returns></returns> public int SortedSet_Trim(string key, int size) { Func<IRedisClient, int> fun = (IRedisClient client) => { return client.RemoveRangeFromSortedSet(key, size, 9999999); }; return TryRedisWrite(fun,0); } /// <summary> /// 獲取SortedSet的長度 /// </summary> /// <param name="key"></param> /// <returns></returns> public int SortedSet_Count(string key) { Func<IRedisClient, int> fun = (IRedisClient client) => { return client.GetSortedSetCount(key); }; return TryRedisWrite(fun,0); } /// <summary> /// 獲取SortedSet的分頁數據 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="pageIndex"></param> /// <param name="pageSize"></param> /// <returns></returns> public List<T> SortedSet_GetList<T>(string key, int pageIndex, int pageSize) { Func<IRedisClient, List<T>> fun = (IRedisClient client) => { var list = client.GetRangeFromSortedSet(key, (pageIndex - 1) * pageSize, pageIndex * pageSize - 1); if (list != null && list.Count > 0) { List<T> result = new List<T>(); foreach (var item in list) { var data = item.DeserializeFromJson<T>(false); result.Add(data); } return result; } return null; }; return TryRedisRead(fun,0); } /// <summary> /// 獲取SortedSet的所有數據 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="pageIndex"></param> /// <param name="pageSize"></param> /// <returns></returns> public List<T> SortedSet_GetListALL<T>(string key) { Func<IRedisClient, List<T>> fun = (IRedisClient client) => { var list = client.GetRangeFromSortedSet(key, 0, 9999999); if (list != null && list.Count > 0) { List<T> result = new List<T>(); foreach (var item in list) { var data = item.DeserializeFromJson<T>(false); result.Add(data); } return result; } return null; }; return TryRedisWrite(fun,0); } /// <summary> /// 設置緩存過時 /// </summary> /// <param name="key"></param> /// <param name="datetime"></param> public bool SortedSet_SetExpire(string key, DateTime datetime) { Func<IRedisClient, bool> fun = (IRedisClient client) => { return client.ExpireEntryAt(key, datetime); }; return TryRedisWrite(fun,0); } #endregion /// <summary> /// 經過key刪除數據 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public bool KRemove(string key) { Func<IRedisClient, bool> fun = (IRedisClient client) => { return client.Remove(key); }; return TryRedisRead(fun,0); } #endregion 寫對象 #region 讀對象 /// <summary> /// 根據ID獲取指定對象 Hash類型數據 /// </summary> /// <param name="key">須要獲取的主鍵,通常爲對象ID值</param> /// <returns></returns> public T HGet<T>(string key) { if (key == null) { return default(T); } Func<IRedisClient, T> fun = (IRedisClient client) => { string ser = ""; string hashId = GetEntryId<T>(); ser = client.GetValueFromHash(hashId, key.ToString()); return ser == null ? default(T) : ser.DeserializeFromJson<T>(false); }; return TryRedisRead(fun,0); } //以hash方式存儲 public List<string> GetHashKeys<T>(string hashKey) { Func<IRedisClient, List<string>> fun = (IRedisClient client) => { return client.GetHashKeys(hashKey); }; return TryRedisRead(fun,0); } /// <summary> /// 判斷key是否存在 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public bool KIsExist(string key) { Func<IRedisClient, bool> fun = (IRedisClient client) => { string ser = ""; ser = client.GetValue(key.ToString()); return string.IsNullOrEmpty(ser) == false; }; return TryRedisRead(fun,0); } /// <summary> /// 讀取Key/Value值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public T KGet<T>(string key) { Func<IRedisClient, T> fun = (IRedisClient client) => { string ser = ""; ser = client.GetValue(key); if (string.IsNullOrEmpty(ser) == false) { return ser.DeserializeFromJson<T>(false); } else { return default(T); } }; return TryRedisRead(fun,0); } public string GetStr(string key) { Func<IRedisClient, string> fun = (IRedisClient client) => { string ser = ""; ser = client.GetValue(key); if (string.IsNullOrEmpty(ser) == false) { return ser; } else { return default(string); } }; return TryRedisRead(fun,0); } /// <summary> /// 讀取Key/Value值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="keys"></param> /// <returns></returns> public Dictionary<string, T> KGet<T>(IList<string> keys) { Func<IRedisClient, Dictionary<string, T>> fun = (IRedisClient client) => { return (Dictionary<string, T>)client.GetAll<T>(keys); }; return TryRedisRead(fun,0); } /// <summary> /// 讀取Key/Value值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="keys"></param> /// <returns></returns> public IList<T> KGetList<T>(IList<string> keys) { Dictionary<string, T> dics = KGet<T>(keys); return dics.Values.ToList(); } /// <summary> /// 返回根據條件查找到的KEY對象列表 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="pattern"></param> /// <returns></returns> public IList<string> KSearchKeys(string pattern) { Func<IRedisClient, IList<string>> fun = (IRedisClient client) => { return client.SearchKeys(pattern); }; return TryRedisRead(fun,0); } /// <summary> /// 返回根據條件查找到的value對象列表 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="pattern"></param> /// <returns></returns> public IList<T> KSearchValues<T>(string pattern) { Func<IRedisClient, IList<T>> fun = (IRedisClient client) => { IList<string> keys = new List<string>(); //先查找KEY keys = client.SearchKeys(pattern); if (keys != null && keys.Count > 0) { //再直接根據key返回對象列表 Dictionary<string, T> dics = (Dictionary<string, T>)client.GetAll<T>(keys); return dics.Values.ToList<T>(); } else { return new List<T>(); } }; return TryRedisRead(fun,0); } #endregion 讀對象 #region List方式存儲以及操做 /// <summary> /// 入隊 /// </summary> /// <param name="hashKey"></param> /// <param name="key"></param> /// <param name="values"></param> /// <param name="db"></param> /// <returns></returns> public bool EnqueueList(string listId, string values, int db) { Func<IRedisClient, bool> fun = (IRedisClient client) => { client.EnqueueItemOnList(listId, values); return true; }; return TryRedisWrite(fun, db); } /// /// 出隊 /// </summary> /// <param name="hashKey"></param> /// <param name="key"></param> /// <param name="values"></param> /// <param name="db"></param> /// <returns></returns> public string DequeueList(string listId, int db) { Func<IRedisClient, string> fun = (IRedisClient client) => { return client.DequeueItemFromList(listId); }; return TryRedisRead(fun, db); } /// /// 獲取隊列的數量 /// </summary> /// <param name="hashKey"></param> /// <param name="key"></param> /// <param name="values"></param> /// <param name="db"></param> /// <returns></returns> public int GetListCount(string listId, int db) { Func<IRedisClient, int> fun = (IRedisClient client) => { return client.GetListCount(listId); }; return TryRedisRead(fun, db); } #endregion #region hash方式存儲及操做 /// <summary> /// 以hash方式存儲 /// </summary> /// <param name="hashKey"></param> /// <param name="key"></param> /// <param name="values"></param> /// <param name="db"></param> /// <returns></returns> public bool SetHash(string hashKey, string key, string values, int db) { Func<IRedisClient, bool> fun = (IRedisClient client) => { client.SetEntryInHash(hashKey, key, values); return true; }; return TryRedisWrite(fun, db); } /// <summary> /// 移除指定的記錄 /// </summary> /// <param name="key">須要移除的主鍵,通常爲對象ID值</param> /// <returns></returns> public bool RemoveEntryFromHash(string hashId, string key, int db) { Func<IRedisClient, bool> fun = (IRedisClient client) => { return client.RemoveEntryFromHash(hashId, key); }; return TryRedisWrite<bool>(fun, db); } /// <summary> /// 獲取全部hashid數據集的key/value數據集合 /// </summary> public Dictionary<string, string> GetAllEntriesFromHash(string hashid, int db) { Func<IRedisClient, Dictionary<string, string>> fun = (IRedisClient client) => { return client.GetAllEntriesFromHash(hashid); }; return TryRedisRead(fun, db); } /// <summary> /// 獲取hashid數據集中全部key的集合 /// </summary> /// <param name="hashid"></param> /// <param name="db"></param> /// <returns></returns> public List<string> GetHashKeys(string hashid, int db) { Func<IRedisClient, List<string>> fun = (IRedisClient client) => { return client.GetHashKeys(hashid); }; return TryRedisRead(fun, db); } /// <summary> /// 獲取hashid數據集中的全部value集合 /// </summary> public List<string> GetHashValues(string hashid, int db) { Func<IRedisClient, List<string>> fun = (IRedisClient client) => { return client.GetHashValues(hashid); }; return TryRedisRead(fun, db); } /// <summary> /// 獲取hashid數據集中,key的value數據 /// </summary> public string GetValueFromHash(string hashid, string key, int db) { Func<IRedisClient, string> fun = (IRedisClient client) => { return client.GetValueFromHash(hashid, key); }; return TryRedisRead(fun, db); } /// <summary> /// 判斷hashid數據集中是否存在key的數據 /// </summary> public bool HashContainsEntry(string hashid, string key, int db) { Func<IRedisClient, bool> fun = (IRedisClient client) => { return client.HashContainsEntry(hashid, key); }; return TryRedisWrite<bool>(fun, db); } #endregion #region 推送消息 /// <summary> /// 推送消息 /// </summary> /// <param name="toChannel">頻道名稱</param> /// <param name="message">消息名稱</param> /// <param name="db">數據庫</param> /// <returns></returns> public int PublishMessage(string toChannel, string message, int db) { Func<IRedisClient, int> fun = (IRedisClient client) => { return client.PublishMessage(toChannel, message); }; return TryRedisWrite<int>(fun, db); } /// <summary> /// 建立一個Subscription /// </summary> /// <param name="db"></param> /// <returns></returns> public IRedisSubscription CreateSubscription(int db) { Func<IRedisClient, IRedisSubscription> fun = (IRedisClient client) => { return client.CreateSubscription(); }; return TryRedisWrite<IRedisSubscription>(fun, db); } #endregion #region 通用的讀寫方法 /// <summary> /// 通用讀取數據方法 /// </summary> /// <typeparam name="F"></typeparam> /// <param name="doRead"></param> /// <returns></returns> private F TryRedisRead<F>(Func<IRedisClient, F> doRead, int db) { using (PooledRedisClientManager prcm = Managers.Instance().GetManagers(db)) { IRedisClient client = null; try { using (client = prcm.GetReadOnlyClient()) { return doRead(client); } } catch (RedisException ex) { if (DateTime.Now > Monitor_RedisStatusDateTime) { string SystemErrorEmailTo = TCConfigManager.GetConfig("SystemErrorEmailTo"); EmailCommom.SendEmail(SystemErrorEmailTo, "", "RedisErr", ex.Message, "", ""); FileLog.AddLog("Redis_TryRedisRead", ex.Message); Monitor_RedisStatusDateTime = DateTime.Now.AddMinutes(30); } return default(F); } finally { if (client != null) { prcm.Dispose(); client.Dispose(); } } } } private static DateTime Monitor_RedisStatusDateTime = DateTime.Now; /// <summary> /// 通用寫入數據方法 /// </summary> /// <typeparam name="F"></typeparam> /// <param name="doWrite"></param> /// <returns></returns> private F TryRedisWrite<F>(Func<IRedisClient, F> doWrite, int db) { using (PooledRedisClientManager prcm = Managers.Instance().GetManagers(db)) { IRedisClient client = null; try { using (client = prcm.GetClient()) { return doWrite(client); } } catch (RedisException ex) { if (DateTime.Now > Monitor_RedisStatusDateTime) { string SystemErrorEmailTo = TCConfigManager.GetConfig("SystemErrorEmailTo"); EmailCommom.SendEmail(SystemErrorEmailTo, "", "RedisErr", ex.Message, "", ""); FileLog.AddLog("Redis_TryRedisWrite", ex.Message); Monitor_RedisStatusDateTime = DateTime.Now.AddMinutes(30); } return default(F); } finally { if (client != null) { prcm.Dispose(); client.Dispose(); } } } } #endregion } }//https://files.cnblogs.com/files/yyyuguo/Redis.zip