二 Redis快取
考慮到將來伺服器的升級擴充套件,使用redis代替.net內建快取是比較理想的選擇。redis是非常成熟好用的快取系統,安裝配置非常簡單,直接上官網下載安裝包 安裝啟動就行了。
1 配置。redis安裝後預設bind 介面是127.0.0.1,也就是本地迴環地址。在開發環境下為了允許多個開發機器作為客戶端訪問,bind配置後面加上了本機區域網ip,如 bind 127.0.0.1 192.168.1.100 。
2 配置了redis讀寫分離。開發web api端的web.config件 增加配置項
<add name="RedisConnection_read" connectionString="192.168.0.100:6379" />
<add name="RedisConnection_write" connectionString="192.168.0.100:6379" />
redis 讀寫輔助類程式碼如下
public class RedisCache: IRedisCache { private static string[] ReadWriteHosts = ConfigurationManager.ConnectionStrings["RedisConnection_read"].ConnectionString.Split(';'); private static string[] ReadOnlyHosts = ConfigurationManager.ConnectionStrings["RedisConnection_write"].ConnectionString.Split(';'); #region -- 連線資訊 -- public static PooledRedisClientManager prcm = CreateManager(ReadWriteHosts, ReadOnlyHosts); private static PooledRedisClientManager CreateManager(string[] readWriteHosts, string[] readOnlyHosts) { // 支援讀寫分離,均衡負載 return new PooledRedisClientManager(readWriteHosts, readOnlyHosts, new RedisClientManagerConfig { MaxWritePoolSize = 5, // “寫”連結池連結數 MaxReadPoolSize = 5, // “讀”連結池連結數 AutoStart = true, }); } #endregion #region -- Item -- /// <summary> /// 設定單體 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="t"></param> /// <param name="timeSpan"></param> /// <returns></returns> public bool Item_Set<T>(string key, T t) { try { using (IRedisClient redis = prcm.GetClient()) { return redis.Set<T>(key, t, new TimeSpan(1, 0, 0)); } } catch (Exception ex) { throw ex; } } /// <summary> /// 設定單體 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="t"></param> /// <param name="timeSpan"></param> /// <returns></returns> public bool Item_Set<T>(string key, T t, int timeout) { try { using (IRedisClient redis = prcm.GetClient()) { return redis.Set<T>(key, t, new TimeSpan(0, timeout, 0)); } } catch (Exception ex) { throw ex; } } /// <summary> /// 獲取單體 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public T Item_Get<T>(string key) where T : class { using (IRedisClient redis = prcm.GetReadOnlyClient()) { return redis.Get<T>(key); } } /// <summary> /// 設定快取過期 /// </summary> /// <param name="key"></param> /// <param name="datetime"></param> public bool Item_Remove(string key) { using (IRedisClient redis = prcm.GetClient()) { return redis.Remove(key); } } /// <summary> /// 設定快取過期 /// </summary> /// <param name="key"></param> public bool Item_SetExpire(string key, int timeout) { using (IRedisClient redis = prcm.GetClient()) { return redis.ExpireEntryIn(key, new TimeSpan(0, timeout, 0)); } } #endregion #region -- List -- public void List_Add<T>(string key, T t) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.As<T>(); redisTypedClient.AddItemToList(redisTypedClient.Lists[key], t); } } public void List_Set<T>(string key, List<T> list) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.As<T>(); redisTypedClient.Lists[key].RemoveAll(); if (list != null) { list.ForEach(p => { redisTypedClient.AddItemToList(redisTypedClient.Lists[key], p); }); } } } public bool List_Remove<T>(string key, T t) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.As<T>(); return redisTypedClient.RemoveItemFromList(redisTypedClient.Lists[key], t) > 0; } } public void List_RemoveAll<T>(string key) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.As<T>(); redisTypedClient.Lists[key].RemoveAll(); } } public void List_RemoveRange<T>(string key,List<T> list) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.As<T>(); if (list != null && list.Count > 0) { for(int i=0; i<list.Count;i++) { List_Remove<T>(key, list[i]); } } } } public long List_Count(string key) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { return redis.GetListCount(key); } } public List<T> List_GetRange<T>(string key, int start, int count) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { var c = redis.As<T>(); return c.Lists[key].GetRange(start, start + count - 1); } } public List<T> List_GetList<T>(string key) { RedisClient client = new RedisClient(""); using (IRedisClient redis = prcm.GetReadOnlyClient()) { var c = redis.As<T>(); return c.Lists[key].GetRange(0, c.Lists[key].Count); } } public List<T> List_GetList<T>(string key, int pageIndex, int pageSize) { int start = pageSize * (pageIndex - 1); return List_GetRange<T>(key, start, pageSize); } /// <summary> /// 設定快取過期 /// </summary> /// <param name="key"></param> /// <param name="datetime"></param> public void List_SetExpire(string key, DateTime datetime) { using (IRedisClient redis = prcm.GetClient()) { redis.ExpireEntryAt(key, datetime); } } #endregion #region -- Set -- public void Set_Add<T>(string key, T t) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.As<T>(); redisTypedClient.Sets[key].Add(t); } } public bool Set_Contains<T>(string key, T t) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.As<T>(); return redisTypedClient.Sets[key].Contains(t); } } public bool Set_Remove<T>(string key, T t) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.As<T>(); return redisTypedClient.Sets[key].Remove(t); } } #endregion #region -- Hash -- /// <summary> /// 判斷某個資料是否已經被快取 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="dataKey"></param> /// <returns></returns> public bool Hash_Exist<T>(string key, string dataKey) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { return redis.HashContainsEntry(key, dataKey); } } /// <summary> /// 儲存資料到hash表 /// </summary> /// <typeparam name="T"></typeparam> Class1.cs /// <param name="key"></param> /// <param name="dataKey"></param> /// <returns></returns> public bool Hash_Set<T>(string key, string dataKey, T t) { using (IRedisClient redis = prcm.GetClient()) { string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t); return redis.SetEntryInHash(key, dataKey, value); } } /// <summary> /// 移除hash中的某值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="dataKey"></param> /// <returns></returns> public bool Hash_Remove(string key, string dataKey) { using (IRedisClient redis = prcm.GetClient()) { return redis.RemoveEntryFromHash(key, dataKey); } } /// <summary> /// 移除整個hash /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="dataKey"></param> /// <returns></returns> public bool Hash_Remove(string key) { using (IRedisClient redis = prcm.GetClient()) { return redis.Remove(key); } } /// <summary> /// 從hash表獲取資料 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="dataKey"></param> /// <returns></returns> public T Hash_Get<T>(string key, string dataKey) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { string value = redis.GetValueFromHash(key, dataKey); return ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(value); } } /// <summary> /// 獲取整個hash的資料 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public List<T> Hash_GetAll<T>(string key) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { var list = redis.GetHashValues(key); if (list != null && list.Count > 0) { List<T> result = new List<T>(); foreach (var item in list) { var value = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item); result.Add(value); } return result; } return null; } } /// <summary> /// 設定快取過期 /// </summary> /// <param name="key"></param> /// <param name="datetime"></param> public void Hash_SetExpire(string key, DateTime datetime) { using (IRedisClient redis = prcm.GetClient()) { redis.ExpireEntryAt(key, datetime); } } /// <summary> /// 獲取Hash集合數量 /// </summary> /// <param name="key">Hashid</param> public long Hash_GetCount(string key) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { return redis.GetHashCount(key); } } #endregion #region -- SortedSet -- /// <summary> /// 新增資料到 SortedSet /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="t"></param> /// <param name="score"></param> public bool SortedSet_Add<T>(string key, T t, double score) { using (IRedisClient redis = prcm.GetClient()) { string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t); return redis.AddItemToSortedSet(key, value, score); } } /// <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) { using (IRedisClient redis = prcm.GetClient()) { string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t); return redis.RemoveItemFromSortedSet(key, value); } } /// <summary> /// 修剪SortedSet /// </summary> /// <param name="key"></param> /// <param name="size">保留的條數</param> /// <returns></returns> public long SortedSet_Trim(string key, int size) { using (IRedisClient redis = prcm.GetClient()) { return redis.RemoveRangeFromSortedSet(key, size, 9999999); } } /// <summary> /// 獲取SortedSet的長度 /// </summary> /// <param name="key"></param> /// <returns></returns> public long SortedSet_Count(string key) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { return redis.GetSortedSetCount(key); } } /// <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) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { var list = redis.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 = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item); result.Add(data); } return result; } } return null; } /// <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) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { var list = redis.GetRangeFromSortedSet(key, 0, 9999999); if (list != null && list.Count > 0) { List<T> result = new List<T>(); foreach (var item in list) { var data = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item); result.Add(data); } return result; } } return null; } /// <summary> /// 設定快取過期 /// </summary> /// <param name="key"></param> /// <param name="datetime"></param> public void SortedSet_SetExpire(string key, DateTime datetime) { using (IRedisClient redis = prcm.GetClient()) { redis.ExpireEntryAt(key, datetime); } } #endregion }
3 用 redis實現 分散式 跨應用session共享,同樣,web api 的webconfig檔案裡session配置為
<system.web> <sessionState mode="Custom" customProvider="RedisSessionStateStore"> <providers> <add name="RedisSessionStateStore" type="MyProject.RedisSessionStateStore" /> </providers> </sessionState> ... <system.web>
RedisSessionStateStore實現程式碼如下:
public class RedisSessionStateStore : SessionStateStoreProviderBase { IRedisCache redis; public RedisSessionStateStore() { redis = IDAL.DALContainer.Resolve<IRedisCache>(); } public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout) { return CreateLegitStoreData(context, null, null, timeout); } internal static SessionStateStoreData CreateLegitStoreData(HttpContext context, ISessionStateItemCollection sessionItems, HttpStaticObjectsCollection staticObjects, int timeout) { if (sessionItems == null) sessionItems = new SessionStateItemCollection(); if (staticObjects == null && context != null) staticObjects = SessionStateUtility.GetSessionStaticObjects(context); return new SessionStateStoreData(sessionItems, staticObjects, timeout); } public override void CreateUninitializedItem(HttpContext context, string id, int timeout) { RedisSessionState state = new RedisSessionState(null, null, timeout); redis.Item_Set<string>(id, state.ToJson(), timeout); } private SessionStateStoreData DoGet(HttpContext context, string id, bool exclusive, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags) { locked = false; lockId = null; lockAge = TimeSpan.Zero; actionFlags = SessionStateActions.None; RedisSessionState state = RedisSessionState.FromJson(redis.Item_Get<string>(id)); if (state == null) { return null; } redis.Item_SetExpire(id, state._timeout); return CreateLegitStoreData(context, state._sessionItems, state._staticObjects, state._timeout); } public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags) { return this.DoGet(context, id, false, out locked, out lockAge, out lockId, out actionFlags); } public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags) { return this.DoGet(context, id, true, out locked, out lockAge, out lockId, out actionFlags); } public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem) { ISessionStateItemCollection sessionItems = null; HttpStaticObjectsCollection staticObjects = null; if (item.Items.Count > 0) sessionItems = item.Items; if (!item.StaticObjects.NeverAccessed) staticObjects = item.StaticObjects; RedisSessionState state2 = new RedisSessionState(sessionItems, staticObjects, item.Timeout); redis.Item_Set<string>(id, state2.ToJson(), item.Timeout); } #region "未實現方法" public override void Dispose() { } public override void EndRequest(HttpContext context) { } public override void InitializeRequest(HttpContext context) { } public override void ReleaseItemExclusive(HttpContext context, string id, object lockId) { } public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item) { redis.Item_Remove(id); } public override void ResetItemTimeout(HttpContext context, string id) { } public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback) { return true; } #endregion } internal sealed class SessionStateItem { public Dictionary<string, SaveValue> Dict; public int Timeout; } internal sealed class SaveValue { public object Value { get; set; } public Type Type { get; set; } } internal sealed class RedisSessionState { internal ISessionStateItemCollection _sessionItems; internal HttpStaticObjectsCollection _staticObjects; internal int _timeout; internal RedisSessionState(ISessionStateItemCollection sessionItems, HttpStaticObjectsCollection staticObjects, int timeout) { this.Copy(sessionItems, staticObjects, timeout); } internal void Copy(ISessionStateItemCollection sessionItems, HttpStaticObjectsCollection staticObjects, int timeout) { this._sessionItems = sessionItems; this._staticObjects = staticObjects; this._timeout = timeout; } public string ToJson() { // 這裡忽略_staticObjects這個成員。 if (_sessionItems == null || _sessionItems.Count == 0) { return null; } Dictionary<string, SaveValue> dict = new Dictionary<string, SaveValue>(_sessionItems.Count); NameObjectCollectionBase.KeysCollection keys = _sessionItems.Keys; string key; object objectValue = string.Empty; Type type = null; //2016-09-04解決存入值沒有型別導致讀取值是jobject問題 for (int i = 0; i < keys.Count; i++) { key = keys[i]; objectValue = _sessionItems[key]; if (objectValue != null) { type = objectValue.GetType(); } else { type = typeof(object); } dict.Add(key, new SaveValue { Value = objectValue, Type = type }); } SessionStateItem item = new SessionStateItem { Dict = dict, Timeout = this._timeout }; return JsonConvert.SerializeObject(item); } public static RedisSessionState FromJson(string json) { if (string.IsNullOrEmpty(json)) { return null; } try { SessionStateItem item = JsonConvert.DeserializeObject<SessionStateItem>(json); SessionStateItemCollection collections = new SessionStateItemCollection(); SaveValue objectValue = null; //2016-09-04解決讀取出來的值 沒有型別的問題 JsonSerializer serializer = new JsonSerializer(); StringReader sr = null; JsonTextReader tReader = null; foreach (KeyValuePair<string, SaveValue> kvp in item.Dict) { objectValue = kvp.Value as SaveValue; if (objectValue.Value == null) { collections[kvp.Key] = null; } else { if (!IsValueType(objectValue.Type)) { sr = new StringReader(objectValue.Value.ToString()); tReader = new JsonTextReader(sr); collections[kvp.Key] = serializer.Deserialize(tReader, objectValue.Type); } else { collections[kvp.Key] = objectValue.Value; } } } return new RedisSessionState(collections, null, item.Timeout); } catch { return null; } } /// <summary> /// 判斷是否為值型別 /// </summary> /// <param name="type">Type</param> /// <returns></returns> public static bool IsValueType(Type type) { if (type.IsValueType) { return true; } //基礎資料型別 if (type == typeof(string) || type == typeof(char) || type == typeof(ushort) || type == typeof(short) || type == typeof(uint) || type == typeof(int) || type == typeof(ulong) || type == typeof(long) || type == typeof(double) || type == typeof(decimal) || type == typeof(bool) || type == typeof(byte)) { return true; } //可為null的基礎資料型別 if (type.IsGenericType && !type.IsGenericTypeDefinition) { Type genericType = type.GetGenericTypeDefinition(); if (Object.ReferenceEquals(genericType, typeof(Nullable<>))) { var actualType = type.GetGenericArguments()[0]; return IsValueType(actualType); } } return false; } }