緩存

一.Redishtml

    1.redis和memcached的區別java

    (1)memcached只能存儲string,而Redis能夠存儲string、list(對應Java中的Queue,linux

             有些公司直接用該類型作消息隊列)、set、zset、hash(對應Java中的Map)。redis

    (2)redis能夠持久化,而memcached不能夠算法

    (3)memcached能夠多線程的運行,而redis只能單線程(線程指的是網絡請求模塊數據庫

             使用了一個線程,其餘模塊用了多個線程).數組

    2.Redis是單線程的緩存

     (1)Redis單線程的含義安全

              即Redis是用"單線程-多路複用io模型"來實現高性能的內存數據服務器

              服務的,同一時刻只有一個操做(命令)在進行,因此,耗時

              的命令會致使併發的降低(讀併發和寫併發),例如:sunion。

              注意:

                     Redis的單線程是指在處理咱們的網絡請求的時候只有一個

              線程來處理,一個正式的Redis Server運行的時候確定是不止

              一個線程的,例如Redis進行持久化的時候會以子進程或者子線

              程的方式執行(具體是子線程仍是子進程待讀者深刻研究);

              在例如在測試服務器上查看Redis進程,而後找到該進程下的線

              程:

     (2)Redis是單線程的,那麼Redis爲何這麼快?

               主要有如下3個緣由:

             (a)徹底基於內存;

             (b)數據結構簡單,對數據操做也簡單;

             (c)使用多路 I/O 複用模型;

                             多路 I/O 複用模型是利用 select、poll、epoll 能夠同時監察多個流的 I/O 事件的

                      能力,在空閒的時候,會把當前線程阻塞掉,當有一個或多個流有 I/O 事件時,就從

                      阻塞態中喚醒,因而程序就會輪詢一遍全部的流(epoll是隻輪詢那些真正發出了事件

                      的流),而且只依次順序的處理就緒的流,這種作法就避免了大量的無用操做。

                             這裏「多路」指的是多個網絡鏈接,「複用」指的是複用同一個線程。採用多路 I/O

                      複用技術可讓單個線程高效的處理多個鏈接請求(儘可能減小網絡 IO 的時間消耗),

                      且 Redis 在內存中操做數據的速度很是快(內存內的操做不會成爲這裏的性能瓶頸),

                      主要以上兩點造就了Redis 具備很高的吞吐量。

                             順便說一句和Memcached 不一樣,Redis 並無直接使用Libevent,而是本身完成

                      了一個很是輕量級的對 select、epoll、evport、kqueue 這些通用的接口的實現。在不

                      同的系統調用選用適合的接口,linux 下默認是epoll。由於Libevent 比較重,更通用,

                      代碼量也就很龐大,擁有不少Redis 用不上的功能,Redis 爲了追求「輕巧」而且去除依

                      賴,就選擇本身去封裝了一套。

    2.redis的持久化方式

    (1)rdb:在指定的時間間隔內生成數據集的時間點快照。Rdb保存默認文件名是

                      dump.rdb文件。該策略是redis默認的持久劃方式。不一樣的版本的Redis,

                      Rdb文件是不一樣的。

             執行過程:實際操做過程是fork一個子進程,先將數據集寫入臨時文件,寫入成

                               功後,再替換以前的文件,用二進制壓縮存儲。

                               save:執行時阻塞,服務不可用

                               bgsave:fork時阻塞,大部分時間服務時可用的。

            觸發時機:

                   --  從節點進行全量複製操做

                   --  degug reload命令

                   --  shut down命令,若是沒有開啓aof則自動執行bgsave(其實它是一個可選

                       項)

            實現原理

                   經過定時器,該定時器每100毫秒就會執行一次

                   --  定時器每次執行時判斷「當前時間-上次執行rdb備份的時間」是否>「咱們指定的時

                       間」,若是大於,則執行rdb備份。

                   --  判斷更新次數是否大於指定修改次數

                   --  若是同時知足上面兩個條件,則生成rdb文件。

                   --  能夠使用命令info persistence查看持久化信息。

                  

    (2)aof: 持久化記錄服務器執行的全部寫操做命令,並在服務器啓動時,經過從新

                       執行這些命令來還原數據集。Aof持久化時文件的格式:.aof文件。

             執行過程:AOF持久化以日誌的形式記錄服務器所處理的每個寫、刪除操做,

             查詢操做不會記錄, 以文本的方式記錄,能夠打開文件看到詳細的操做記錄。

             開啓 aof :appendonly yes  (默認是 no)。

             注:redis寫的時候不直接寫磁盤而是寫到緩衝區,在由緩衝區刷如磁盤

                    有三種刷盤策略:

                     --  always:每次有新命令時,就將緩衝區數據寫。性能會比較差,互聯網公司通常不用

                     --  everysec:默認,每秒將緩衝區的數據寫入並同步到AOF文件入並同步到AOF文件。

                                           服務器忽然宕機會丟失一秒的數據,擎天說即便數據丟失了,redis還提供

                                           數據恢復的命令,這是通常使用的。

                     --  no:將緩衝區數據寫入AOF文件,可是同步操做到交給操做系統來處理。性能比較好,可是

                                 實時性是最差的。

             Aof的重寫:aof 文件持續增加而大時,會 fork 出一條新進程來將文件重寫(也就

                                 是先寫臨時文件最後再 rename),遍歷新進程的內存中的數據,每

                                 條記錄有一條set語句,重寫 aof 文件的操做,並無讀取舊的的

                                 aof 文件,而是將整個內存的數據庫內容用命令的方式重寫了一個新

                                 的 aof 文件。fork進程時同樣阻塞。Aof文件能夠用於不一樣版本的Redis,

                                 這點不一樣於RDB。

          重寫可以壓縮Aof體積的緣由:

          

             Aof文件重寫的觸發機制:

                   (a) redis 會記錄上次重寫的 aof 的大小,默認的配置當 aof 文件大小上次

                             重寫後大小的一倍且文件大於 64M觸發(3G)。

                   (b) 調用BGREWRITEAOF手動觸發

    (4)redis能夠同時開啓Aof和Rdb

              RDB與AOF同時開啓  默認先加載AOF的配置文件。

              二者都開啓的建議:RDB數據不實時,同時使用二者時服務器只會找AOF文件,

                                               可不能夠只使用AOF?建議不要,由於RDB更適合備份數據

                                               庫(AOF在不斷變化,很差備份),快速重啓,並且不會又AOF

                                               可能潛在的BUG,留做萬一的手段。

    (5)Aof和Rdb二者優缺點:

             rdb:性能比較好,尤爲大數據集的時候效率高一些;

             aof:效率比較低可是一致性作的比較好

                   (a) redis 會記錄上次重寫的 aof 的大小,默認的配置當 aof 文件大小上次

                             rewrite後大小的一倍且文件大於 64M觸發(3G)。

                   (b) 調用BGREWRITEAOF手動觸發

    (4)redis能夠同時開啓Aof和Rdb

              RDB與AOF同時開啓  默認先加載AOF的配置文件。

              二者都開啓的建議:RDB數據不實時,同時使用二者時服務器只會找AOF文件,

                                               可不能夠只使用AOF?建議不要,由於RDB更適合備份數據

                                               庫(AOF在不斷變化,很差備份),快速重啓,並且不會又AOF

                                               可能潛在的BUG,留做萬一的手段。

    (5)Aof和Rdb二者優缺點:

             rdb:性能比較好,尤爲大數據集的時候效率高一些;

             aof:效率比較低可是一致性作的比較好

    3.Redis緩存穿透、緩存擊穿、緩存雪崩

     (1)緩存穿透(大量訪問不存在的key)

            (a)什麼是緩存穿透

                     是指查詢一個必定不存在的key時,因爲緩存不命中因此去數據庫查詢,但

                     是數據庫依然查詢不到這個結果,因此該數據不會寫入緩存,這就致使這個

                     不存在的數據每次都要到數據庫去查詢。在高併發大流量時,可能DB就掛

                     掉了。若是有人頻繁的利用不存在的key進行攻擊,這就是系統漏洞。

            (b)解決方案

                   (i)布隆過濾器(經常使用)

                           將全部可能存在的數據哈希到一個足夠大的bitmap中,一個必定不存在

                           的數據會被這個bitmap攔截掉(一個key若是必定不存在布隆過濾器中,

                           那麼它必定不存在redis中,若是它存在布隆過濾器中,那麼它有可能存

                           在也有可能不存在Redis中。),從而避免了對底層存儲系統的查詢壓

                           力。另外也有一個更爲簡單粗暴的方法(咱們採用的就是這種),若是

                           一個查詢返回的數據爲空(無論是數據不存在,仍是系統故障),咱們

                           仍然把這個空結果進行緩存,但它的過時時間會很短,最長不超過五分

                           鍾。

                           關於布隆過濾器的介紹:

                           布隆過濾器簡介:

                           布隆過濾器是一種多哈希函數映射的快速查找算法。它能夠判斷出某個

                           元素確定不在集合裏或者可能在集合裏,即它不會漏報,但可能會誤報

                         (一個key若是必定不存在布隆過濾器中,

                           那麼它必定不存在redis中,若是它存在布隆過濾器中,那麼它有可能存

                           在也有可能不存在Redis中)。一般應用在一些須要快速判斷某個元素是

                           否屬於集合,但不嚴格要求100%正確的場合。

                           布隆過濾器介紹

                                         布隆過濾器是一種多哈希函數映射的快速查找算法。它可

                                  以判斷出某個元素確定不在集合裏或者可能在集合裏,即它不

                                  會漏報,但可能會誤報。一般應用在一些須要快速判斷某個元

                                  素是否屬於集合,但不嚴格要求100%正確的場合。

                                         一個空的布隆過濾器是一個m位的位數組,全部位的值都

                                  爲0。定義了k個不一樣的符合均勻隨機分佈的哈希函數,每一個函

                                  數把集合元素映射到位數組的m位中的某一位。

                                  * 布隆過濾器原理

                                    布隆過濾器有點像HashMap,一個空的布隆過濾器是一個m

                                    位的位數組,全部位的值都爲0。定義了k個不一樣的符合均勻

                                    隨機分佈的哈希函數,每一個函數把集合元素映射到位數組的

                                    m位中的某一位。布隆過濾器是一個 bit 向量或者說bit 數組,

                                    長這樣:

​                             

                                    增長元素和查詢元素

                                           何爲增長一個元素?

                                                   先把這個元素做爲k個哈希函數的輸入,拿到k個數組

                                                   位置,而後把全部的這些位置置爲1。

                                           何爲查詢一個元素?

                                                   把這個元素做爲k個哈希函數的輸入,獲得k個數組位

                                                   置。這些位置中只要有任意一個是0,元素確定不在

                                                   這個集合裏。若是元素在集合裏,那麼這些位置在插

                                                   入這個元素時都被置爲1了。若是這些位置都是1,那

                                                   麼要麼元素在集合裏,要麼全部這些位置是在其餘元

                                                   素插入過程當中被偶然置爲1了,致使了一次「誤報」。

                                           增長一個元素和查詢一個元素詳細說明

                                                  若是咱們要映射一個值到布隆過濾器中,咱們須要使

                                                  用多個不一樣的哈希函數生成多個哈希值,並對每一個生

                                                  成的哈希值指向的 bit 位置 1,例如針對值 「baidu」 和

                                                  三個不一樣的哈希函數分別生成了哈希值 一、四、7,則

                                                  上圖轉變爲:

​                                                

                                                  Ok,咱們如今再存一個值 「tencent」,若是哈希函數

                                                  返回三、四、8 的話,圖繼續變爲:

​                                               

                                                  值得注意的是,4 這個 bit 位因爲兩個值的哈希函數

                                                  都返回了這個 bit 位,所以它被覆蓋了。如今咱們如

                                                  果想查詢「dianping」 這個值是否存在,哈希函數返回

                                                  了 一、五、8三個值,結果咱們發現 5 這個 bit 位上的

                                                  值爲 0,說明沒有任何一個值映射到這個 bit 位上,

                                                  所以咱們能夠很肯定地說 「dianping」 這個值不存在。

                                                  而當咱們須要查詢 「baidu」這個值是否存在的話,那

                                                  麼哈希函數必然會返回 一、四、7,而後咱們檢查發現

                                                  這三個 bit 位上的值均爲 1,那麼咱們能夠說 「baidu」

                                                  存在了麼?答案是不能夠,只能是 「baidu」這個值可

                                                  能存在。

                                                          這是爲何呢?答案很簡單,由於隨着增長的

                                                  值愈來愈多,被置爲 1 的 bit 位也會愈來愈多,這樣

                                                  某個值「taobao」 即便沒有被存儲過,可是萬一哈希

                                                  函數返回的三個 bit 位都被其餘值置位了 1 ,那麼程

                                                  序仍是會判斷「taobao」 這個值存在。

                                           誤判率

                                                  布隆過濾器只會漏判不會誤判

                                                  誤判率就是在插入n個元素後,某元素被判斷爲「可能

                                                  在集合裏」,但實際不在集合裏的機率,此時這個元素

                                                  哈希以後的k個比特位置都被置爲1。

                                                         這裏對誤判率只作了簡單的概述,若是工做中用

                                                  到了誤判率,請看博客:

                                                  https://www.cnblogs.com/Jack47/p/bloom_filter_intro.html

                                                  中的誤判率一節。

                                    刪除元素

                                           布隆過濾器能夠支持 add 和 isExist 操做,可是不支持

                                           delete 操做能夠麼。例如上圖中的bit 位 4 被兩個值共同

                                           覆蓋的話,一旦你刪除其中一個值例如 「tencent」 而將其

                                           置位 0,那麼下次判斷另外一個值例如 「baidu」 是否存在的

                                           話,會直接返回 false,而實際上你並無刪除它。

                                                  那麼如何解決這個問題,答案是計數刪除。可是計

                                           數刪除須要存儲一個數值,而不是原先的 bit 位,會增大

                                           佔用的內存大小。這樣的話,增長一個值就是將對應索

                                           引槽上存儲的值加一,刪除則是減一,判斷是否存在則

                                           是看值是否大於0。

                                  * 如何選擇哈希函數個數和布隆過濾器長度

                                    很顯然,太小的布隆過濾器很快全部的 bit 位均爲 1,那麼查

                                    詢任何值都會返回「可能存在」,起不到過濾的目的了。布隆過

                                    濾器的長度會直接影響誤報率,布隆過濾器越長其誤報率越

                                    小。另外,哈希函數的個數也須要權衡,個數越多則布隆過

                                    濾器 bit 位置位 1 的速度越快,且布隆過濾器的效率越低;

                                    可是若是太少的話,那咱們的誤報率會變高。

                               

                                    k 爲哈希函數個數,m 爲布隆過濾器長度,n 爲插入的元素個

                                    數,p 爲誤報率。至於如何推導這個公式,我在知乎發佈的

                                    文章有涉及,感興趣能夠看看,不感興趣的話記住上面這個

                                    公式就好了。

                                  * 實踐

                                    常見的適用常見有,利用布隆過濾器減小磁盤 IO 或者網絡

                                    請求,由於一旦一個值一定不存在的話,咱們能夠不用進

                                    行後續昂貴的查詢請求。另外,既然你使用布隆過濾器來

                                    加速查找和判斷是否存在,那麼性能很低的哈希函數不是

                                    個好選擇,推薦 MurmurHash、Fnv 這些。

                                  * 大Value拆分

                                    Redis 因其支持 setbit 和 getbit 操做,且純內存性能高等特

                                    點,所以自然就能夠做爲布隆過濾器來使用。可是布隆過濾

                                    器的不當使用極易產生大 Value,增長 Redis 阻塞風險,因

                                    今生成環境中建議對體積龐大的布隆過濾器進行拆分。拆分

                                    的形式方法多種多樣,可是本質是不要將 Hash(Key) 以後

                                    的請求分散在多個節點的多個小 bitmap 上,而是應該拆分

                                    成多個小 bitmap 以後,對一個 Key 的全部哈希函數都落在

                                    這一個小 bitmap 上。

                                    布隆過濾器參考文章:

                                    https://www.jianshu.com/p/2104d11ee0a2

                                    https://www.cnblogs.com/Jack47/p/bloom_filter_intro.html

                  

    (2)緩存擊穿(熱點key過時)

            (a)什麼是緩存擊穿

                     對於一些設置了過時時間的key,若是這些key可能會在某些時間點被超高並

                     發地訪問,是一種很是「熱點」的數據。而剛好這些熱點key緩存過時,這個時

                     候大量的請求會訪問數據庫,這就是緩存擊穿。     

            (b)解決方案

                   (i)使用互斥鎖

                           在根據key得到的value值爲空時,先鎖上,再從數據庫加載,加載完畢,

                           釋放鎖。若其餘線程發現獲取鎖失敗(此處的邏輯是,該線程先去查緩

                           存,發現爲空,那麼再去獲取鎖,但獲取不到鎖),則睡眠50ms後重

                           試。至於鎖的類型,單機環境用併發包的Lock類型就行,集羣環境則使

                           用分佈式鎖。

                   (ii)「提早」使用過時鎖(這個方案不對,若是這些key一直沒被訪問,過時了

                            仍是會形成緩存擊穿)

                           在value內部設置1個超時值(timeout1), 該超時值比實際的超時值

                         (timeout2)小。當從緩存讀取到timeout1發現它已通過期時候,立刻延

                           長timeout1並從新設置到緩存。而後再從數據庫加載數據並設置到緩存

                           中。

                   (iii)設置永不過時

                             設置Redis的key永不過時,這就保證了熱點key不會過時。把過時時間

                             存在key對應的value裏,若是發現要過時了,經過一個後臺的異步線

                             程進行緩存的構建(例如,該方案redis本身維護一個timeout,

                             當timeout小於System.currentTimeMillis())。 從實戰看,這種方法對於

                             性能很是友好,惟一不足的就是構建緩存時候,其他線程(非構建緩存的

                             線程)可能訪問的是老數據,可是對於通常的互聯網功能來講這個仍是可

                             以忍受。

                   (iv)採用netflix的hystrix,能夠作資源的隔離保護主線程池,若是把這個應

                            用到緩存的構建也何嘗不可,可是這隻適合於java應用。

     (3)緩存雪崩(緩存同時失效)

            (a)什麼是緩存雪崩

                     緩存雪崩是指在咱們設置緩存時採用了相同的過時時間,致使緩存在某一時

                     刻同時失效,請求所有轉發到DB,DB瞬時壓力太重雪崩。

            (b)解決方案

                     多數系統設計者考慮用加鎖或者隊列的方式保證緩存的單線 程(進程)寫,

                     從而避免失效時大量的併發請求落到底層存儲系統上。這裏分享一個簡單

                     方案就是講緩存失效時間分散開,好比咱們能夠在原有的失效時間基礎上增

                     加一個隨機值,好比1-5分鐘隨機,這樣每個緩存的過時時間的重複率就

                     會下降,就很難引起集體失效的事件。

    4.  redis的集羣

         本節內容來自官方文檔:

         http://www.redis.cn/topics/cluster-tutorial.html

       (1)Redis集羣的優勢

                --  自動分割數據到不一樣的節點上。

                --  整個集羣的部分節點失敗或者不可達的狀況下可以繼續處理命令

       (2)Redis集羣是如何分片的

                Redis 集羣有16384個哈希槽,每一個key經過CRC16校驗後對16384取模來決定放置哪一個槽。

                集羣的每一個節點負責一部分hash槽。舉個例子,好比當前集羣有3個節點,那麼:

                --  節點 A 包含 0 到 5500號哈希槽。

                --  節點 B 包含5501 到 11000 號哈希槽。

                --  節點 C 包含11001 到 16384號哈希槽。

                這種結構很容易添加或者刪除節點,好比若是我想新添加個節點D, 我須要從節點 A, B, C中

                得部分槽到D上; 若是我想移除節點A,須要將A中的槽移到B和C節點上,而後將沒有任何

                槽的A節點從集羣中移除便可。因爲從一個節點將哈希槽移動到另外一個節點並不會中止服務,

                因此不管添加刪除或者改變某個節點的哈希槽的數量都不會形成集羣不可用的狀態.

       (3)Redis集羣數據一致性保證以及它在的兩個缺陷(會丟失少許數據)

                Redis 並不能保證數據的強一致性。這意味這在實際中集羣在特定的條件下可能會丟失寫操做

                第一個緣由是由於集羣是用了異步複製. 寫操做過程:

                --  客戶端向主節點B寫入一條命令.

                --  主節點B向客戶端回覆命令狀態.

                --  主節點將寫操做複製給他得從節點 B1, B2 和 B3.

                      主節點對命令的複製工做發生在返回命令回覆以後, 由於若是每次處理命令請求都須要等

               待複製操做完成的話, 那麼主節點處理命令請求的速度將極大地下降 —— 咱們必須在性能和

               一致性之間作出權衡。 注意:Redis 集羣可能會在未來提供同步寫的方法。 Redis 集羣另一

               種可能會丟失命令的狀況是集羣出現了網絡分區, 而且一個客戶端與至少包括一個主節點在內

               的少數實例被孤立

                      舉個例子,假設集羣包含 A 、 B 、 C 、 A1 、 B1 、 C1 六個節點, 其中 A 、B 、C 爲主

               節點, A1 、B1 、C1 爲A,B,C的從節點, 還有一個客戶端 Z1 假設集羣中發生網絡分區,

                那麼集羣可能會分爲兩方,大部分的一方包含節點 A 、C 、A1 、B1 和 C1 ,小部分的一方則

                包含節點 B 和客戶端 Z1 Z1仍然可以向主節點B中寫入,若是網絡分區發生時間較短,那麼集羣將

                會繼續正常運做,若是分區的時間足夠讓大部分的一方將B1選舉爲新的master,那麼Z1寫入B中

                得數據便丟失。

                :redis是線程安全的,可是Redis集羣不是線程安全的

    5.  Redis分區算法

         :redis是線程安全的,可是Redis集羣不是線程安全的

                Redis分區算法:

                     (1)範圍分區:就是將一個範圍內的key都映射到同一個Redis實例中,具體作

                                                法以下:咱們能夠將用戶ID從0到10000的用戶數據映射到R0實

                                                例,而將用戶ID從10001到20000的對象映射到R1實例,依次類

                                                推。

                     (2)hash分區:常見的hash分區有 「節點取餘分區」、「一致性哈希分區」、「虛擬槽

                                                分區」。

                       還有redis集羣配合文章:

                        https://blog.csdn.net/sanpo/article/details/52839044

                    (3)redis cluster

​                       

    5.  Redis的主從複製

       (1)概念

                       是指將一臺Redis服務器的數據,複製到其餘的Redis服務器。前者稱爲主服務器

             (master)後者稱爲從服務器);數據的複製是單向的,只能由主服務器到從服務器。

               注:默認況下,每臺Redis服務器都是主服務器;一個主服務器能夠有多個從服務器(

                      或沒有從服務器),但一個從服務器只能有一個主服務器。

       (2)主從複製的做用

                --  數據冗餘

                           主從複製實現了數據的熱備份,是持久化以外的一種數據冗餘方式。

                --  故障恢復

                           當主服務器出現問題時,能夠由從服務器提供服務,實現快速的故障恢復;實

                           際上是一種服務的冗餘。

                --  負載均衡

                           在主從複製的基礎上,配合讀寫分離,能夠由主服務器提供寫服務,由從服務

                           器提供讀服務(即寫Redis數據時應用鏈接主服務器,讀Redis數據時應用鏈接

                           從服務器),分擔服務器負載;尤爲是在讀多寫少的場景下,經過多個從服務

                           器分擔讀負載,能夠大大提升Redis服務器的併發量。

                --  高可用基石

                            除了上術做用之外,主從複製仍是哨兵桕集羣可以實施的基礎,因仳說主從復

                            制是Redis高可用的基

       (3)配置主從的方式

                Redis配置主從,有兩種方式,一種是經過命令,一種是經過redis.config

                配置完以後,能夠使用命令 info replication去查看主從複製狀態

       (4)三種拓撲結構

              (a)一主一從

                       從節點只是作備份

              (b)星形結構

                       

 

                       通常用來分擔主節點上的讀寫的壓力。

                              對於用於讀多寫少的場景,能夠把讀命令發送到從節點來分擔主節點壓力。

                       如:keys命令,能夠在其中一臺從節點上執行,防止慢查詢對主節點形成阻塞從

                       而影響線上服務的穩定性。

                              對於寫多讀少的場景,多個從節點會致使主節點寫命令的屢次發送從而過分

                       消耗網絡帶寬,同時也加劇了主節點的負載影響服務穩定性。

              (c)樹狀結構

                       

                      

                              從節點不但能夠複製主節點數據,同時能夠做爲其餘從節點的主節點繼續向

                       下層複製。經過這種結構能夠有效下降主節點負載和須要傳送給從節點的數據量

        

         其原理以下:

                當一個從數據庫啓動後,會向主數據庫發送SYNC命令。同時主數據庫接收到SYNC

                命令後會開始在後臺保存快照(即RDB持久化的過程),並將保存快照期間接收到

                的命令緩存起來。當快照完成後Redis會將快照文件和全部緩存的命令發送給從數據

                庫,從數據庫收到後,會載入快照文件並執行收到的緩存的命令。以上過程稱爲復

                制初始化。複製初始化結束後,主數據庫每當收到寫命令時就會將命令同步給從數

                據庫,從而保證主從數據庫數據一致。

                       當主從數據庫之間的鏈接斷開重連後,Redis2.6以及以前的版本會從新進行復制

                初始化(即主數據庫從新保存快照並傳送給從數據庫),即便從數據庫能夠僅有幾條

                命令沒有收到,主數據庫也必需要將數據庫裏的全部數據從新傳送給從數據庫。這使

                得主從數據庫斷線重連後的數據恢復過程效率很低下在網絡環境很差的時候這一問題

                尤爲明顯。Redis2.8版的一個重要改進就是斷線重連可以支持有條件的增量數據傳輸,

                當從數據庫從新鏈接上主數據庫後,主數據庫只須要將斷線期間執行的命令傳送給從

                數據庫,從而大大提升Redis複製的實用性。8.17節會詳細介紹增量複製的實現原

                理以及應用條件。

                主從複製和哨兵的區別:

                       主從是集羣方式而哨兵只是提供了監控和自動選主的機制,並非集羣方式

   6.  redis的高可用

              一個哨兵能夠監控多個主從系統,多個哨兵能夠監控一個主從系統

     (1)哨兵系統主要有三個任務

            (a)監控

                            哨兵(sentinel) 會不斷地檢查你的Master和Slave是否運做正常

            (b)從新選主(或叫自動故障遷移)

                            master數據庫出現故障時,能夠自動經過投票機制,從slave節點中選舉新的

                     master,實現將從數據庫轉換爲主數據庫的自動切換。

            (b)提醒

                            當被監控的某個 Redis出現問題時, 哨兵(sentinel) 能夠經過 API 向管理員或者

                     其餘應用程序發送通知。

       (2)哨兵節點要部署在不一樣的物理機上,至少三個哨兵節點(必定是奇數)

       (3)哨兵的是實現原理

                       哨兵啓動後,會與要監控的主數據庫創建兩條鏈接(這兩個鏈接的創建方式與普

                通的Redis客戶端無異)。其中一條鏈接用來訂閱該主數據的_sentine_:helllo頻道,

                以獲取其餘一樣監控該數據庫的哨兵節點的信息;另一條鏈接時哨兵也須要按期向

                主數據庫發送INFO等命令來獲取主數據庫自己的信息(由於4.4.4節介紹過當客戶

                端的鏈接進入訂閱模式時就不能再執行其餘命令了,因此這時哨兵會使用另一條連

                接來發送這些命令)。   

                       連接創建完成以後,哨兵會定時執行以下幾個操做:

                       --  每10秒哨兵會向主數據庫和從數據庫發送INFO命令。

                                  哨兵經過發送INFO命令來得到當前數據庫的相關信息(包括運行ID、複製信

                           息等)從而實現新節點的自動發現(前面說配置哨兵監控Redis主從系統時只須要

                           指定主數據庫的信息便可,由於哨兵正是藉助工INFO命令來獲取全部複製該主數

                           據庫的從數據庫信息的)。啓動後,哨兵向主數據庫發送工INFO命令,經過解析

                           返回結果來得知從數據庫列表,然後對每一個從數據庫一樣創建兩個鏈接,這兩個

                           鏈接的做用和前文介紹的與主數據庫創建的兩個鏈接徹底一致。在此以後,哨兵

                           會每10秒定時向已知的全部主從數據庫發送INFO命令來獲取信息更新並進行相應

                           操做,好比對新增的從數據庫創建鏈接並加入監控列表,對主從數據庫的角色變

                           化(由故障恢復操做引發)進行信息更新等。

                       --  每2秒哨兵會向主數據庫和從數據庫的sentinel:hello頻道發送本身的信息。

                                   哨兵向主從數據庫的sentinel:hello頻道發送信息來與一樣監控該數據庫的哨

                            兵分享本身的信息。主要包括的哨兵的基本信息,以及其監控的主數據庫的信息。

                            其具體發送的消息內容爲:

                            

                                   哨兵會訂閱每一個其監控的數據庫的_sentinel_:hello頻道,當其餘哨兵收到消

                            息後,會判斷髮消息的哨兵是否是新發現的哨兵。若是是則將其加入己發現的哨

                            兵列表中並建立一個到其的鏈接(與數據庫不一樣,哨兵與哨兵之間只會建立一條

                            鏈接用來發送ping命令,而不須要建立另一條鏈接來訂閱頻道,由於哨兵只需

                            要訂閱數據庫的頻道便可實現自動發現其餘哨兵)。同時哨兵會判斷信息中主數

                            據庫的配置版本,若是該版本比當前記錄的主數據庫的版本高,則更新主數據庫

                            的數據。配置版本的做用會在後面詳細介紹。

                       --  每1秒哨兵會向主數據庫、從數據庫和其餘哨兵節點發送PING命令。

                                  實現了自動發現從數據庫和其餘哨兵節點後,哨兵要作的就是定時監控這些

                            數據庫和節點有沒有中止服務。這是經過每隔必定時間向這些節點發送PING命

                            令實現的(時間間隔與down—after—milliseconds選項指定時間有關,當

                            down—after—milliseconds的值小於1秒時,哨兵會每隔

                            down—after—milliseconds指定的時間發送一次PING命令,當

                            down-after-milliseconds的值大於1秒時,哨兵會每隔1秒發送一次PING命令。

                                   當超過down-after-milliseconds選項指定時間後若是被PING的數據庫或節

                            點仍然未進行回覆,則哨兵認爲其主觀下線。主觀下線表示當前的哨兵進程

                            看來,該節點已經下線。若是該節點是主數據庫,則哨兵會進一步判斷是否需

                            要對其進行故障恢復(哨兵發送SENTINEL  is-master-down-by-addr命令詢問

                            其餘哨兵節點以瞭解他們是否也認爲該主數據庫主觀下線,若是達到指定數量

                            時,哨兵會認爲其客觀下線,並選舉領頭的哨兵節點對主從系統發起故障恢

                            復。這個指定數量即爲前文介紹的quorum參數)。

                            關於領頭哨兵選舉:

                                   故障恢復須要由領頭的哨兵來完成,這樣能夠保證同一時間只有一個哨兵節

                                   點來執行故障恢復。選舉領頭哨兵的過程使用了Raft算法,具體過程以下:

                                   *  發現主數據庫客觀下線的哨兵節點(下面稱做A)向每一個哨兵節點發送命

                                        令,要求對方選本身成爲領頭哨兵。

                                   *  若是目標哨兵節點沒有選過其餘人,則會贊成將A設置成領頭哨兵。

                                   *  若是A發現有超過半數且超過quorum參數值的哨兵節點贊成選本身成爲

                                      領頭哨兵,則A成功成爲領頭哨兵。

                                   *  當有多個哨兵節點同時參選領頭哨兵,則會出現沒有任何節點當選的可

                                      能。此時每一個參選節點將等待一個隨機時間從新發起參選請求,進行下一

                                      輪選舉,直到選舉成功。

                            選舉出領頭哨兵後,哨兵開始對主數據庫進行故障恢復,故障恢復的過程以下:

                                   *  全部在線的從數據庫中,選擇優先級最高的從數據庫。優先級能夠經過

                                      slave-priority選項來設置。

                                   *  若是有多個最高優先級的從數據庫,則複製的命令偏移量(見8.1.7節)

                                      越大(即複製越完整)越優先。

                                   *  若是以上條件都同樣,則選擇運行ID較小的從數據庫。

                       以上三個操做貫穿哨兵進程的整個生命週期中,很是重要,能夠說了解了這3個操

                       做的意義就可以瞭解哨兵工做原理的一半內容了。下面分別詳細介紹。           

    7. redis與數據庫的數據一致性

      (1)數據庫和緩存數據一致性的策略

              先說兩種套路:

            (a)先更新數據庫在更新緩存

            (b)先更新緩存

              可是不管那種方式,都會有更新一個成功而另一個失敗的狀況。

              因此採起的策略是:因此通常的策略是當更新數據時,先刪除緩存數據,而後

              更新數據庫,而不是更新緩存,等要查詢的時候才把最新的數據更新到緩存。

     (2)數據庫與緩存雙寫狀況下致使數據不一致問題

              首先說明,該問題是選用(1)中的策略產生的

              場景一

                     當更新數據時,如更新某商品的庫存,當前商品的庫存是100,如今要更

                     新爲99,先更新數據庫更改爲99,而後刪除緩存,發現刪除緩存失敗了,

                     這意味着數據庫存的是99,而緩存是100,這致使數據庫和緩存不一致。

              場景一解決方案

                     這種狀況應該是先刪除緩存,而後在更新數據庫,若是刪除緩存失敗,那

                     就不要更新數據庫,若是說刪除緩存成功,而更新數據庫失敗,那查詢的

                     時候只是從數據庫裏查了舊的數據而已,這樣就能保持數據庫與緩存的一

                     致性。 

              場景二

                     在高併發的狀況下,若是當刪除完緩存的時候,這時去更新數據庫,但還

                     沒有更新完,另一個請求來查詢數據,發現緩存裏沒有,就去數據庫裏

                     查,仍是以上面商品庫存爲例,若是數據庫中產品的庫存是100,那麼查

                     詢到的庫存是100,而後插入緩存,插入完緩存後,原來那個更新數據庫

                     的線程把數據庫更新爲了99,致使數據庫與緩存不一致的狀況。

             場景二解決方案

                    原文寫的並不完善,根據我屢次讀這段話的意思以爲下文中商品的id就是

                    redis中的key。

                    遇到這種狀況,能夠用隊列的去解決這個問,建立幾個隊列,如20個,根

                    據商品的ID去作hash值,而後對隊列個數取摸,當有數據更新請求時,先

                    把它丟到隊列裏去,當更新完後在從隊列裏去除,若是在更新的過程當中,

                    遇到以上場景,先去緩存裏看下有沒有數據,若是沒有,能夠先去隊列裏

                    看是否有相同商品ID在作更新,若是有也把查詢的請求發送到隊列裏去,

                    而後同步等待緩存更新完成。這裏有一個優化點,若是發現隊列裏有一個

                    查詢請求了,那麼就不要放新的查詢操做進去了,用一個while(true)循

                    環去查詢緩存,循環個200MS左右,若是緩存裏尚未則直接取數據庫的

                    舊數據,通常狀況下是能夠取到的。  

                            我的認爲若是不考慮請求處理的順序,能夠直接查redis裏有沒有,

                    若是沒有,則sleep(200ms),而後在去查redis。  

    8.Redis的過時策略和內存淘汰機制

     (1)過時策略

                     咱們set key的時候,均可以給一個expire time(值爲-1時,表示永不過時

              ),就是過時時間,指定這個key好比說只能存活1個小時,咱們本身能夠指定

              緩存到期就失效。如果假設你設置一個一批key只能存活1個小時,那麼接下來

              1小時後,redis是怎麼對這批key進行刪除的?

              答案是:按期刪除+惰性刪除

                     所謂按期刪除,指的是redis默認是每隔100ms就隨機抽取一些設置了

              過時時間的key,檢查其是否過時,若是過時就刪除。

                     注意,這裏可不是每隔100ms就遍歷全部的設置過時時間的key,那

              樣就是一場性能上的災難。

               實際上redis是每隔100ms隨機抽取一些key來檢查和刪除的。可是,

              按期刪除可能會致使不少過時key到了時間並無被刪除掉,因此就得靠

              惰性刪除了。這就是說,在你獲取某個key的時候,redis會檢查一下 ,這

              個key若是設置了過時時間那麼是否過時了?若是過時了此時就會刪除,

              不會給你返回任何東西。並非key到時間就被刪除掉,而是你查詢這個

              key的時候,redis再懶惰的檢查一下經過上述兩種手段結合起來,保證過

              期的key必定會被幹掉。

                     可是實際上這仍是有問題的,若是按期刪除漏掉了不少過時key,然

              後你也沒及時去查,也就沒走惰性刪除,此時會怎麼樣?

                    若是大量過時key堆積在內存裏,致使redis內存塊耗盡了,怎麼辦?

               答案是:走內存淘汰機制。

     (2)內存淘汰機制

                             redis內存淘汰指的是用戶存儲的一些鍵被能夠被Redis主動地從

                    實例中刪除,從而產生讀miss的狀況,redis爲了更好地使用內存,用

                    必定的緩存miss來換取內存的使用效率。 咱們能夠經過配置

                    redis.conf中的maxmemory這個值來開啓內存淘汰功能。maxmemory

                    爲0的時候表示咱們對Redis的內存使用沒有限制。

                    redis內存淘汰過程

                            * 客戶端發起了須要申請更多內存的命令(如set)。

                            * Redis檢查內存使用狀況,若是已使用的內存大於maxmemory

                              則開始根據用戶配置的不一樣淘汰策略來淘汰內存(key),從

                              而換取必定的內存。

                            * 若是上面都沒問題,則這個命令執行成功。

                    redis中有以下一些內存淘汰策略

                            noeviction:當內存不足以容納新寫入數據時,新寫入操做會報

                                                錯,這個通常沒人用吧。

                            allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除

                                                最近最少使用的key(這個是最經常使用的)

                            allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,

                                                        隨機移除某個key,這個通常沒人用吧

                            volatile-lru:當內存不足以容納新寫入數據時,在設置了過時時間

                                                的鍵空間中,移除最近最少使用的key(這個通常不

                                                太合適)

                            volatile-random:當內存不足以容納新寫入數據時,在設置了過

                                                        期時間的鍵空間中,隨機移除某個key

                            volatile-ttl:當內存不足以容納新寫入數據時,在設置了過時時間

                                               的鍵空間中,優先移除更早過時時間的key。    

                            鍵空間:Redis 是一個鍵值對(key-value pair)數據庫服務器,

                                          服務器中的每一個數據庫都由一個 redis.h/redisDb 結構表

                                          示,其中,redisDb 結構的dict字典保存了數據庫中的所

                                          有鍵值對, 咱們將這個字典稱爲鍵空間(key space)

                                          說白了上文中的鍵空間就翻譯爲key-value對。

                           上面的過時策略看不懂的話,下面有一個白話版:

                                  noeviction : 永不過時,返回錯誤

                                  allkeys-lru : 刪除lru算法的key

                                  allkeys-random:隨機刪除

                                  volatile-lru:只對設置了過時時間的key進行LRU(默認值)

                                  volatile-random:隨機刪除即將過時key
                                 
                                  volatile-ttl : 刪除即將過時的

                           注意:內存淘汰刪除的和key自己的過時時間沒有關係,而取決

                                      於內存淘汰測率是哪種。

                    過時的key對rdb和aof的影響

                    * 過時的key對rdb沒有任何影響

                      當從內存數據庫持久化數據到RDB文件使,持久化key以前,會檢查

                      是否過時,過時的key不進入RDB文件;當從RDB文件恢復數據到內

                      存數據庫數據載入數據庫以前,會對key先進行過時檢查,若是過時,

                      不導入數據庫(主庫狀況)

                    * 過時key對aof沒有任何影響

                      當從內存數據庫持久化數據到AOF文件時:

                      當key過時後,尚未被刪除,此時進行執行持久化操做(該key是

                      不會進入aof文件的,由於沒有發生修改命令);當key過時後,在

                      發生刪除操做時,程序會向aof文件追加 一條del命令(在未來的以

                      aof文件恢復數據的時候該過時的鍵就會被刪掉)。

                      當aof重寫時:

                      重寫時,會先判斷key是否過時,已過時的key不會重寫到aof文件。

     (3)redis和jredis的線程安全

              redis自己是單線程的是線程安全的,Jredis是非線程安全的,能夠經過

              JedisPool獲取線程安全的Redis實例,如 下代碼:

Jedis jedis = null;
try {
    jedis = pool.getResource();
    /// ... 執行相關的Redis操做
    jedis.set("foo", "bar");
    String foobar = jedis.get("foo");
    jedis.zadd("sose", 0, "car"); jedis.zadd("sose", 0, "bike"); 
    Set<String> sose = jedis.zrange("sose", 0, -1);
} finally {
    if (jedis != null) {
        jedis.close();
    }
}
/// ... 當關閉應用程序時:
pool.destroy();

     9.redis事務

        redis的事務時不可嵌套的。

        詳細參考文章:

        http://blog.csdn.net/hechurui/article/details/49508749

        http://www.runoob.com/redis/redis-transactions.html

        http://blog.csdn.net/cuipeng0916/article/details/53698774

    10.redis實現分佈式鎖

       http://www.javashuo.com/article/p-svvbuzni-o.html

    11.redis數據備份

    (1)建立一個按期任務(cron job), 每小時將一個 RDB 文件備份到一個文件

             夾,而且天天將一個 RDB 文件備份到另外一個文件夾。確保快照的備份都帶

             有相應的日期和時間信息, 每次執行按期任務腳本時, 使用 find 命令來刪

             除過時的快照:好比說, 你能夠保留最近 48 小時內的每小時快照, 還能夠

             保留最近一兩個月的每日快照。至少天天一次, 將 RDB 備份到你的數據中

             心以外, 或者至少是備份到你運行 Redis 服務器的物理機器以外。

    (2)Redis 的容災備份基本上就是對數據進行備份, 並將這些備份傳送到多個不

             同的外部數據中心。容災備份能夠在 Redis 運行併產生快照的主數據中心發

             生嚴重的問題時, 仍然讓數據處於安全狀態。有的Redis 用戶是創業者,他

             們沒有大把大把的錢能夠浪費, 因此下面介紹的都是一些實用又便宜的容災

             備份方法:

             Amazon S3 ,以及其餘相似 S3 的服務,是一個構建災難備份系統的好地方。

            最簡單的方法就是將你的每小時或者每日 RDB 備份加密並傳送到 S3 。 對數

            據的加密能夠經過 gpg -c 命令來完成(對稱加密模式)。 記得把你的密碼放

            到幾個不一樣的、安全的地方去(好比你能夠把密碼複製給你組織裏最重要的人

            物)。 同時使用多個儲存服務來保存數據文件,能夠提高數據的安全性。傳

            送快照能夠使用 SCP 來完成(SSH 的組件)。 如下是簡單而且安全的傳送

            方法:

           買一個離你的數據中心很是遠的 VPS(虛擬專用服務器) , 裝上 SSH , 建立

           一個無口令的 SSH客戶端 key ,並將這個 key 添加到 VPS 的 authorized_keys

           文件中, 這樣就能夠向這個 VPS傳送快照備份文件了。 爲了達到最好的數據安

           全性,至少要從兩個不一樣的提供商那裏各購買一個 VPS 來進行數據容災備份。

           須要注意的是, 這類容災系統若是沒有當心地進行處理的話,是很容易失效的。

           最低限度下, 你應該在文件傳送完畢以後, 檢查所傳送備份文件的體積和原始

           快照文件的體積是否相同。若是你使用的是 VPS , 那麼還能夠經過比對文件的

           SHA1 校驗和來確認文件是否傳送完整。另外,你還須要一個獨立的警報系統,

           讓它在負責傳送備份文件的傳送器(transfer)失靈時通知你。

    12.Redis的數據結構

         學習本節,若是看不明白能夠先看如下博客

         http://www.javashuo.com/article/p-brrnmeyh-bp.html

         

                Redis內部,使用字典來存儲不一樣類型的數據,如圖中的dictht,字典由一組dicEntry組

         成,其中包含了指向key和value的指針以及指向下一個dicEntry的指針。

       (1)RedisObject

                       在Redis中全部的對象都被封裝成了RedisObject(Redis 內部使用一個 redisObject

                對象來表示全部的 key 和 value),如上圖中淺綠色的模塊,RedisObject包括了對象的

                類型(Redis支持的5種數據類型),另外還包括了具體對象的存儲方式,好比最右邊虛

                線模塊內的幾種類型。

                

 

                上圖說明:

                type:表明咱們的5中數據類型(string、List、Hash、Set、ZSet)

                encoding:表明具體的實現方式,其內容以下表:

                 

                                  擴展:爲何要有這麼多編碼方式?

                                             爲的是更合理的使用內存。編碼方式一旦升級是不能降級的

                lru:LRU_BITS:保存的是最近一次訪問的時間戳,用來作內存清理。

                refcount:引用計數器,redis中0-9999是共享對象。使用命令 object refcount key查看

                                key被引用了幾回。

                咱們來結合類型來介紹具體的數據存儲方式:

       (2)數據的存儲方式

              (a)String

                    

                       

 

 

 

                    String類型的內部是經過SDS來實現,SDS相似於Java中的ArrayList,能夠經過預

                    分配冗餘空間的方式來減小內存的頻繁分配。

                    有三種編碼方式:

                    --  INT

                        整數類型

                    --  EMBSTR

                        字符長度>44字節

                    --  RAW

                        字符長度<44字節

                    Embstr和raw區別

                    embstr和raw的區別是,embstr在建立字符串對象的時候,會分配一次空間,這個空間

                    包含redisObject對象結構和sdshdr結構(保存字符串),而raw則會進行兩次分配,分

                    表分配空間給redisObject和sdshdr。因此在字符串較短時,使用embstr會有一些優點:

                    分配次數下降,釋放次數下降(由於只須要釋放一個空間,而raw須要釋放兩個空間),

                    字符串連續。

              (b)List

                       3.2以前,List類型有ZipList壓縮鏈表和LinkedList雙鏈表實現;3.2開始,只有quicklist

                       ZipList是存存儲在一段連續的

                       內存空間上,存儲效率高,可是它不利於修改操做,適用於數據較少的狀況;

                       LinkedList在插入節點上覆雜度很低,但它的內存開銷很大,每一個節點的地址不連

                       續,容易產生內存碎片;此外在3.2版本後增長了quicklist,quicklist結合了二者的

                       優勢,quicilist自己是一個雙向無環鏈表,每個節點都是一個ziplist。

                       擴展:什麼是ziplist

                               ziplist是Redis爲了節約內存而開發的,具體結構以下:

                      

 

         

                    

                           ziplist節點構成:

                         

                          --  previous_entry_length

                              previous_entry_length屬性以字節爲單位,記錄了壓縮列表中前一個節點的長度。

                          --  encoding

                              encoding屬性記錄了節點的content屬性所保存數據的類型以及長度

                          --  content

                              content屬性負責保存節點的值,節點值能夠是一個字節數組或者整數,值的

              (c)Hash類型

                              Hash類型有ziplist和字典(或叫hashtable)兩種實現,當hash表中全部的key和

                       value字符串長度都小於64字節且鍵值對的數量小於512個時,使用ziplist來節省空間,

                       超過時,使用字典。查找和增長同Java中的相同。

                       擴展:什麼是字典?

                    

 

                            

                       --  dictht ht[2]:表示兩個hash表,一個是正在使用的,一個是用來作擴容的。

                                                hash表有一個負載因子,當超過負載因子時,效率就會變差,因此就

                                                要使用兩個hash表,一個是正在使用的,一個是用來作擴容的。 一旦

                                                超過這個因子作hash擴容的時候,它會把正在使用的數據慢慢的遷移

                                                到空的hash表裏面去。遷移完成以後用新的hash替換原來的hash(指針

                                                互換)。

                                                       *  爲何使用兩個hash作擴容,而不像Java中直接作擴容?

                                                          執行擴容時等待時間變長了,而Redis是單線程的,因此它接受

                                                          不了這樣的等待。

                                                       *  訪問的時候發現key被遷移了,正在使用的hash表裏不存在這個

                                                           key,怎麼辦?

                                                           它能夠作路由的功能,去擴容的hash獲取key。         

                       --  hash表

                           同Java中的同樣,也是一個數組的鏈表

              (d)set

                       set類型的內部實現能夠是intset或者是字典(或稱HashTablle),當集合中元素小於512

                       且全部的數據都是數值類型時,纔會使用Intset,不然使用hashtable

                       http://www.javashuo.com/article/p-pbzxqcxl-bv.html

              (e)sorted set

                       實現能夠是ziplist或者是skiplist跳錶,當有序集合中的元素數量小於128個時,並 

                       且全部元素長度都小於64字節時會使用ziplist,不然會轉化成skiplist跳錶

                       什麼是跳錶:

                       http://www.javashuo.com/article/p-pbzxqcxl-bv.html

    12. Redis PipeLine(管道)

                 Redis的pipeline(管道)功能在命令行中沒有,但redis是支持pipeline的,並且在各個語言版的

          client中都有相應的實現。 因爲網絡開銷延遲,就算redis server端有很強的處理能力,也會因爲 

          收到的client消息少,而形成吞吐量小。

        (1)爲何要使用PipeLine

                                Redis客戶端與Redis服務器之間使用TCP協議進行鏈接,一個客戶端能夠經過一個

                         socket連接發起多個請求命令。每一個請求命令發出後client一般會阻塞並等待redis服務器處理,

                         redis處理完請求命令後會將結果經過響應報文返回給client,所以當執行多條命令的時候都需

                         要等待上一條命令執行完畢才能執行。好比:

                         

                         其執行過程以下圖所示:

                    

 

                   因爲通訊會有網絡延遲,假如client和server之間的包傳輸時間須要0.125秒。那麼上面的三個命令

                   6個報文至少須要0.75秒才能完成。這樣即便redis每秒能處理100個命令,而咱們的client也只能一

                   秒鐘發出四個命令。這顯然沒有充分利用 redis的處理能力。

                          而管道(pipeline)能夠一次性發送多條命令並在執行完後一次性將結果返回,pipeline經過減

                   少客戶端與redis的通訊次數來實現下降往返延時時間,並且Pipeline 實現的原理是隊列,而隊列的

                   原理是時先進先出,這樣就保證數據的順序性。 Pipeline 的默認的同步的個數爲53個,也就是說

                   arges中累加到53條數據時會把數據提交。其過程以下圖所示:client能夠將三個命令放到一個tcp報

                   文一塊兒發送,server則能夠將三條命令的處理結果放到一個tcp報文返回。

                 

        (2)使用pipeline應該注意的點

                        須要注意到是用 pipeline方式打包命令發送,redis必須在處理完全部命令前先緩存起全部命令的

                 處理結果。打包的命令越多,緩存消耗內存也越多。因此並非打包的命令越多越好。具體多少合適

                 須要根據具體狀況測試。

                        另外,編碼時還應該注意,pipeline期間將「獨佔」連接,此期間將不能進行非「管道」類型的其餘操

                 做,直到pipeline關閉;若是你的pipeline的指令集很龐大,爲了避免干擾連接中的其餘操做,你能夠爲

                 pipeline操做新建Client連接,讓pipeline和其餘正常操做分離在2個client中。

        (3)pipeline的適用場景

                        有些系統可能對可靠性要求很高,每次操做都須要立馬知道此次操做是否成功,是否數據已經寫

                 進redis了,那這種場景就不適合。

                   還有的系統,多是批量的將數據寫入redis,容許必定比例的寫入失敗,那麼這種場景就能夠使

                 用了,好比10000條一下進入redis,可能失敗了2條無所謂,後期有補償機制就好了,好比短信羣發

                 這種場景,若是一下羣發10000條,按照第一種模式去實現,那這個請求過來,要好久才能給客戶端

                 響應,這個延遲就太長了,若是客戶端請求設置了超時時間5秒,那確定就拋出異常了,並且自己羣發

                 短信要求實時性也沒那麼高,這時候用pipeline最好了。

        (3)pipeline和普通模式示例                 

/*
     * 測試普通模式與PipeLine模式的效率: 
     * 測試方法:向redis中插入10000組數據
     */
    public static void testPipeLineAndNormal(Jedis jedis)
            throws InterruptedException {
        Logger logger = Logger.getLogger("javasoft");
//普通模式開始
long start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { jedis.set(String.valueOf(i), String.valueOf(i)); } long end = System.currentTimeMillis(); logger.info("the jedis total time is:" + (end - start)); //普通模式結束
//Pipeline模式開始 Pipeline pipe
= jedis.pipelined(); // 先建立一個pipeline的連接對象 long start_pipe = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { pipe.set(String.valueOf(i), String.valueOf(i)); } pipe.sync(); // 獲取全部的response long end_pipe = System.currentTimeMillis(); logger.info("the pipe total time is:" + (end_pipe - start_pipe));
        //Pipeline模式結束
BlockingQueue<String> logQueue = new LinkedBlockingQueue<String>(); long begin = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { logQueue.put("i=" + i); } long stop = System.currentTimeMillis(); logger.info("the BlockingQueue total time is:" + (stop - begin)); }

    13. Redis 事務

        (1)說一下Redis對兩種類型錯誤及對其的處理

                  --  在調用EXEC命令以前就失敗。

                      一個命令可能會在被放入隊列時失敗。所以,事務有可能在調用EXEC命令以前就發生錯誤。例

                      如,這個命令可能會有語法錯誤(參數的數量錯誤、命令名稱錯誤,等等),或者可能會有某些

                      臨界條件(例如:若是使用maxmemory指令,爲Redis服務器配置內存限制,那麼就可能會有內

                      存溢出條件)。 

                             Redis 2.6.5版本以前,能夠使用Redis客戶端檢測第一種類型的錯誤,在調用EXEC命令之

                      前,這些客戶端能夠檢查被放入隊列的命令的返回值:若是命令的返回值是QUEUE字符串,那

                      麼就表示已經正確地將這個命令放入隊列;不然,Redis將返回一個錯誤。若是將某個命令放入

                      隊列時發生錯誤,那麼大多數客戶端將會停止事務,而且丟棄這個事務。

                             然而,從Redis 2.6.5版本開始,服務器會記住事務積累命令期間發生的錯誤。而後,

                      Redis會拒絕執行這個事務,在運行EXEC命令以後,便會返回一個錯誤消息。最後,Redis會

                      自動丟棄這個事務。

                  --   在調用EXEC命令以後失敗。

                       事務中的某個命令可能會執行失敗。例如,咱們對某個鍵執行了錯誤類型的操做(例如,對一

                       個字符串(String)類型的鍵執行列表(List)類型的操做)。

                            此時,Redis不會進行任何特殊處理:在事務運行期間,即便某個命令運行失敗,全部其

                       他的命令也將會繼續執行(這也是Redis事務的缺陷,它不支持回滾)

        (2)相關命令介紹

                (a)MULTI

                         用於標記事務塊的開始。Redis會將後續的命令逐個放入隊列中,而後才能使用EXEC命令原

                         子化地執行這個命令序列。

                         這個命令的運行格式以下所示:

                         MULTI

                         這個命令的返回值是一個簡單的字符串,老是OK

                (b)EXEC

                         在一個事務中執行全部先前放入隊列的命令,而後恢復正常的鏈接狀態。

                         當使用WATCH命令時,只有當受監控的鍵沒有被修改時,EXEC命令纔會執行事務中的命令,

                         這種方式利用了檢查再設置(CAS)的機制。這個命令的運行格式以下所示:

                         EXEC

                         這個命令的返回值是一個數組,其中的每一個元素分別是原子化事務中的每一個命令的返回值。

                         當使用WATCH命令時,若是事務執行停止,那麼EXEC命令就會返回一個Null值。

                (c)DISCARD

                         清除全部先前在一個事務中放入隊列的命令,而後恢復正常的鏈接狀態。

                         若是使用了WATCH命令,那麼DISCARD命令就會將當前鏈接監控的全部鍵取消監控。

                         這個命令的運行格式以下所示:

                         DISCARD

                         這個命令的返回值是一個簡單的字符串,老是OK。

                (c)WATCH

                         當某個事務須要按條件執行時,就要使用這個命令將給定的鍵設置爲受監控的。這個命令的

                         運行格式以下所示:

                         WATCH key [key ...]

                         這個命令的返回值是一個簡單的字符串,老是OK。

                         對於每一個鍵來講,時間複雜度老是O(1)

                         注:用了WATCH命令也不能保證事務的真正意義上的回滾,執行EXEC命令期間出現錯誤,

                                是不能回滾的。

                (c)UNWATCH

                         清除全部先前爲一個事務監控的鍵。

                         若是你調用了EXEC或DISCARD命令,那麼就不須要手動調用UNWATCH命令。

                         這個命令的運行格式以下所示:

                         UNWATCH

                         這個命令的返回值是一個簡單的字符串,老是OK。

                         時間複雜度老是O(1)。

    12.redis的五種數據類型及其經常使用命令經常使用命令

       (1)String(重點)

                * set key value:設置key的值,若存在則覆蓋

                * setnx key value:SET if Not exists,若key存在則不操做,因此不會覆

                                            蓋原值。

                * setex:e表示expired。該命令同set命令,同時能夠指定key的過時時間。

                            注意這是一個原子命令,例子:

                            setex color 10 red

                            指定key(color)的值爲red,過時時間10秒,10秒後返回nil(

                            redis裏nil表示空)。

                * setrange:替換字符串。

                                 例子:set email 174754613@163.com

                                 setrange email  10 ww

                                 表示重第10位開始替換,替換後的字符串爲ww 

                * getset:返回舊值並設置新值

                * incr和decr:對一個值進行遞增和遞減,這是一個原子命令

                * incrby和decrby:對某個值進行指定長度的遞增和遞減

                                           語法:

                                           incrby key [步長]

                                           decrby key [步長]

                * mset key1 value1 key2 value2 ... keyN valueN:設置這些key的值,若

                                                                                                存在則覆蓋

                * mget key1 key2...keyN:獲取這些key的value,例子:

                                                        redis> SET key1 "Hello"

                                                        "OK"

                                                        redis> SET key2 "World"

                                                        "OK"

                                                        redis> MGET key1 key2 nonexisting

                                                        1) "Hello"

                                                        2) "World"

                                                        3) (nil)

                                                        redis> 

                * append key value:向key的字符串追加拼接

                * strlen [key]:獲取key對應value的長度

                * del key :刪除指定的key

                * mset key1 value1 key2 value2 ... keyN valueN:設置這些key的值,若

                                                                                                存在則覆蓋。

               * rename key:重命名key

    (2)hash(重點)

           (a)hash介紹

                    相似於Java中的HashMap

                    Hash類型是String類型的field和value的映射表,或者說一個String集

                    合。它特別適合存儲對象,相比較而言將一個對象存儲在Hash類型

                    中要比存儲在String類型中更節省空間。

          (b)hash的有兩種使用方式:

                 (i)一條數據對應一個hash。以下表:

                    

                        須要兩個hash表,以下

                        hash1(id:1,name:zhangsan,age:20)

                        hash2(id:2,name:lisi,age:25)

                        對應redis命令:

                        HSET hash1 id 1 name zhangsan age 20

                        HSET hash2 id 2 name lisi age 25

                 (ii)一個hash存多個對象

                         表仍是如(i)中的,filed能夠是uuid,value是對象的JSON字符

                         串hash1(1:Object1.toJSON,2:Object2.toJSON)

                         對應redis命令:

                         HSET hash1 id Object1.toJSON hash2 id Object2.toJSON(總決定這個不對應該是下面的)

                         HSET hash1 1 Object1.toJSON 2 Object2.toJSON

                         HSET key field value:key是對象名,field是屬性,value是值。

                         HMSET key field value [field value ...]:同時設置多個屬性。

                         HGET key field:獲取該對象的該屬性。 

                 (iii)Hash命令介紹

                           * HMGET key field value [field value ...]:獲取多個屬性值。

                           * HGETALL key:獲取對象的全部信息。

                           * HKEYS key:獲取對象的全部屬性。

                           * HVALS key:獲取對象的全部屬性值。

                           * HDEL key field:刪除對象的該屬性。

                           * HEXISTS key field:查看對象是否存在該屬性。

                           * HINCRBY key field value:原子自增操做,只能是integer的

                                                                         屬性值能夠使用。

                           * HLEN key:Return the number of entries (fields) contained

                                                 in the hash storedat key.獲取屬性的個數。

                ???redis除了String類型存數據的時候,須要本身拼數據嗎?

    (3)List(簡單的說它就是java中的Queue)

           (i)簡介

                   List類型是一個鏈表結構的集合,其主要功能有push、pop、獲取元

                   素等。更詳細的說,List類型是一個雙端鏈表的結構,咱們能夠操做

                   集合的頭部或者尾部進行添加刪除元素,List的設計很是簡單精巧,

                   既能夠做爲棧,也能夠做爲隊列,知足絕大多數需求。有些公司直

                   接使用該類型作消息隊列。

           (ii)經常使用命令

                    * lpush keyList value:向keyList左邊(頭部,先進後出,該類型相

                                                      當於棧)添加元素。

                                                      取值時使用命令:

                                                      lrange keyList 0 -1;

                    * rpush keyList value:向keyList右邊(尾部)添加元素(先進先

                                                      出,該類型至關於隊列),向後加,r表示右

                                                      邊。   

                                                      取值時使用命令:

                                                      lrange keyList 0 -1;               

                    * lrange keyList beginIndex endIndex:獲取keyList的元素,用兩

                                                                                  端的索引取出子集,

                                                                                  endIndex=-1則表示所有

                                                                                  取出。

                    * llen keyList:獲取keyList的長度大小。

                    * lpop keyList:取出並移除keyList第一個元素,左邊的元素。

                    * rpop keyList:取出並移除keyList最後一個元素,右邊的元素。

                    * rpoplpush:該命令是一個原子命令。該命令包含以下兩個步驟:

                                          先從尾部刪除元素,而後子從頭部加入元素。

                    * lindex key index:獲取該索引下的元素。

                    * lrem key count value:刪除count個value。(count爲正數,從

                                                           頭開始,刪除count個value元素;count

                                                           爲負,則從尾部向頭刪除|count|個value

                                                           元素;count爲0,則全部的元素爲value

                                                           的都刪除)

                    * LSET key index value:設置索引爲index下的元素爲value.超出

                                                             索引範圍報錯。

                    * Linsert:在指定集合元素前插入元素(該方法基本不用)。

                                  例子:

                                  集合元素{one,two}

                                  執行命令:

                                  Linsert list3 before 「one」 "three"

                                  結合元素變爲:{three,one,two}

                    * LTRIM key start end:清空索引在start 和end以外的元素,索引

                                                          從0開始,兩端保留,兩端以外的清空。

                    * RPOPLPUSH srckey dstkey:源隊列srckey,目標隊列dstkey,

                                                                    將srckey的最後一個移除,並放到

                                                                    dstkey的第一個。

           (iii)redis實現消息隊列思路

                     使用命令rpush和lpop、rpop

                     * 使用rpush放入數據到list;

                     * 使用命令lpop或rpop方法從list的頭部或尾部刪除數據並返回刪

                       除的數據

    (4)set(像Java List的升級版)

             set是string類型的無序集合(zset是string類型有序集合),set是通

             過hashtable實現的,對集合咱們能夠取交集、並集、差集。set中的

             元素不容許重複。

             sadd key value : 向set添加元素。

             srem key value :從set中移除元素。

             smembers key : 取出全部set元素。

             SISMEMBER key value: 查看value是否存在set中。

             SUNION key1 key2 ... keyN:將全部key合併後取出來,相同的值只取一次。

             key1 = {a,b,c,d}

             key2 = {c}

             key3 = {a,c,e}

             SUNION key1 key2 key3 = {a,b,c,d,e}

             scard key : 獲取set中元素的個數。

             SRANDMEMBER key: Return a random element from a Set, without removing the

                                                  element.隨機取出一個。

             SDIFF key1 key2 ... keyN:獲取第一set中不存在後面幾個set裏的元素。

             SDIFFSTORE dstkey key1 key2 ... keyN:和sdiff相同,獲取key1中不存在其餘key

                                                                                 裏的元素,但要存儲到dstkey中。

             SINTER key1 key2 ... keyN:取出這些set的交集。

             SINTERSTORE dstkey key1 key2 ... keyN:取出這些key的交集並存儲到dstkey。

             SMOVE srckey dstkey member:將元素member從srckey中轉移到dstkey中,這個

                                                                   操做是原子的。

    (5)zset(至關於java中有序的set)

             zset是string類型有序集合

             ZADD key score member:向有序set中添加元素member,其中score爲分數,默認

                                                         升序。

             ZRANGE key start end [WITHSCORES]:獲取按score從低到高索引範圍內的元素,

                                                                                 索引能夠是負數,-1表示最後 一個,-2表

                                                                                 示倒數第二個,即從後往前。withscores

                                                                                 可選,表示獲取包括分數。

            ZREVRANGE key start end [WITHSCORES]:同上,但score從高到低排序。

            ZCOUNT key min max:獲取score在min和max範圍內的元素的個數。

            ZCARD key:獲取集合中元素的個數。

            ZINCRBY key increment member:根據元素,score原子增長increment.。

            ZREMRANGEBYSCORE key min max:清空集合內的score位於min和max之間的元

                                                                             素。

            ZRANK key member:獲取元素的索引(照score從低到高排列)。

            ZREM key member:移除集合中的該元素。

            ZSCORE key member:獲取該元素的score。

            更多詳細參考:

            http://www.cnblogs.com/woshimrf/p/5198361.html

    13.redis的五種數據類型及使用場景

         http://www.javashuo.com/article/p-hnomguns-k.html

相關文章
相關標籤/搜索