Redis簡介
Redis是一個開源的,使用C語言編寫,面向“鍵/值”對類型數據的分布式NoSQL數據庫系統,特點是高性能,持久存儲,適應高並發的應用場景。Redis純粹為應用而產生,它是一個高性能的key-value數據庫,並且提供了多種語言的API
性能測試結果表示SET操作每秒鍾可達110000次,GET操作每秒81000次(當然不同的服務器配置性能不同)。
Redis目前提供五種數據類型:string(字符串),list(鏈表), Hash(哈希),set(集合)及zset(sorted set) (有序集合)
Redis開發維護很活躍,雖然它是一個Key-Value數據庫存儲系統,但它本身支持MQ功能,所以完全可以當做一個輕量級的隊列服務來使用。對於RabbitMQ和Redis的入隊和出隊操作,各執行100萬次,每10萬次記錄一次執行時間。測試數據分為128Bytes、512Bytes、1K和10K四個不同大小的數據。實驗表明:入隊時,當數據比較小時Redis的性能要高於RabbitMQ,而如果數據大小超過了10K,Redis則慢的無法忍受;出隊時,無論數據大小,Redis都表現出非常好的性能,而RabbitMQ的出隊性能則遠低於Redis。
Redis與Memcached的比較
1.Memcached是多線程,而Redis使用單線程.
2.Memcached使用預分配的內存池的方式,Redis使用現場申請內存的方式來存儲數據,並且可以配置虛擬內存。
3.Redis可以實現持久化,主從復制,實現故障恢復。
4.Memcached只是簡單的key與value,但是Redis支持數據類型比較多。
Redis的存儲分為內存存儲、磁盤存儲 .從這一點,也說明了Redis與Memcached是有區別的。Redis 與Memcached一樣,為了保證效率,數據都是緩存在內存中。區別的是redis會周期性的把更新的數據寫入磁盤或者把修改 操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。
Redis有兩種存儲方式,默認是snapshot方式,實現方法是定時將內存的快照(snapshot)持久化到硬盤,這種方法缺點是持久化之后如果出現crash則會丟失一段數據。因此在完美主義者的推動下作者增加了aof方式。aof即append only mode,在寫入內存數據的同時將操作命令保存到日志文件,在一個並發更改上萬的系統中,命令日志是一個非常龐大的數據,管理維護成本非常高,恢復重建時間會非常長,這樣導致失去aof高可用性本意。另外更重要的是Redis是一個內存數據結構模型,所有的優勢都是建立在對內存復雜數據結構高效的原子操作上,這樣就看出aof是一個非常不協調的部分。
其實aof目的主要是數據可靠性及高可用性.
Redis安裝
文章的最后我提供了下載包,當然你也可以去官網下載最新版本的Redis https://github.com/dmajkic/redis/downloads
將服務程序拷貝到一個磁盤上的目錄,如下圖:
文件說明:
redis-server.exe:服務程序
redis-check-dump.exe:本地數據庫檢查
redis-check-aof.exe:更新日志檢查
redis-benchmark.exe:性能測試,用以模擬同時由N個客戶端發送M個 SETs/GETs 查詢.
redis-cli.exe: 服務端開啟后,我們的客戶端就可以輸入各種命令測試了
1、打開一個cmd窗口,使用cd命令切換到指定目錄(F:\Redis)運行 redis-server.exe redis.conf
2、重新打開一個cmd窗口,使用cd命令切換到指定目錄(F:\Redis)運行 redis-cli.exe -h 127.0.0.1 -p 6379,其中 127.0.0.1是本地ip,6379是redis服務端的默認端口 (這樣可以開啟一個客戶端程序進行特殊指令的測試).
可以將此服務設置為windows系統服務,下載Redis服務安裝軟件,安裝即可。(https://github.com/rgl/redis/downloads )
如果你的電腦是64bit系統,可以下載redis-2.4.6-setup-64-bit.exe
安裝完成Redis服務后,我們會在計算機的服務里面看到
然后啟動此服務。
接下來在使用Redis時,還需要下載C#驅動(也就是C#開發庫),如下圖:
Redis常用數據類型
使用Redis,我們不用在面對功能單調的數據庫時,把精力放在如何把大象放進冰箱這樣的問題上,而是利用Redis靈活多變的數據結構和數據操作,為不同的大象構建不同的冰箱。
Redis最為常用的數據類型主要有以下五種:
- String
- Hash
- List
- Set
- Sorted set
String類型
String是最常用的一種數據類型,普通的key/value存儲都可以歸為此類 。一個Key對應一個Value,string類型是二進制安全的。Redis的string可以包含任何數據,比如jpg圖片(生成二進制)或者序列化的對象。基本操作如下:
var client = new RedisClient("127.0.0.1", 6379); client.Set<int>("pwd", 1111); int pwd=client.Get<int>("pwd"); Console.WriteLine(pwd); UserInfo userInfo = new UserInfo() { UserName = "zhangsan", UserPwd = "1111" };//</span>(底層使用json序列化 ) client.Set<UserInfo>("userInfo", userInfo); UserInfo user=client.Get<UserInfo>("userInfo"); Console.WriteLine(user.UserName); List<UserInfo> list = new List<UserInfo>() { new UserInfo(){UserName="lisi",UserPwd="111"},new UserInfo(){UserName="wangwu",UserPwd="123"} }; client.Set<List<UserInfo>>("list",list); List<UserInfo>userInfoList=client.Get<List<UserInfo>>("list"); foreach (UserInfo userInfo in userInfoList) { Console.WriteLine(userInfo.UserName); }
Hash類型
Hash是一個string 類型的field和value的映射表。hash特別適合存儲對象。相對於將對象的每個字段存成單個string 類型。一個對象存儲在hash類型中會占用更少的內存,並且可以更方便的存取整個對象。
作為一個key value存在,很多開發者自然的使用set/get方式來使用Redis,實際上這並不是最優化的使用方法。尤其在未啟用VM情況下,Redis全部數據需要放入內存,節約內存尤其重要.
增加了序列化/反序列化的開銷,並且在需要修改其中一項信息時,需要把整個對象取回
Redis為單進程單線程模式,采用隊列模式將並發訪問變為串行訪問。Redis本身沒有鎖的概念,Redis對於多個客戶端連接並不存在競爭 .. redis是個單線程的程序,為什么會這么快呢 ?
1、大量線程導致的線程切換開銷
2、鎖、
3、非必要的內存拷貝。
4. Redis多樣的數據結構,每種結構只做自己愛做的事.
Hash對應的Value內部實際就是一個HashMap,實際這里會有2種不同實現,這個HashMap的成員比較少時,Redis為了節省內存會采用類似一維數組的方式來緊湊存儲,而不會采用真正的HashMap結構,當成員量增大時會自動轉成真正的HashMap.
Key仍然是用戶ID, value是一個Map,這個Map的key是成員的屬性名,value是屬性值,這樣對數據的修改和存取都可以直接通過其內部Map的Key(Redis里稱內部Map的key為field), 也就是通過 key(用戶ID) + field(屬性標簽) 就可以操作對應屬性數據了,既不需要重復存儲數據,也不會帶來序列化和反序列化
client.SetEntryInHash("user", "userInfo", "aaaaaaaaaa"); List<string> list = client.GetHashKeys("user"); List<string> list = client.GetHashValues("userInfo");//獲取值 List<string> list = client.GetAllKeys();//獲取所有的key。
Redis為不同數據類型分別提供了一組參數來控制內存使用,我們在前面提到過的Redis Hash的value內部是一個
HashMap,如果該Map的成員比較少,則會采用一維數組的方式來緊湊存儲該MAP,省去了大量指針的內存開銷,這個參數在redis,conf配置文件中下面2項。
Hash-max-zipmap-entries 64
Hash-max-zipmap-value 512.
含義是當value這個Map內部不超過多少個成員時會采用線性緊湊格式存儲,默認是64,即value內部有64個以下的成員就是使用線性緊湊存儲,超過該值自動轉成真正的HashMap.
Hash-max-zipmap-value含義是當value這個MAP內部的每個成員值長度不超過多少字節就會采用線性緊湊存儲來節省空間。以上兩個條件任意一個條件超過設置值都會轉成真正的HashMap,也就不會再節省內存了,這個值設置多少需要權衡,HashMap的優勢就是查找和操作時間短。
一個key可對應多個field,一個field對應一個value
這里同時需要注意,Redis提供了接口(hgetall)可以直接取到全部的屬性數據,但是如果內部Map的成員很多,那么涉及到遍歷整個內部Map的操作,由於Redis單線程模型的緣故,這個遍歷操作可能會比較耗時,而另其它客戶端的請求完全不響應,這點需要格外注意
建議使用對象類別和ID構成鍵名,使用字段表示對象屬性,字
段值存儲屬性值,例如:car:2 price 500
List類型
list是一個鏈表結構,主要功能是push,pop,獲取一個范圍的所有的值等,操作中key理解為鏈表名字。 Redis的list類型其實就是一個每個子元素都是string類型的雙向鏈表。我們可以通過push,pop操作從鏈表的頭部或者尾部添加刪除元素,這樣list既可以作為棧,又可以作為隊列。Redis list的實現為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內存開銷,Redis內部的很多實現,包括發送緩沖隊列等也都是用的這個數據結構
//隊列使用 client.EnqueueItemOnList("name", "zhangsan"); client.EnqueueItemOnList("name", "lisi"); int count= client.GetListCount("name"); for (int i = 0; i < count; i++) { Console.WriteLine(client.DequeueItemFromList("name")); } //棧使用 client.PushItemToList("name2", "wangwu"); client.PushItemToList("name2", "maliu"); int count = client.GetListCount("name2"); for (int i = 0; i < count; i++) { Console.WriteLine(client.PopItemFromList("name2")); }
Set類型
它是string類型的無序集合。set是通過hash table實現的,添加,刪除和查找,對集合我們可以取並集,交集,差集.
//對Set類型進行操作 client.AddItemToSet("a3", "ddd"); client.AddItemToSet("a3", "ccc"); client.AddItemToSet("a3", "tttt"); client.AddItemToSet("a3", "sssh"); client.AddItemToSet("a3", "hhhh"); System.Collections.Generic.HashSet<string>hashset=client.GetAllItemsFromSet("a3"); foreach (string str in hashset) { Console.WriteLine(str); } //求並集 client.AddItemToSet("a3", "ddd"); client.AddItemToSet("a3", "ccc"); client.AddItemToSet("a3", "tttt"); client.AddItemToSet("a3", "sssh"); client.AddItemToSet("a3", "hhhh"); client.AddItemToSet("a4", "hhhh"); client.AddItemToSet("a4", "h777"); System.Collections.Generic.HashSet<string>hashset= client.GetUnionFromSets(new string[] { "a3","a4"}); foreach (string str in hashset) { Console.WriteLine(str); } //求交集 System.Collections.Generic.HashSet<string> hashset = client.GetIntersectFromSets(new string[] { “a3”, “a4” }); //求差集. System.Collections.Generic.HashSet<string> hashset = client.GetDifferencesFromSet("a3",new string[] { "a4"});
返回存在於第一個集合,但是不存在於其他集合的數據。差集
Sorted Set類型
sorted set 是set的一個升級版本,它在set的基礎上增加了一個順序的屬性,這一屬性在添加修改 .元素的時候可以指定,每次指定后,zset(表示有序集合)會自動重新按新的值調整順序。可以理解為有列的表,一列存 value,一列存順序。操作中key理解為zset的名字.
Redis sorted set的使用場景與set類似,區別是set不是自動有序的,而sorted set可以通過用戶額外提供一個優先級(score)的參數來為成員排序,並且是插入有序的,即自動排序。當你需要一個有序的並且不重復的集合列表,那么可以選擇sorted set數據結構,
client.AddItemToSortedSet("a5", "ffff"); client.AddItemToSortedSet("a5","bbbb"); client.AddItemToSortedSet("a5", "gggg"); client.AddItemToSortedSet("a5", "cccc"); client.AddItemToSortedSet("a5", "waaa"); System.Collections.Generic.List<string> list =client.GetAllItemsFromSortedSet("a5"); foreach (string str in list) { Console.WriteLine(str); }
給Redis設置密碼
安裝redis服務器后,不能讓其在外網環境下面裸奔啊,我們可以為其設置一個復雜的訪問密碼,最好20位以上,這樣可以有效防止別人暴力破解。
找到redis的配置文件,默認在這個地方:C:\Program Files\Redis\conf\redis.conf,查找requirepass選項配置
把這個選項前面的#注釋干掉,然后在后面添加一個復雜的密碼
# use a very strong password otherwise it will be very easy to break. # requirepass 2016@Test.88210_yujie # Command renaming.
帶密碼的訪問
var client = new RedisClient("192.168.2.154", 6379); //ip和端口 client.Password = "2016@Test.88210_yujie"; //redis訪問密碼
ServiceStack.Redis源碼:https://github.com/ServiceStack/ServiceStack.Redis
注:此文使用的redis C#驅動是1.0版本,目前最新版本為4.0,而且已經開源。
4.0版操作Demo如下:
/* ============================================================================== * 功能描述:ReidsDemo * 創 建 者:Zouqj * 創建日期:2016/4/14/11:40 * 更多redis相關技術請參考我的博文:http://www.cnblogs.com/jiekzou/p/4487356.html ==============================================================================*/ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; //添加如下引用 using ServiceStack.Redis; using ServiceStack.Redis.Generic; using System.Web.Script.Serialization; namespace RedisConsoleApplication1 { // 更多redis相關技術請參考我的博文:http://www.cnblogs.com/jiekzou/p/4487356.html class Program { #region static field static string host = "192.168.2.154";/*訪問host地址*/ static string password = "2016@Msd.1127_kjy";/*實例id:密碼*/ static readonly RedisClient client = new RedisClient(host, 6379, password); //static readonly RedisClient client = new RedisClient("xxxxx.m.cnsza.kvstore.aliyuncs.com", 6379, "dacb71347ad0409c:xxxx"); //49正式環境 static IRedisTypedClient<InStoreReceipt> redis = client.As<InStoreReceipt>(); #endregion static void Main(string[] args) { try { RedisTestApp(); } catch (Exception ex) { Console.WriteLine(ex.Message); } } #region redis Test public static void RedisTestApp() { StringTest(); //字符串測試 HashTest(); //Hash測試 ObjectTest(); //實體對象測試 SingleObjEnqueueTest(); //單個對象隊列測試 ListObjTest(); //對象列表測試 QueueTest(); //隊列和棧測試 Console.ReadKey(); } #region static method /// <summary> /// 隊列和棧測試 /// </summary> private static void QueueTest() { Console.WriteLine("*******************隊列 先進先出********************"); client.EnqueueItemOnList("test", "饒成龍");//入隊。 client.EnqueueItemOnList("test", "周文傑"); long length = client.GetListCount("test"); for (int i = 0; i < length; i++) { Console.WriteLine(client.DequeueItemFromList("test"));//出隊. } Console.WriteLine("*********************棧 先進后出*****************"); client.PushItemToList("name1", "鄒瓊俊");//入棧 client.PushItemToList("name1", "周文傑"); long length1 = client.GetListCount("name1"); for (int i = 0; i < length1; i++) { Console.WriteLine(client.PopItemFromList("name1"));//出棧. } Console.ReadKey(); } /// <summary> /// 單個對象隊列測試 /// </summary> private static void SingleObjEnqueueTest() { Console.WriteLine("******************實體對象隊列操作********************"); Student _stu = new Student { Name = "張三", Age = 21 }; JavaScriptSerializer json = new JavaScriptSerializer(); client.EnqueueItemOnList("stu", json.Serialize(_stu)); _stu = json.Deserialize<Student>(client.DequeueItemFromList("stu")); Console.WriteLine(string.Format("姓名:{0},年齡{1}", _stu.Name, _stu.Age)); Console.ReadKey(); } /// <summary> /// List對象測試 /// </summary> public static void ListObjTest() { List<InStoreReceipt> list = new List<InStoreReceipt>() { new InStoreReceipt() { IdentityID = 1, ReceiptStatus = 1, ReceiptTime = DateTime.Now, ReceiptMessage = "test1" }, new InStoreReceipt() { IdentityID = 2, ReceiptStatus = 1, ReceiptTime = DateTime.Now, ReceiptMessage = "test2" },new InStoreReceipt() { IdentityID = 3, ReceiptStatus = 1, ReceiptTime = DateTime.Now, ReceiptMessage = "test3" }}; AddInStoreInfo(list); var rList = redis.GetAllItemsFromList(redis.Lists["InStoreReceiptInfoList"]); rList.ForEach(v => Console.WriteLine(v.IdentityID + "," + v.ReceiptTime + "," + v.ReceiptMessage)); redis.RemoveAllFromList(redis.Lists["InStoreReceiptInfoList"]); Console.ReadKey(); } /// <summary> /// 實體對象測試 /// </summary> private static void ObjectTest() { Console.WriteLine("**************實體對象,單個,列表操作*****************"); UserInfo userInfo = new UserInfo() { UserName = "zhangsan", UserPwd = "1111" };//</span>(底層使用json序列化 ) client.Set<UserInfo>("userInfo", userInfo); UserInfo user = client.Get<UserInfo>("userInfo"); Console.WriteLine(user.UserName); //List<UserInfo> list = new List<UserInfo>() { new UserInfo() { UserName = "lisi", UserPwd = "222" }, new UserInfo() { UserName = "wangwu", UserPwd = "123" } }; //client.Set<List<UserInfo>>("list", list); List<UserInfo> userInfoList = client.Get<List<UserInfo>>("list"); userInfoList.ForEach(u => Console.WriteLine(u.UserName)); client.Remove("list"); Console.ReadKey(); } /// <summary> /// Hash測試 /// </summary> private static void HashTest() { Console.WriteLine("********************Hash*********************"); client.SetEntryInHash("userInfoId", "name", "zhangsan"); var lstKeys= client.GetHashKeys("userInfoId"); lstKeys.ForEach(k => Console.WriteLine(k)); var lstValues=client.GetHashValues("userInfoId"); lstValues.ForEach(v => Console.WriteLine(v)); client.Remove("userInfoId"); Console.ReadKey(); } /// <summary> /// 字符串測試 /// </summary> private static void StringTest() { #region 字符串類型 Console.WriteLine("*******************字符串類型*********************"); client.Set<string>("name", "zouqj"); string userName = client.Get<string>("name"); Console.WriteLine(userName); Console.ReadKey(); #endregion } /// <summary> /// 添加需要回執的進倉單信息到Redis /// </summary> /// <param name="lstRInStore">進倉單回執信息列表</param> private static void AddInStoreInfo(List<InStoreReceipt> inStoreReceipt) { IRedisList<InStoreReceipt> rlstRInStore = redis.Lists["InStoreReceiptInfoList"]; rlstRInStore.AddRange(inStoreReceipt); } #endregion #endregion } /// <summary> /// 進倉單回執信息(對應清關系統) /// </summary> public class InStoreReceipt { /// <summary> /// 主鍵ID /// </summary> public int IdentityID { get; set; } /// <summary> /// 回執狀態 /// </summary> public int ReceiptStatus { get; set; } /// <summary> /// 回執時間 /// </summary> public DateTime ReceiptTime { get; set; } /// <summary> /// 回執信息 /// </summary> public string ReceiptMessage { get; set; } } public class Student { /// <summary> /// 姓名 /// </summary> public string Name { get; set; } /// <summary> /// 年齡 /// </summary> public int Age { get; set; } } public class UserInfo { public string UserName { get; set; } public string UserPwd { get; set; } } }
RedisHelper類如下:
public static class RedisHelper { #region static field static string host = "192.168.2.154";/*訪問host地址*/ static string password = "2016@Msd.1127_kjy";/*實例id:密碼*/ static readonly RedisClient client = new RedisClient(host, 6379, password); #endregion #region /// <summary> /// 獲取信息 /// </summary> /// <typeparam name="T">對象類型</typeparam> /// <returns>對象</returns> public static T Get<T>(string token) where T : class,new() { return client.Get<T>(token); } /// <summary> /// 設置信息 /// </summary> /// <typeparam name="T">對象類型</typeparam> /// <param name="token">key</param> /// <param name="obj">對象</param> public static void Set<T>(string token,T obj) where T:class,new() { client.Set<T>(token, obj); } /// <summary> /// 設置指定Key的過期時間 /// </summary> /// <param name="token">具體的key值</param> /// <param name="seconds">過期時間,單位:秒</param> public static void Expire(string token,int seconds) { client.Expire(token, seconds); } #endregion }
特別說明:ServiceStack.Redis從4.0開始商用,有每小時6000次訪問的限制,如果要在生產環境使用,建議使用低版本。
Redis工具和所需資料代碼全下載,地址:http://pan.baidu.com/s/155F6A