上一篇介紹瞭如何使用nginx+iis部署一個簡單的分散式系統,文章結尾留下了幾個問題,其中一個是”如何解決多站點下Session共享”。這篇文章將會介紹如何使用Redis,下一篇在此基礎上實現Session。
這裡特別說明一下,其實沒有必要使用Redis來解決Session共享。Asp.net提供了StateServer模式來共享Session,這裡重複造輪子的目的1:熟悉Redis的基本知識和使用 2.學習和鞏固Session的實現原理。
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 。更多下載地址:
官網 :http://redis.io/download MSOpenTech:https://github.com/MSOpenTech/redis dmajkic:https://github.com/dmajkic/redis/downloads
下載完成之後解壓執行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物件
1 |
var client = new RedisClient("120.26.197.185", 6379); |
1.String
String是最常用的一種資料型別,普通的key/value儲存都可以歸為此類,value其實不僅是String,也可以是數字:比如想知道什麼時候封鎖一個IP地址(訪問超過幾次)。INCRBY命令讓這些變得很容易,通過原子遞增保持計數。
1 2 3 4 5 6 7 8 9 10 |
#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
1 2 3 4 5 6 |
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操作將任務取出進行執行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#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還為集合提供了求交集、並集、差集等操作,可以非常方便的實
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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的倒序來獲取工作任務。讓重要的任務優先執行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#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,同時用到了讀寫分離的概念,可以直接拿來使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
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 } } |
使用很簡單,幾行程式碼
1 2 3 4 5 |
//會往主服務裡面寫入 RedisBase.Hash_Set<string>("PooledRedisClientManager", "one", "123"); //從服務裡面讀取資訊 RedisBase.Hash_Get<string>("PooledRedisClientManager", "one"); |
連線池的初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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管理工具檢視伺服器快取中的資料