Redis基礎教程

說明:本文中涉及的代碼是c#所寫,鏈接redis的第三方驅動爲ServiceStack.Redis。鏈接redis的客戶端軟件爲redis-desktop-managerhtml

1、Redis是什麼

Redis是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的APIweb

Redis是一種內存數據庫,是一種NoSQL數據庫。redis

簡單歸納:數據庫

1. 是一個徹底開源免費的key-value內存數據庫c#

2. 一般被認爲是一個數據結構服務器,主要是由於其有着豐富的數據結構 stringsmaplistsetssorted setswindows

NoSQL數據庫的四大分類緩存

鍵值(Key-Value)存儲數據庫安全

這一類數據庫主要會使用到一個哈希表,這個表中有一個特定的鍵和一個指針指向特定的數據。Key/value模型對於IT系統來講的優點在於簡單、易部署。可是若是DBA只對部分值進行查詢或更新的時候,Key/value就顯得效率低下了。 舉例如:Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB.服務器

列存儲數據庫。網絡

這部分數據庫一般是用來應對分佈式存儲的海量數據。鍵仍然存在,可是它們的特色是指向了多個列。這些列是由列家族來安排的。如:Cassandra, HBase, Riak.

文檔型數據庫

文檔型數據庫的靈感是來自於Lotus Notes辦公軟件的,並且它同第一種鍵值存儲相相似。該類型的數據模型是版本化的文檔,半結構化的文檔以特定的格式存儲,好比JSON。文檔型數據庫可 以看做是鍵值數據庫的升級版,容許之間嵌套鍵值。並且文檔型數據庫比鍵值數據庫的查詢效率更高。如:CouchDB, MongoDb. 國內也有文檔型數據庫SequoiaDB,已經開源。

圖形(Graph)數據庫

圖形結構的數據庫同其餘行列以及剛性結構的SQL數據庫不一樣,它是使用靈活的圖形模型,而且可以擴展到多個服務器上。NoSQL數據庫沒有標準的查詢語言(SQL),所以進行數據庫查詢須要制定數據模型。許多NoSQL數據庫都有REST式的數據接口或者查詢API。 如:Neo4J, InfoGrid, Infinite Graph.

所以,咱們總結NoSQL數據庫在如下的這幾種狀況下比較適用:

一、數據模型比較簡單;

二、須要靈活性更強的IT系統;

三、對數據庫性能要求較高;

四、不須要高度的數據一致性;

五、對於給定key,比較容易映射覆雜值的環境。

RedisMemcached

網絡IO模型方面:Memcached是多線程,分爲監聽線程、worker線程,引入鎖,帶來了性能損耗。Redis使用單線程的IO複用模型,將速度優點發揮到最大,也提供了較簡單的計算功能

內存管理方面:Memcached使用預分配的內存池的方式,帶來必定程度的空間浪費 而且在內存仍然有很大空間時,新的數據也可能會被剔除,而Redis使用現場申請內存的方式來存儲數據,不會剔除任何非臨時數據 Redis更適合做爲存儲而不是cache

數據的一致性方面:Memcached提供了cas命令來保證.Redis提供了事務的功能,能夠保證一串 命令的原子性,中間不會被任何操做打斷

存儲方式方面:Memcached只支持簡單的key-value存儲,不支持枚舉,不支持持久化和複製等功能

Redis與關係數據庫

redis目前還只能做爲小數據量存儲(所有數據可以加載在內存中) ,海量數據存儲方面並非redis所擅長的領域

設計、實現方法很不同.關係型數據庫經過表來存儲數據,經過SQL來查詢數據。而Redis通上述五種數據結構來存儲數據,經過命令來查詢數據

 

2、爲何要用Redis

1. 速度快:使用標準C寫,全部數據都在內存中完成,讀寫速度分別達到10/20

2. 持久化:對數據的更新採用Copy-on-write技術,能夠異步地保存到磁盤上,主要有兩種策略,一是根據時間,更新次數的快照(save 300 10 )二是基於語句追加方式(Append-only fileaof)

3.集中式緩存管理,統一緩存

4. 自動操做:對不一樣數據類型的操做都是自動的,很安全

5. 快速的主--從複製,官方提供了一個數據,Slave21秒即完成了對Amazon網站10G key set的複製。

6. Sharding技術: 很容易將數據分佈到多個Redis實例中,數據庫的擴展是個永恆的話題,在關係型數據庫中,主要是以添加硬件、以分區爲主要技術形式的縱向擴展解決了不少的應用場景,但隨着web2.0、移動互聯網、雲計算等應用的興起,這種擴展模式已經不太適合了,因此近年來,像採用主從配置、數據庫複製形式的,Sharding這種技術把負載分佈到多個特理節點上去的橫向擴展方式用處愈來愈多。

 

3、Redis的應用場景

1.取最新N個數據的操做

好比典型的取你網站的最新文章,經過下面方式,咱們能夠將最新的5000條評論的ID放在RedisList集合中,並將超出集合部分從數據庫獲取

使用LPUSH latest.comments<ID>命令,向list集合中插入數據

插入完成後再用LTRIM latest.comments 0 5000命令使其永遠只保存最近5000ID

若是你還有不一樣的篩選維度,好比某個分類的最新N條,那麼你能夠再建一個按此分類的List,只存ID的話,Redis是很是高效的。

2.排行榜應用,取TOP N操做

這個需求與上面需求的不一樣之處在於,前面操做以時間爲權重,這個是以某個條件爲權重,好比按頂的次數排序,這時候就須要咱們的sorted set出馬了,將你要排序的值設置成sorted setscore,將具體的數據設置成相應的value,每次只須要執行一條ZADD命令便可。

3.須要精準設定過時時間的應用

好比你能夠把上面說到的sorted setscore值設置成過時時間的時間戳,那麼就能夠簡單地經過過時時間排序,定時清除過時數據了,不只是清除Redis中的過時數據,你徹底能夠把Redis裏這個過時時間當成是對數據庫中數據的索引,用Redis來找出哪些數據須要過時刪除,而後再精準地從數據庫中刪除相應的記錄。

4.計數器應用

Redis的命令都是原子性的,你能夠輕鬆地利用INCRDECR命令來構建計數器系統。

5.Uniq操做,獲取某段時間全部數據排重值

這個使用Redisset數據結構最合適了,只須要不斷地將數據往set中扔就好了,set意爲集合,因此會自動排重。

6.實時系統,反垃圾系統

經過上面說到的set功能,你能夠知道一個終端用戶是否進行了某個操做,能夠找到其操做的集合並進行分析統計對比等。沒有作不到,只有想不到。

7.Pub/Sub構建實時消息系統

RedisPub/Sub系統能夠構建實時的消息系統,好比不少用Pub/Sub構建的實時聊天系統的例子。

8.構建隊列系統

使用list能夠構建隊列系統,使用sorted set甚至能夠構建有優先級的隊列系統。

9.緩存

這個沒必要說了,性能優於Memcached,數據結構更多樣化。

 

4、Redis示例

Redis主要支持類型

String

Hash

List

Set

Sorted set

public static T GetIfNullFunc<T>(string redisIP, int redisPort, string key, Func<T> func) where T : class, new()

        {

            using (RedisClient rclient = new RedisClient(redisIP, redisPort))

            {

                var cacheData = ser.Deserialize(rclient.Get<byte[]>(key)) as T;

                if (cacheData == null)

                {

                    cacheData = func();

                    if (cacheData != null)

                        rclient.Set<byte[]>(key, ser.Serialize(cacheData));

 

                }

                return (T)cacheData;

            }

        }

帶過時時間

public static T GetIfNullFunc<T>(string redisIP, int redisPort, int redisSeconds, string key, Func<T> func) where T : class, new()

        {

            using (RedisClient rclient = new RedisClient(redisIP, redisPort))

            {

                var cacheData = ser.Deserialize(rclient.Get<byte[]>(key)) as T;

                if (cacheData == null)

                {

                    cacheData = func();

                    if (cacheData != null)

                        rclient.Set<byte[]>(key, ser.Serialize(cacheData), TimeSpan.FromSeconds(redisSeconds));

                }

                return (T)cacheData;

            }

        }

key-value存取示例

 string cacheKey = string.Format("CNKI_OKMS_Category_Unit_{0}", unitId);

            return RedisHelper.GetIfNullFunc<List<Entity.CategoryInfo>>(CoreConfig.Instance.RedisIP, CoreConfig.Instance.RedisPort,

                CoreConfig.Instance.RedisSeconds, cacheKey, () =>

            {

                return _categoryDal.GetCategorys(unitId).ToList<Entity.CategoryInfo>();

            });

string

RedisClient rclient = new RedisClient("192.168.103.69", 6379);

            rclient.Set<int>("z1", 6);

            rclient.Set<string>("z2", "hello world");

            rclient.Set<double>("z3", 22.09);

            rclient.Set<int>("IpAccessCount", 0);

            //次數遞增

            rclient.Incr("IpAccessCount");

            Console.WriteLine(rclient.Get<int>("IpAccessCount"));

            Student z4 = RedisHelper.GetIfNullFunc<Student>("192.168.103.69", "z4", () =>

            {

                return new Student()

                {

                    Name = "zhangsan",

                    age = 22,

                    email = "zhangsan@123.com"

                };

            });

遠程查看內存數據庫(使用軟件redis-desktop-manager查看)

     

Hash

rclient.Remove("hash1");

    rclient.SetEntryInHash("hash1", "key1", "6.0");

    rclient.SetEntryInHash("hash1", "key2", "value2");

    rclient.SetEntryInHash("hash1", "key3", "value3");

    rclient.IncrementValueInHash("hash1", "key1", 2.0);

    List<string> keys = rclient.GetHashKeys("hash1");

    List<string> values =  rclient.GetHashValues("hash1");

    Dictionary<string, string> dic = rclient.GetAllEntriesFromHash("hash1");

List

應用:

實現最新消息排行等功能。  

 Lists的另外一個應用就是消息隊列,能夠利用ListsPUSH操做,將任務存在Lists中,而後工做線程再用POP操做將任務取出進行執行。

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));

#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

set

應用場景:  

Redis set對外提供的功能與list相似是一個列表的功能,特殊之處在於set是能夠自動排重的。

好比在微博應用中,每一個人的好友存在一個集合(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 + ","));

SortedSet

應用場景:

以某個條件爲權重,好比按項的次數排序.  

ZREVRANGE命令能夠用來按照得分來獲取前100名的用戶,ZRANK能夠用來獲取用戶排名,很是直接並且操做容易。  

Redis sorted set的使用場景與set相似,區別是set不是自動有序的,而sorted set能夠經過用戶額外提供一個優先級(score)的參數來爲成員排序,而且是插入有序的,即自動排序。  

好比:twitter public timeline能夠以發表時間做爲score來存儲,這樣獲取時就是自動按時間排好序的。  

好比:全班同窗成績的SortedSetsvalue能夠是同窗的學號,而score就能夠是其考試得分,這樣數據插入集合的,就已經進行了自然的排序。  

另外還能夠用Sorted Sets來作帶權重的隊列,好比普通消息的score1,重要消息的score2,而後工做線程能夠選擇按score的倒序來獲取工做任務。讓重要的任務優先執行。

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 + ","));

5、主從

     

過程: 數據寫到master-->master存儲到slaverdb-->slave加載rdb到內存。

存儲點(save point): 當網絡中斷了, 連上以後, 繼續傳.

Master-slave下第一次同步是全傳,後面是增量同步;

主(Master

若是用的是rediswindows服務,修改配置文件redis.windows-service.conf;若是用redis-server.exe在命令行啓動,修改配置文件redis.windows.conf

port 6379(能夠自行修改)

bind 192.168.103.69

其餘參數:

設置master密碼,slave鏈接master時須要輸入密碼

masterauth <master-password>

設置數據庫個數,默認寫入db0(第一個數據庫)

databases 16

保存數據到磁盤:

save 900 1   修改110key15分鐘後保存到磁盤

save 300 10    修改1010000key5分鐘後保存到磁盤

save 60 10000   修改10000key以上,1分鐘後保存到磁盤

 

1slave1)與master主機相同,端口不一樣

修改redis.windows.conf配置文件

port 6385

bind 192.168.103.69

slaveof 192.168.103.69 6379

2slave2

修改redis.windows-service.conf

port 6379

bind 192.168.25.246

slaveof 192.168.103.69 6379

 

先重啓master上的redis服務

而後重啓slave上的redis服務

master日誌:

[3424] 12 Aug 03:33:00.015 * Slave 192.168.25.246:6379 asks for synchronization

[3424] 12 Aug 03:33:00.016 * Full resync requested by slave 192.168.25.246:6379

[3424] 12 Aug 03:33:00.017 * Starting BGSAVE for SYNC with target: disk

[3424] 12 Aug 03:33:00.047 * Background saving started by pid 3408

[3424] 12 Aug 03:33:00.208 # fork operation complete

[3424] 12 Aug 03:33:00.385 * Background saving terminated with success

[3424] 12 Aug 03:33:00.388 * Synchronization with slave succeeded

 

slave2日誌:

[28016] 11 Aug 19:39:18.705 * Connecting to MASTER 192.168.103.69:6379

[28016] 11 Aug 19:39:18.705 * MASTER <-> SLAVE sync started

[28016] 11 Aug 19:39:18.707 * Non blocking connect for SYNC fired the event.

[28016] 11 Aug 19:39:18.716 * Master replied to PING, replication can continue...

[28016] 11 Aug 19:39:18.726 * Partial resynchronization not possible (no cached master)

[28016] 11 Aug 19:39:18.729 * Full resync from master: ab7f739c4ed4c62d4941a455e9f5706153efe1e8:1

[28016] 11 Aug 19:39:19.100 * MASTER <-> SLAVE sync: receiving 1086 bytes from master

[28016] 11 Aug 19:39:19.102 * MASTER <-> SLAVE sync: Flushing old data

[28016] 11 Aug 19:39:19.102 * MASTER <-> SLAVE sync: Loading DB in memory

[28016] 11 Aug 19:39:19.102 * MASTER <-> SLAVE sync: Finished with success

主從同步狀況查看

 

讀寫分離訪問

 private static string[] ReadWriteHosts = System.Configuration.ConfigurationSettings.AppSettings["readWriteHosts"].Split(new char[] { ';' });

       private static string[] ReadOnlyHosts = System.Configuration.ConfigurationSettings.AppSettings["readOnlyHosts"].Split(new char[] { ';' });

        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,

            });

        }

        /// <summary>

        /// 移除單體

        /// </summary>

        /// <param name="key"></param>

        public static bool Item_Remove(string key)

        {

            using (IRedisClient redis = prcm.GetClient())

            {

                return redis.Remove(key);

            }

        }

        public static int List_Count(string key)

        {

            using (IRedisClient redis = prcm.GetReadOnlyClient())

            {

                return redis.GetListCount(key);

            }

        }

日誌和慢查詢日誌

查詢日誌

loglevel notice

logfile "Logs/redis_log.txt"

慢查詢日誌

slowlog-log-slower-than 10000(單位:微秒)

slowlog-max-len 128  (隊列長度)

 

6、發佈訂閱(消息系統)

 

簡單pub_sub

 

 

7、消息隊列

RabbitMQ是使用Erlang編寫的一個開源的消息隊列,自己支持不少的協議:AMQPXMPP, SMTP, STOMP,也正是如此,使的它變的很是重量級,更適合於企業級的開發。我的認爲,在互聯網開發中,使用消息隊列,更多的由於在高併發環境下,因爲來不及同步處理,請求會發生堵塞,因此咱們須要一個隊列服務來進行異步的處理,在這種場景下,只要隊列服務知足最基本的Push/Pop已經足夠了。

Redis是一個Key-ValueNoSQL數據庫,開發維護很活躍,雖然它是一個Key-Value數據庫存儲系統,但它自己支持list數據結構的操做,因此徹底能夠當作一個輕量級的隊列服務來使用。

   

 

經驗與教訓

1. 要進行Master-slave配置,出現服務故障時能夠支持切換。

2. master側禁用數據持久化,只需在slave上配置數據持久化。

3. 物理內存+虛擬內存不足,這個時候dump一直死着,時間久了機器掛掉。這個狀況就是災難

4. Redis物理內存使用超過內存總容量的3/5時就會開始比較危險了,就開始作swap,內存碎片大

5. 當達到最大內存時,會清空帶有過時時間的key,即便key未到過時時間.

6. redisDB同步寫的問題,先寫DB,後寫redis,由於寫內存基本上沒有問題

 

參考資料

1.Redis bookRedis設計與實現) http://redisbook.readthedocs.org/en/latest/

2.Redis 官網 http://redis.io/

3.http://devres.zoomquiet.io/data/20110714104018/index.html

4.ServiceStack源碼

5.NServiceKit源碼

相關文章
相關標籤/搜索