下面是一些關於Redis比較好的文章,由於篇幅較大,我就將其摺疊起來了。不太喜歡分不一樣的筆記去記載,除非真的不少不少。因此本文不只要對Redis作簡單的介紹,還要分別介紹Redis中的五種結構,並會貼上一些示例代碼,由於篇幅比較大,因此示例代碼都是摺疊起來的,有須要看代碼的請自行點開,還請諒解。這裏只附上了不分示例代碼。php
redis.conf配置詳細解析https://www.cnblogs.com/kreo/p/4423362.html
項目首頁,下方是各類語言支持列表:
http://code.google.com/p/redis/
做者在wiki中給出了一個很是好的例子,以使咱們能夠快速上手,地址:
http://code.google.com/p/redis/wiki/TwitterAlikeExample
同時做者推薦的另外一個教程,地址:
http://labs.alcacoop.it/doku.php?id=articles:redis_land
一個redis愛好者建立的相關問題討論網站:
http://www.rediscookbook.org/
爲何使用 Redis及其產品定位
http://www.infoq.com/cn/articles/tq-why-choose-redis
Redis內存使用優化與存儲
http://www.infoq.com/cn/articles/tq-redis-memory-usage-optimization-storage
https://www.linuxidc.com/Linux/2011-02/32700.htm
本地緩存,當前進程的內存,把數據存起來,下次直接使用,能夠提高效率。html
一、容量有限,window下面,32位一個進程最多2G或者3G,64位最多也就4Glinux
二、多服務器直接須要緩存共享,須要分佈式緩存,遠程服務器內存管理數據,提供讀寫接口,效率高。git
分佈式緩存:github
一、Memcached,最先流行的web
二、Redis(NoSQL)如今的主流方案redis
爲何如今都升級到Redis(NoSQL)去了?數據庫
NoSQL:緩存
非關係型數據庫,Not Only SQL服務器
web1.0時代,服務端提供數據,客戶端看,只能看新聞
web2.0時代,客戶端能夠向服務端互動了,如今能評論了
數據的關係負責:
好友關係(張三有100個好友,映射表,其實這個映射表就榮譽了,關係型數據庫開始累贅了)
再就是數據庫讀取和寫入壓力,硬盤的速度知足不了,尤爲是一些大數據量,因此產生了NoSQL。
特色:基於內存(MongoDB是基於文檔的),沒有嚴格的數據格式,不是一行數據的列必須同樣。封堵的類型,知足web2.0的需求。
Redis和MongoDB都是NoSQL,應該在什麼場景去選擇呢?數據量比較大,用MongoDB,不然其餘的一切需求,用Redis就能夠解決。
Redis:REmote DIctionary Server,遠程點點服務器,基於內存管理(數據存在內存),實現了五種數據結構(String、HashTable、Set、ZSet、List去分別對應各類具體需求),單線程模型應用程序(單進程單線程),對外提供插入-查詢-固化-集羣功能。
Redis----SQLServer
Redis-Cli---SQLClient
Redis支持N多個命令,至關於SQL語句
ServerStack(1小時3600次請求)----ADO.NET
StackExchange,免費的,其實更像是ORM,封裝了鏈接+命令。
https://github.com/dmajkic/redis/downloads 下載地址
管理員模式打開控制檯(配置成本身的路徑)
能夠打開redis-cli.exe嘗試使用,
輸入:set key value
結果:OK
輸入:get key
結果:value
Redis安裝與啓動,以及可視化管理工具,請參考:http://www.javashuo.com/article/p-mqurzecg-cs.html
用cli,能夠簡單的對Redis數據新增和獲取
基於內存管理,速度快,不能當作數據庫。Redis還有個固化數據的功能,VitualMemory,把一些不常常訪問是會存在硬盤。能夠哦誒之的,down掉會丟失數據snapshot能夠保存到硬盤。AOF,數據辯護記錄日誌,不多用。Redis畢竟不是數據庫,只能用來提高性能,不能做爲數據的最終依據。
多線程模型:
.NET應用都是多線程模型,尤爲是網站,能夠更好的發揮硬件的能力,可是也有線衝突的問題和調度成本。
單線程模型:
Node.js是單線程,整個進程只有一個線程,線程就是執行流,性能低?實際上並不是如此。一次網絡請求操做----正則解析請求----加減乘除+數據庫操做(發命令--等結果),讀文件(發命令---等結果)+調用接口(發命令----等結果),單線程都是事件驅動,發起命令就作下一件事,這個線程是徹底不作等待的,一直在計算。
多進程,多進程提供集羣;單線程多進程的模式來提供集羣服務。,B 查詢還有沒有--有---1更新
單線程最大的好處就是原子性操做,就是要麼都成功,要麼都失敗,不會出現中間狀態,Redis中的每一個命令都是原子性的(由於單線程),不用考慮併發。
C#程序中,要使用Redis,首先要Nuget包安裝一些程序集。下面是一些初始化的工做,寫法通常都是固定的,我就摺疊起來了。
/// <summary> /// redis配置文件信息 /// 也能夠放到配置文件去 /// </summary> public sealed class RedisConfigInfo { /// <summary> /// 可寫的Redis連接地址 /// format:ip1,ip2 /// /// 默認6379端口 /// </summary> public string WriteServerList = "127.0.0.1:6379"; /// <summary> /// 可讀的Redis連接地址 /// format:ip1,ip2 /// </summary> public string ReadServerList = "127.0.0.1:6379"; /// <summary> /// 最大寫連接數 /// </summary> public int MaxWritePoolSize = 60; /// <summary> /// 最大讀連接數 /// </summary> public int MaxReadPoolSize = 60; /// <summary> /// 本地緩存到期時間,單位:秒 /// </summary> public int LocalCacheTime = 180; /// <summary> /// 自動重啓 /// </summary> public bool AutoStart = true; /// <summary> /// 是否記錄日誌,該設置僅用於排查redis運行時出現的問題, /// 如redis工做正常,請關閉該項 /// </summary> public bool RecordeLog = false; } /// <summary> /// Redis管理中心 /// </summary> public class RedisManager { /// <summary> /// redis配置文件信息 /// </summary> private static RedisConfigInfo RedisConfigInfo = new RedisConfigInfo(); /// <summary> /// Redis客戶端池化管理 /// </summary> private static PooledRedisClientManager prcManager; /// <summary> /// 靜態構造方法,初始化連接池管理對象 /// </summary> static RedisManager() { CreateManager(); } /// <summary> /// 建立連接池管理對象 /// </summary> private static void CreateManager() { string[] WriteServerConStr = RedisConfigInfo.WriteServerList.Split(','); string[] ReadServerConStr = RedisConfigInfo.ReadServerList.Split(','); prcManager = new PooledRedisClientManager(ReadServerConStr, WriteServerConStr, new RedisClientManagerConfig { MaxWritePoolSize = RedisConfigInfo.MaxWritePoolSize, MaxReadPoolSize = RedisConfigInfo.MaxReadPoolSize, AutoStart = RedisConfigInfo.AutoStart, }); } /// <summary> /// 客戶端緩存操做對象 /// </summary> public static IRedisClient GetClient() { return prcManager.GetClient(); } } /// <summary> /// RedisBase類,是redis操做的基類,繼承自IDisposable接口,主要用於釋放內存 /// </summary> public abstract class RedisBase : IDisposable { public IRedisClient iClient { get; private set; } /// <summary> /// 構造時完成連接的打開 /// </summary> public RedisBase() { iClient = RedisManager.GetClient(); } //public static IRedisClient iClient { get; private set; } //static RedisBase() //{ // iClient = RedisManager.GetClient(); //} private bool _disposed = false; protected virtual void Dispose(bool disposing) { if (!this._disposed) { if (disposing) { iClient.Dispose(); iClient = null; } } this._disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void Transcation() { using (IRedisTransaction irt = this.iClient.CreateTransaction()) { try { irt.QueueCommand(r => r.Set("key", 20)); irt.QueueCommand(r => r.Increment("key", 1)); irt.Commit(); // 提交事務 } catch (Exception ex) { irt.Rollback(); throw ex; } } } /// <summary> /// 清除所有數據 請當心 /// </summary> public virtual void FlushAll() { iClient.FlushAll(); } /// <summary> /// 保存數據DB文件到硬盤 /// </summary> public void Save() { iClient.Save();//阻塞式save } /// <summary> /// 異步保存數據DB文件到硬盤 /// </summary> public void SaveAsync() { iClient.SaveAsync();//異步save } }
Redis中的五大結構
一、String:
key-value,緩存,支持過時,value不超過512M。Redis單線程的特性,好比SetAll、AppendToValue、GetAndSetValue、IncrementValue、IncrementValueBy,這些命令,看上去是組合命令,其實是具體的命令,是一個原則性的操做,不用考慮併發,不可能出現中間狀態,能夠應對一些併發狀況。
如今有個場景,就是超賣的問題,超賣,顧名思義,就是訂單數超過商品。
數據庫:秒殺的時候,10件商品,100我的想買,假定你們一瞬間都來了,A 查詢還有沒有--有---1更新;C 查詢還有沒有--有---1更新;可能會賣出12 12甚至20件商品;微服務也有超賣的問題,異步隊列。Redis原子性操做--保證一個數值只出現一次--防止一個商品賣給多我的
Redis是單線程的,程序有怎麼多線程操做Redis呢?打開多個連接,去提交任務,對程序而言,Redis是併發。因此用上了Redis,一方面保證絕對不會超賣,另外一方面沒有效率影響,數據庫是能夠爲成功的人併發的,還有撤單的時候增長庫存,能夠繼續秒殺,,限制秒殺的庫存是放在redis,不是數據庫,不會形成數據的不一致性,Redis可以攔截無效的請求,若是沒有這一層,全部的請求壓力都到數據庫。
超賣的代碼示例:
/// <summary> /// key-value 鍵值對:value能夠是序列化的數據 /// </summary> public class RedisStringService : RedisBase { #region 賦值 /// <summary> /// 設置key的value /// </summary> public bool Set<T>(string key, T value) { return base.iClient.Set<T>(key, value); } /// <summary> /// 設置key的value並設置過時時間 /// </summary> public bool Set<T>(string key, T value, DateTime dt) { return base.iClient.Set<T>(key, value, dt); } /// <summary> /// 設置key的value並設置過時時間 /// </summary> public bool Set<T>(string key, T value, TimeSpan sp) { return base.iClient.Set<T>(key, value, sp); } /// <summary> /// 設置多個key/value /// </summary> public void Set(Dictionary<string, string> dic) { base.iClient.SetAll(dic); } #endregion #region 追加 /// <summary> /// 在原有key的value值以後追加value,沒有就新增一項 /// </summary> public long Append(string key, string value) { return base.iClient.AppendToValue(key, value); } #endregion #region 獲取值 /// <summary> /// 獲取key的value值 /// </summary> public string Get(string key) { return base.iClient.GetValue(key); } /// <summary> /// 獲取多個key的value值 /// </summary> public List<string> Get(List<string> keys) { return base.iClient.GetValues(keys); } /// <summary> /// 獲取多個key的value值 /// </summary> public List<T> Get<T>(List<string> keys) { return base.iClient.GetValues<T>(keys); } #endregion #region 獲取舊值賦上新值 /// <summary> /// 獲取舊值賦上新值 /// </summary> public string GetAndSetValue(string key, string value) { return base.iClient.GetAndSetValue(key, value); } #endregion #region 輔助方法 /// <summary> /// 獲取值的長度 /// </summary> public long GetLength(string key) { return base.iClient.GetStringCount(key); } /// <summary> /// 自增1,返回自增後的值 /// </summary> public long Incr(string key) { return base.iClient.IncrementValue(key); } /// <summary> /// 自增count,返回自增後的值 /// </summary> public long IncrBy(string key, int count) { return base.iClient.IncrementValueBy(key, count); } /// <summary> /// 自減1,返回自減後的值 /// </summary> public long Decr(string key) { return base.iClient.DecrementValue(key); } /// <summary> /// 自減count ,返回自減後的值 /// </summary> /// <param name="key"></param> /// <param name="count"></param> /// <returns></returns> public long DecrBy(string key, int count) { return base.iClient.DecrementValueBy(key, count); } #endregion }
public class OversellTest { private static bool IsGoOn = true;//秒殺活動是否結束 public static void Show() { using (RedisStringService service = new RedisStringService()) { service.Set<int>("Stock", 10);//是庫存 } for (int i = 0; i < 5000; i++) { int k = i; Task.Run(() =>//每一個線程就是一個用戶請求 { using (RedisStringService service = new RedisStringService()) { if (IsGoOn) { long index = service.Decr("Stock");//-1而且返回 if (index >= 0) { Console.WriteLine($"{k.ToString("000")}秒殺成功,秒殺商品索引爲{index}"); //能夠分隊列,去數據庫操做 } else { if (IsGoOn) { IsGoOn = false; } Console.WriteLine($"{k.ToString("000")}秒殺失敗,秒殺商品索引爲{index}"); } } else { Console.WriteLine($"{k.ToString("000")}秒殺中止......"); } } }); } Console.Read(); } } public class OversellField { private static bool IsGoOn = true;//秒殺活動是否結束 private static int Stock = 0; public static void Show() { Stock = 10; for (int i = 0; i < 5000; i++) { int k = i; Task.Run(() =>//每一個線程就是一個用戶請求 { if (IsGoOn) { long index = Stock;//-1而且返回 去數據庫查一下當前的庫存 Thread.Sleep(100); if (index >= 1) { Stock = Stock - 1;//更新庫存 Console.WriteLine($"{k.ToString("000")}秒殺成功,秒殺商品索引爲{index}"); //能夠分隊列,去數據庫操做 } else { if (IsGoOn) { IsGoOn = false; } Console.WriteLine($"{k.ToString("000")}秒殺失敗,秒殺商品索引爲{index}"); } } else { Console.WriteLine($"{k.ToString("000")}秒殺中止......"); } }); } Console.Read(); } }
二、Hash
key-dictionary
一、節約空間(zipmap的緊密擺放的存儲模式)
二、更新/訪問方便(hashid+key)
三、Hash數據結構很想關係型數據庫的一張表的一行數據。可是其實字段是能夠隨意定製的,沒有嚴格約束。
緩存一個用戶的信息,用String類型能夠嗎?也能夠,由於String類型,key-value,先序列化,而後再反序列化,而後存儲:
using (RedisStringService service = new RedisStringService()) { //service.Set<string>($"userinfo_{user.Id}", Newtonsoft.Json.JsonConvert.SerializeObject(user)); service.Set<UserInfo>($"userinfo_{user.Id}", user); var userCacheList = service.Get<UserInfo>(new List<string>() { $"userinfo_{user.Id}" }); var userCache = userCacheList.FirstOrDefault(); //string sResult = service.Get($"userinfo_{user.Id}"); //var userCache = Newtonsoft.Json.JsonConvert.DeserializeObject<UserInfo>(sResult); userCache.Account = "Admin"; service.Set<UserInfo>($"userinfo_{user.Id}", userCache); }
若是修改需求,就是查詢---反序列化---修改---序列化---存儲。這樣真的太麻煩了。
如今有了Hash類型。
using (RedisHashService service = new RedisHashService()) { service.SetEntryInHash("student", "id", "bingle1"); service.SetEntryInHash("student", "name", "bingle2"); service.SetEntryInHash("student", "remark", "bingle3"); var keys = service.GetHashKeys("student"); var values = service.GetHashValues("student"); var keyValues = service.GetAllEntriesFromHash("student"); Console.WriteLine(service.GetValueFromHash("student", "id")); service.SetEntryInHashIfNotExists("student", "name", "bingle"); service.SetEntryInHashIfNotExists("student", "description", "bingle"); Console.WriteLine(service.GetValueFromHash("student", "name")); Console.WriteLine(service.GetValueFromHash("student", "description")); service.RemoveEntryFromHash("student", "description"); Console.WriteLine(service.GetValueFromHash("student", "description")); }
Hash---》Hashid---UserInfo
多個key,String類型的value,最小是512byte,即便只保存一個1,也要佔用512byte的空間。而hash是一種zipmap存儲,數據緊密排列,能夠節約空間(配置zip兩個屬性,只要都知足就能夠用zipmap存儲)。
Hash的有點:
一、節約空間
二、更新方便
若是實體類型是帶ID的,能夠直接實體存儲和讀取。
using (RedisHashService service = new RedisHashService()) { service.FlushAll(); //反射遍歷作一下 service.SetEntryInHash($"userinfo_{user.Id}", "Account", user.Account); service.SetEntryInHash($"userinfo_{user.Id}", "Name", user.Name); service.SetEntryInHash($"userinfo_{user.Id}", "Address", user.Address); service.SetEntryInHash($"userinfo_{user.Id}", "Email", user.Email); service.SetEntryInHash($"userinfo_{user.Id}", "Password", user.Password); service.SetEntryInHash($"userinfo_{user.Id}", "Account", "Admin"); service.StoreAsHash<UserInfo>(user);//含ID才能夠的 var result = service.GetFromHash<UserInfo>(user.Id); }
三、Set
using (RedisSetService service = new RedisSetService()) { service.FlushAll();//清理所有數據 service.Add("advanced", "111"); service.Add("advanced", "112"); service.Add("advanced", "114"); service.Add("advanced", "114"); service.Add("advanced", "115"); service.Add("advanced", "115"); service.Add("advanced", "113"); var result = service.GetAllItemsFromSet("advanced"); var random = service.GetRandomItemFromSet("advanced");//隨機獲取 service.GetCount("advanced");//獨立的ip數 service.RemoveItemFromSet("advanced", "114"); { service.Add("begin", "111"); service.Add("begin", "112"); service.Add("begin", "115"); service.Add("end", "111"); service.Add("end", "114"); service.Add("end", "113"); var result1 = service.GetIntersectFromSets("begin", "end"); var result2 = service.GetDifferencesFromSet("begin", "end"); var result3 = service.GetUnionFromSets("begin", "end"); //共同好友 共同關注 } }
好友管理,共同好友,可能認識
去重:IP統計去重;添加好友申請;投票限制;點贊。
四、ZSet:是一個有序集合,去重
using (RedisZSetService service = new RedisZSetService()) { service.FlushAll();//清理所有數據 service.Add("advanced", "1"); service.Add("advanced", "2"); service.Add("advanced", "5"); service.Add("advanced", "4"); service.Add("advanced", "7"); service.Add("advanced", "5"); service.Add("advanced", "9"); var result1 = service.GetAll("advanced"); var result2 = service.GetAllDesc("advanced"); service.AddItemToSortedSet("Sort", "bingle1", 123234); service.AddItemToSortedSet("Sort", "bingle2", 123); service.AddItemToSortedSet("Sort", "bingle3", 45); service.AddItemToSortedSet("Sort", "bingle4", 7567); service.AddItemToSortedSet("Sort", "bingle5", 9879); service.AddRangeToSortedSet("Sort", new List<string>() { "123", "花生", "加菲貓" }, 3232); var result3 = service.GetAllWithScoresFromSortedSet("Sort"); //交叉並 }
最後一個參數,是本身設定的,設定value的分數的。
實時排行榜:刷個禮物。
維度不少,平臺/房間/主播/日/周/年/月
A對B刷個禮物,影響不少。
沒Redis以前,刷個禮物值記錄流水,不影響排行,凌晨24點跑任務更新。
實時排行榜,Redis-IncrementItemInSortedSet,刷禮物增長Redis分數,就能夠試試獲取最新的排行,多個維度就是多個ZSet,刷禮物的時候保存數據庫並更新Redis。
五、List
生產者消費者:
using (RedisListService service = new RedisListService()) { service.Add("test", "這是一個學生Add1"); service.Add("test", "這是一個學生Add2"); service.Add("test", "這是一個學生Add3"); service.LPush("test", "這是一個學生LPush1"); service.LPush("test", "這是一個學生LPush2"); service.LPush("test", "這是一個學生LPush3"); service.LPush("test", "這是一個學生LPush4"); service.LPush("test", "這是一個學生LPush5"); service.LPush("test", "這是一個學生LPush6"); service.RPush("test", "這是一個學生RPush1"); service.RPush("test", "這是一個學生RPush2"); service.RPush("test", "這是一個學生RPush3"); service.RPush("test", "這是一個學生RPush4"); service.RPush("test", "這是一個學生RPush5"); service.RPush("test", "這是一個學生RPush6"); List<string> stringList = new List<string>(); for (int i = 0; i < 10; i++) { stringList.Add(string.Format($"放入任務{i}")); } service.Add("task", stringList); Console.WriteLine(service.Count("test")); Console.WriteLine(service.Count("task")); var list = service.Get("test"); list = service.Get("task", 2, 4); Action act = new Action(() => { while (true) { Console.WriteLine("************請輸入數據**************"); string testTask = Console.ReadLine(); service.LPush("test", testTask); } }); act.EndInvoke(act.BeginInvoke(null, null)); }
生產者消費者(隊列)一個數據,只能被一個對象消費,一個程序寫入,一個程序即時讀取消費,還能夠多個程序讀取消費,按照時間順序,數據失敗了還能夠放回去下次重試,這種東西在項目中有什麼價值呢?和那些MQ差很少,就是隊列。
異步隊列:
優勢:
一、能夠控制併發數量
二、之前要求立馬處理完,如今能夠在一個時段完成
三、失敗還能重試
四、流量削峯,下降高峯期的壓力
五、高可用
六、可擴展
缺點:
一、不能理解處理
二、事務的一致性問題。
發佈訂閱,發佈一個數據,所有的訂閱者都能收到。觀察者,一個數據源,多個接受者,只要訂閱了就能夠收到的,能被多個數據源共享。觀察者模式:微信訂閱號---羣聊天----數據同步
MSMQ---RabbitMQ---ZeroMQ----RocketMQ---RedisList
分佈式緩存,多個服務器均可以訪問到,多個生產者,多個消費者,任何產品只被消費一次。
using (RedisListService service = new RedisListService()) { service.Add("test", "這是一個學生Add1"); service.Add("test", "這是一個學生Add2"); service.Add("test", "這是一個學生Add3"); service.LPush("test", "這是一個學生LPush1"); service.LPush("test", "這是一個學生LPush2"); service.LPush("test", "這是一個學生LPush3"); service.LPush("test", "這是一個學生LPush4"); service.LPush("test", "這是一個學生LPush5"); service.LPush("test", "這是一個學生LPush6"); service.RPush("test", "這是一個學生RPush1"); service.RPush("test", "這是一個學生RPush2"); service.RPush("test", "這是一個學生RPush3"); service.RPush("test", "這是一個學生RPush4"); service.RPush("test", "這是一個學生RPush5"); service.RPush("test", "這是一個學生RPush6"); List<string> stringList = new List<string>(); for (int i = 0; i < 10; i++) { stringList.Add(string.Format($"放入任務{i}")); } service.Add("task", stringList); Console.WriteLine(service.Count("test")); Console.WriteLine(service.Count("task")); var list = service.Get("test"); list = service.Get("task", 2, 4); //Action act = new Action(() => //{ // while (true) // { // Console.WriteLine("************請輸入數據**************"); // string testTask = Console.ReadLine(); // service.LPush("test", testTask); // Console.ReadLine(); // } //}); //act.EndInvoke(act.BeginInvoke(null, null)); while (true) { Console.WriteLine("************請輸入數據**************"); string testTask = Console.ReadLine(); service.LPush("test", testTask); } }
public class ServiceStackProcessor { public static void Show() { string path = AppDomain.CurrentDomain.BaseDirectory; string tag = path.Split('/', '\\').Last(s => !string.IsNullOrEmpty(s)); Console.WriteLine($"這裏是 {tag} 啓動了。。"); using (RedisListService service = new RedisListService()) { Action act = new Action(() => { while (true) { var result = service.BlockingPopItemFromLists(new string[] { "test", "task" }, TimeSpan.FromHours(3)); Thread.Sleep(100); Console.WriteLine($"這裏是 {tag} 隊列獲取的消息 {result.Id} {result.Item}"); } }); act.EndInvoke(act.BeginInvoke(null, null)); } } }
ask項目,問答,一天的問題都是幾萬,表裏面是幾千萬數據,首頁要戰士最新的問題,Ajax動態定時獲取刷新,還有前20也是不少人訪問的。
每次寫入數據庫的時候,把ID_標題寫到RedisList,後面搞個TrimList,只要最近200個,用戶刷新頁面的時候就不須要去數據庫了,直接Redis。
還有一種就是水平分表,第一次的時候無論分頁,只拿數據,存數據的時候能夠保存id+表全名。
主要解決數據量大,變化的數據分頁問題。二八原則,80%的訪問集中在20%的數據,List裏面只用保存大概的量就夠了。