上一篇介紹了如何使用nginx+iis部署一個簡單的分布式系統,文章結尾留下了幾個問題,其中一個是"如何解決多站點下Session共享"。這篇文章將會介紹如何使用Redis,下一篇在此基礎上實現Session。
這里特別說明一下,其實沒有必要使用Redis來解決Session共享。Asp.net提供了StateServer模式來共享Session,這里重復造輪子的目的1:熟悉Redis的基本知識和使用 2.學習和鞏固Session的實現原理。
3.學習Redis應用場景
閱讀目錄
Redis安裝配置
redis是一個key-value存儲系統。和Memcached類似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數據都是緩存在內存中。區別的是redis會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。
最新版本的redis版本為3.0.3,支持集群功能。我這下載的是window版本的,實際場景都是安裝在linux系統下的。下載地址:redis-2.8.19.rar 。更多下載地址:
下載完成之后解壓運行redis-server.exe就啟動了redis了,啟動后會在進程里面看到reids。
1.讀寫分離配置
redis的讀寫分離需要修改配置文件,把解壓的文件復制了一份。兩份文件是一樣的,分別命名為MasterRedis-2.8.19(主redis服務),SlaveRedis-2.8.19(從redis服務)。redis默認綁定的是6379端口,
我們保持主服務配置不變,修改從服務配置。
- 修改從服務綁定端口(修改時可以直接搜索port關鍵字)
- 修改從服務對應的主服務地址(修改時可以直接搜索slaveof關鍵字)
- 配置文件修改完成以后,分別啟動主服務和從服務
從服務啟動以后,主服務會發送一條同步的sync命令,同步從服務器的緩存數據。
五種數據類型使用
服務搭建好以后可以使用.net版本redis操作類庫ServiceStack.Redis來操作redis,本文會用到以下三個dll。
初始化RedisClient對象
var client = new RedisClient("120.26.197.185", 6379);
1.String
String是最常用的一種數據類型,普通的key/value存儲都可以歸為此類,value其實不僅是String,也可以是數字:比如想知道什么時候封鎖一個IP地址(訪問超過幾次)。INCRBY命令讓這些變得很容易,通過原子遞增保持計數。
#region "字符串類型" client.Set<string>("name", "laowang"); string userName = client.Get<string>("name"); Console.WriteLine(userName); //訪問次數 client.Set<int>("IpAccessCount", 0); //次數遞增 client.Incr("IpAccessCount"); Console.WriteLine(client.Get<int>("IpAccessCount")); #endregion
2.Hash
一個hashid可以存儲多項信息,每一項信息也有自己的key
client.SetEntryInHash("userInfoId", "name", "zhangsan"); client.SetEntryInHash("userInfoId", "name1", "zhangsan1"); client.SetEntryInHash("userInfoId", "name2", "zhangsan2"); client.SetEntryInHash("userInfoId", "name3", "zhangsan3"); client.GetHashKeys("userInfoId").ForEach(e => Console.WriteLine(e)); client.GetHashValues("userInfoId").ForEach(e => Console.WriteLine(e));
3.List
應用場景:
- Redis list的應用場景非常多,也是Redis最重要的數據結構之一。
- 我們可以輕松地實現最新消息排行等功能。
- Lists的另一個應用就是消息隊列,可以利用Lists的PUSH操作,將任務存在Lists中,然后工作線程再用POP操作將任務取出進行執行。
#region "List類型" client.AddItemToList("userInfoId1", "123"); client.AddItemToList("userInfoId1", "1234"); Console.WriteLine("List數據項條數:" + client.GetListCount("userInfoId1")); Console.WriteLine("List數據項第一條數據:" + client.GetItemFromList("userInfoId1", 0)); Console.WriteLine("List所有數據"); client.GetAllItemsFromList("userInfoId1").ForEach(e => Console.WriteLine(e)); #endregion #region "List類型做為隊列和棧使用" Console.WriteLine(client.GetListCount("userInfoId1")); //隊列先進先出 //Console.WriteLine(client.DequeueItemFromList("userInfoId1")); //Console.WriteLine(client.DequeueItemFromList("userInfoId1")); //棧后進先出 Console.WriteLine("出棧"+client.PopItemFromList("userInfoId1")); Console.WriteLine("出棧"+client.PopItemFromList("userInfoId1")); #endregion
4.Set
應用場景:
- Redis set對外提供的功能與list類似是一個列表的功能,特殊之處在於set是可以自動排重的,當你需要存儲一個列表數據,又不希望出現重復數據時,set是一個很好的選擇,並且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。
- 比如在微博應用中,每個人的好友存在一個集合(set)中,這樣求兩個人的共同好友的操作,可能就只需要用求交集命令即可。
- Redis還為集合提供了求交集、並集、差集等操作,可以非常方便的實
client.AddItemToSet("A", "B"); client.AddItemToSet("A", "C"); client.AddItemToSet("A", "D"); client.AddItemToSet("A", "E"); client.AddItemToSet("A", "F"); client.AddItemToSet("B", "C"); client.AddItemToSet("B", "F"); //求差集 Console.WriteLine("A,B集合差集"); client.GetDifferencesFromSet("A", "B").ToList<string>().ForEach(e => Console.Write(e + ",")); //求集合交集 Console.WriteLine("\nA,B集合交集"); client.GetIntersectFromSets(new string[] { "A", "B" }).ToList<string>().ForEach(e => Console.Write(e + ",")); //求集合並集 Console.WriteLine("\nA,B集合並集"); client.GetUnionFromSets(new string[] { "A", "B" }).ToList<string>().ForEach(e => Console.Write(e + ","));
5.Sort Set(排序)
應用場景:
- 以某個條件為權重,比如按頂的次數排序.
- ZREVRANGE命令可以用來按照得分來獲取前100名的用戶,ZRANK可以用來獲取用戶排名,非常直接而且操作容易。
- Redis sorted set的使用場景與set類似,區別是set不是自動有序的,而sorted set可以通過用戶額外提供一個優先級(score)的參數來為成員排序,並且是插入有序的,即自動排序。
- 比如:twitter 的public timeline可以以發表時間作為score來存儲,這樣獲取時就是自動按時間排好序的。
- 比如:全班同學成績的SortedSets,value可以是同學的學號,而score就可以是其考試得分,這樣數據插入集合的,就已經進行了天然的排序。
- 另外還可以用Sorted Sets來做帶權重的隊列,比如普通消息的score為1,重要消息的score為2,然后工作線程可以選擇按score的倒序來獲取工作任務。讓重要的任務優先執行。
#region "有序Set操作" client.AddItemToSortedSet("SA", "B", 2); client.AddItemToSortedSet("SA", "C", 1); client.AddItemToSortedSet("SA", "D", 5); client.AddItemToSortedSet("SA", "E", 3); client.AddItemToSortedSet("SA", "F", 4); //有序集合降序排列 Console.WriteLine("\n有序集合降序排列"); client.GetAllItemsFromSortedSetDesc("SA").ForEach(e => Console.Write(e + ",")); Console.WriteLine("\n有序集合升序序排列"); client.GetAllItemsFromSortedSet("SA").ForEach(e => Console.Write(e + ",")); client.AddItemToSortedSet("SB", "C", 2); client.AddItemToSortedSet("SB", "F", 1); client.AddItemToSortedSet("SB", "D", 3); Console.WriteLine("\n獲得某個值在有序集合中的排名,按分數的升序排列"); Console.WriteLine(client.GetItemIndexInSortedSet("SB", "D")); Console.WriteLine("\n獲得有序集合中某個值得分數"); Console.WriteLine(client.GetItemScoreInSortedSet("SB", "D")); Console.WriteLine("\n獲得有序集合中,某個排名范圍的所有值"); client.GetRangeFromSortedSet("SA", 0, 3).ForEach(e => Console.Write(e + ",")); #endregion
封裝拓展
最后提供一份別人寫好的Redis操作的幫助類,用到了PooledRedisClientManager連接池來獲取RedisClient,同時用到了讀寫分離的概念,可以直接拿來使用

using System; using System.Collections.Generic; using System.Linq; using System.Text; using ServiceStack.Redis; namespace Com.Redis { /// <summary> /// 來源:http://blog.wx6.org/2013/349.htm /// </summary> public class RedisBase { private static string[] ReadWriteHosts = System.Configuration.ConfigurationSettings.AppSettings["readWriteHosts"].Split(new char[] { ';' }); private static string[] ReadOnlyHosts = System.Configuration.ConfigurationSettings.AppSettings["readOnlyHosts"].Split(new char[] { ';' }); #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 static 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) { // LogInfo } return false; } /// <summary> /// 獲取單體 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public static T Item_Get<T>(string key) where T : class { using (IRedisClient redis = prcm.GetClient()) { return redis.Get<T>(key); } } /// <summary> /// 移除單體 /// </summary> /// <param name="key"></param> public static bool Item_Remove(string key) { using (IRedisClient redis = prcm.GetClient()) { return redis.Remove(key); } } #endregion #region -- List -- public static void List_Add<T>(string key, T t) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.GetTypedClient<T>(); redisTypedClient.AddItemToList(redisTypedClient.Lists[key], t); } } public static bool List_Remove<T>(string key, T t) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.GetTypedClient<T>(); return redisTypedClient.RemoveItemFromList(redisTypedClient.Lists[key], t) > 0; } } public static void List_RemoveAll<T>(string key) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.GetTypedClient<T>(); redisTypedClient.Lists[key].RemoveAll(); } } public static int List_Count(string key) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { return redis.GetListCount(key); } } public static List<T> List_GetRange<T>(string key, int start, int count) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { var c = redis.GetTypedClient<T>(); return c.Lists[key].GetRange(start, start + count - 1); } } public static List<T> List_GetList<T>(string key) { using (IRedisClient redis = prcm.GetReadOnlyClient()) { var c = redis.GetTypedClient<T>(); return c.Lists[key].GetRange(0, c.Lists[key].Count); } } public static 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 static void List_SetExpire(string key, DateTime datetime) { using (IRedisClient redis = prcm.GetClient()) { redis.ExpireEntryAt(key, datetime); } } #endregion #region -- Set -- public static void Set_Add<T>(string key, T t) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.GetTypedClient<T>(); redisTypedClient.Sets[key].Add(t); } } public static bool Set_Contains<T>(string key, T t) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.GetTypedClient<T>(); return redisTypedClient.Sets[key].Contains(t); } } public static bool Set_Remove<T>(string key, T t) { using (IRedisClient redis = prcm.GetClient()) { var redisTypedClient = redis.GetTypedClient<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 static 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> /// <param name="key"></param> /// <param name="dataKey"></param> /// <returns></returns> public static 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 static 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 static 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 static 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 static 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 static void Hash_SetExpire(string key, DateTime datetime) { using (IRedisClient redis = prcm.GetClient()) { redis.ExpireEntryAt(key, datetime); } } #endregion #region -- SortedSet -- /// <summary> /// 添加數據到 SortedSet /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="t"></param> /// <param name="score"></param> public static 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 static 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 static int 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 static int 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 static 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 static 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 static void SortedSet_SetExpire(string key, DateTime datetime) { using (IRedisClient redis = prcm.GetClient()) { redis.ExpireEntryAt(key, datetime); } } #endregion } }
使用很簡單,幾行代碼
//會往主服務里面寫入 RedisBase.Hash_Set<string>("PooledRedisClientManager", "one", "123"); //從服務里面讀取信息 RedisBase.Hash_Get<string>("PooledRedisClientManager", "one");
連接池的初始化
private static string[] ReadWriteHosts = System.Configuration.ConfigurationSettings.AppSettings["readWriteHosts"].Split(new char[] { ';' }); private static string[] ReadOnlyHosts = System.Configuration.ConfigurationSettings.AppSettings["readOnlyHosts"].Split(new char[] { ';' }); #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, }); }
配置文件
總結
1.其實php,java等多種語言都能使用redis,在我接觸的項目中見到使用redis做為消息隊列和緩存組件,當然它的功能遠不止於此。后面的文章將詳細介紹redis的幾個使用案例。
2.可以使用redis desktop manager管理工具查看服務器緩存中的數據
本篇文章用到的資源打包下載地址:redis_demo
svn下載地址:http://code.taobao.org/svn/ResidSessionDemo/