Redis再再 深入學習

Redis深入學習

一、數據結構類型

  • string ,set get ex
  • list :lpush lpop 用於消息隊列
  • set :sadd、smenbers 一鍵多值,用於標籤
  • zset : zadd user:rank score menber
    • zadd user:rank 1 james 21 kate 32 jack 46 frank 258 tom;` james獲得1票,kate21票…
    • zrange user:rank james ---- 找到james的票數 --dict
    • zrange user:rank 0 10 — 找到前10的人的名字
    • zrank user:rank james 返回它的排名
    • zscore user:rank james 返回它的分數值
    • zrange user:rank 0 10 with scores —就是把前10的人票數和名字都列出來
  • hash:hset user:001 name huanghuo age 1 / hgetall user:001 / hget user:001 name

二、過期策略

最近最少使用原則LRU.、 深入

隨機時間 – 深入

設置的過期時間

永不過期

三、持久化

  • RDB

    他會fork一個進程,在指定時間內把內存的數據備份到dump.gdb文件中,再次啓動的時候就會把讀取日誌到內存中。缺點就是無法實時化,進程佔用內存比較大。主要用於大規模的數據恢復

  • AOF,二者可以同時存在,但是是先去讀取AOF文件的

    以日誌的形式記錄每個寫操作。

    缺點就是文件會很大。優點就是數據完整性高,當發生宕機的時候,可以用它來恢復數據就和mysql的redolog一個作用

四、事務

multi 開啓事務、exec 執行事務、discard 回滾事務

redis是部分支持事務的,最後執行的時候,成功的就成功,失敗的就失敗

redis沒有隔離性因爲它是每個命令進入隊列,最後一起執行的

五、主從複製(高可用)

slave of 本質是從庫心跳線程不斷去主庫同步數據,通過RDB文件

  • 分爲兩種:全量複製(只在第一次的時候),增量複製

  • 先說兩個參數:runid是每臺機的id,用來標誌他是否是第一次請求同步數據。offset:是主庫和從庫都有的參數,比如主庫發送了N個字節的數據過來,那麼主庫和從庫的offset就是N,在增量同步的時候會使用到。

  • 過程:

    • 這一切都是剛剛連上主庫,發送slaveof的初始化操作

      **一種總結:**從庫發送slaveof給主庫,判斷自己是否是有runid,如果沒有那麼就是第一次同步,會發送PSYNC ? -1指令,主庫會把快照發給從庫,從庫進行復制,並記錄offset和runid。如果不是第一次複製(主庫比較從庫發過來的runid和自己的runid是否匹配),那麼從庫需要重新發送一個指令PSYNC RUNID OFFSET給主庫,主庫根據offset,發送快照給從庫。

      **補充總結:**配置好slave後,slave與master建立連接,然後發送sync命令. 無論是第一次連接還是重新連接,master都會啓動一個後臺進程,將數據庫快照保存到文件中,同時master主進程會開始收集新的寫命令並緩存. 後臺進程完成寫文件後,master就發送文件給slave,slave將文件保存到硬盤上,再加載到內存中, 接着master就會把緩存的命令轉發給slave,後續master將收到的寫命令發送給slave. 如果master同時收到多個slave發來的同步連接命令,master只會啓動一個進程來寫數據庫鏡像, 然後發送給所有的slave

      **再次總結:**可以總結爲下面三個過程:

      • 同步快照階段:Master創建併發送快照給Slave,Slave再入快照並解析。Master同時將此階段產生的新的命令寫入到積壓緩衝區中。----區分第幾次
      • 同步寫緩衝階段:Master向Slave同步存儲在緩衝區的寫操作命令。—主庫在想從庫發送快照的時候,發生的寫操作,就進入緩衝區。
      • 同步增量階段:Master向SLave同步寫操作命令。
    • 正常每一次:

      主庫有了寫操作,那麼就會主動推送給所有從庫。

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

六、哨兵模式(高可用,和主從複製是一夥的)

哨兵有多個,一般就是幾個redis幾個哨兵,他監控所有的redis服務器(在sentinel.conf中配置),每次ping一下各個服務器,如果超過半數哨兵都判斷不ok,那麼就投票出來換一個redis’做主庫。

七、redis的集羣

根據分區規則,比如哈希算法,我們把數據放到不同的redis服務器中。

具體實現cluster-enabled : true \ cluster-config-file

多個master,然後每個master又有自己的slave。

在這裏插入圖片描述

八、Zset實現原理

ZSET(SORTED SET)在redis中的編碼可以是ziplist或者是skiplist

  • 跳錶的原理skiplist

    參考視頻:https://www.bilibili.com/video/BV1LJ411t7Aihttps://www.bilibili.com/video/BV18J411s7eN 兩個非常好的skiplist原理講解,這方面視頻很少。

    skiplist的java實現:https://blog.csdn.net/bohu83/article/details/83654524

    
      public Integer insert(int key, int value) {
        	SkipListEntry p, q;
            int i = 0;
     
            // 查找適合插入的位子
            p = findEntry(key);
     
            // 如果跳躍表中存在含有key值的節點,則進行value的修改操作即可完成
            if(p.key ==key) {
                Integer oldValue = p.value;
                p.value = value;
                return oldValue;
            }
     
         // 如果跳躍表中不存在含有key值的節點,則進行新增操作
            q = new SkipListEntry(key, value); 
          
         q.left = p;
         q.right = p.right;
         p.right.left = q;
         p.right = q;
     
           //本層操作完畢,看更高層操作
         //拋硬幣隨機決定是否上層插入
         while ( r.nextDouble() < 0.5 /* Coin toss */ )
         {
        	 if ( i >= h )   // We reached the top level !!!
             {
                //Create a new empty TOP layer
        		 addEmptyLevel();
             }
        	 /* ------------------------------------
             Find first element with an UP-link
             ------------------------------------ */
    	      while ( p.up == null )
    	      {
    	         p = p.left;
    	      }
          /* --------------------------------
    	   Make p point to this UP element
    	   -------------------------------- */
              p = p.up;
     
    	/* ---------------------------------------------------
              Add one more (k,*) to the column
    	   Schema for making the linkage:
                   p <--> e(k,*) <--> p.right
                             ^
    		          |
    		          v
    		          q
    	   ---------------------------------------------------- */
      	SkipListEntry e;
      	 // 這裏需要注意的是除底層節點之外的節點對象是不需要value值的
      	e = new SkipListEntry(key, null); 
      	/* ---------------------------------------
    	   Initialize links of e
    	   --------------------------------------- */
    	e.left = p;
    	e.right = p.right;
    	e.down = q;
    	/* ---------------------------------------
    	   Change the neighboring links..  
    	   --------------------------------------- */
    	p.right.left = e;
    	p.right = e;
    	q.up = e;
        
    	//把q執行新插入的節點:
    	 q = e; 
    	 // level增加
    	 i = i + 1;  
    	
        	 
         }
        n = n+1; //更新鏈表長度        
            return null;
    

    redis的skiplist參考文章:https://blog.csdn.net/weixin_38008100/article/details/94629753

    跳錶跳錶的查找插入都是logN複雜度。

  • 壓縮表ziplist

    參考文章:https://blog.csdn.net/yellowriver007/article/details/79021049?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-2

    壓縮表是用字節組成雙向鏈表,在內存中一起佔用一塊區域.下圖是一個真實的結構
    在這裏插入圖片描述
    解釋

    • 這個ziplist一共包含33個字節。字節編號從byte[0]到byte[32]。圖中每個字節的值使用16進製表示。
    • 頭4個字節(0x21000000)是按小端(little endian)模式存儲的字段。什麼是小端呢?就是指數據的低字節保存在內存的低地址中(參見維基百科詞條Endianness)。因此,這裏的值應該解析成0x00000021,用十進制表示正好就是33。
    • 接下來4個字節(byte[4…7])是,用小端存儲模式來解釋,它的值是0x0000001D(值爲29),表示最後一個數據項在byte[29]的位置(那個數據項爲0x05FE14)。
    • 再接下來2個字節(byte[8…9]),值爲0x0004,表示這個ziplist裏一共存有4項數據。
    • 接下來6個字節(byte[10…15])是第1個數據項。其中,prevrawlen=0,因爲它前面沒有數據項;len=4,相當於前面定義的9種情況中的第1種,表示後面4個字節按字符串存儲數據,數據的值爲”name”。
    • 接下來8個字節(byte[16…23])是第2個數據項,與前面數據項存儲格式類似,存儲1個字符串”tielei”。
    • 接下來5個字節(byte[24…28])是第3個數據項,與前面數據項存儲格式類似,存儲1個字符串”age”。
    • 接下來3個字節(byte[29…31])是最後一個數據項,它的格式與前面的數據項存儲格式不太一樣。其中,第1個字節prevrawlen=5,表示前一個數據項佔用5個字節;第2個字節=FE,相當於前面定義的9種情況中的第8種,所以後面還有1個字節用來表示真正的數據,並且以整數表示。它的值是20(0x14)。
    • 最後1個字節(byte[32])表示,是固定的值255(0xFF)。

    hash結構如果數據少,那麼也是採用的ziplist結構.下圖的示意圖其實是來自以下命令:當數據大的時候就會變成dict結構

    hset user:001 name tielei
    hset user:001 age 20
    

    一些問題:

    • 怎麼實現雙向? 我們使用遍歷的方法找到指定位置,然後因爲每個壓縮表節點存放了前一個節點的長度,那麼我們就能夠往前找元素.
    • 怎麼實現從頭或者從尾巴插入數據?ziplist存放了zltail,表示最後一個數據在什麼位置上,那麼我們就能在尾巴上插入數據.
    • 爲麼數據元素變多或者變大,就不用ziplist了?因爲他還是使用遍歷的手段,但數據量大的時候,效率是不高的.另外他插入元素的時候,過程是進行內存拷貝的,但數據量大的時候,效率不高
    • 優點是什麼?佔用內存小,是一整塊內存,不會有內存碎片問題.
  • redis的zset(sort set)

    • zset的編碼可以是ziplist或者是skiplist。同時滿足以下條件時使用ziplist編碼 :下面的具體限制數字都是可以在配置文件中更改的

      • 元素數量小於128個
      • 所有menber長度都小於64字節
    • redis的skiplistNode是存放key(score)和value(menber)的,但是實現上不是放在一起的,是同時包含一個字段dict和skiplist的,skiplist就是score的多層鏈表,dict就存放menber值,和skiplist的score有對應關係,這樣查找score是O(logN),找對應的menber就只是O(1)。因此也可以有重複的score,就不會想hash表一樣。

    • skipList的隨機數是<0.25; 最高層限制是32層

    • 有了上面的預備知識我們可以知道下面命令的過程:

      • zscore 是查詢menber的score是由dict來提供。但是如果是ziplist怎麼辦呢?他的key value是連在一起的,因此得到key的位置以後,下一個就是value
      • zrange 是查詢就是差skiplist
      • zrank 是查詢menber的排名,那麼就是先去dict,然後再去skiplist
  • skiplist和平衡樹、哈希表對比

    • 哈希表不是有序排列的,只能找到key對應的值,無法做範圍查詢,而平衡樹skiplist能夠實現範圍查詢。
    • 在做範圍查找的時候,平衡樹操作相對複雜,先查找出小值,之後中序遍歷查找不超過範圍最大值的節點。如果不是個完全平衡二叉樹,這裏的中序遍歷並不簡單。而跳錶找到最小值以後,直接進行若干個遍歷就ok找到範圍最大值。
    • 平衡樹插入刪除操作都可能引發子樹的調整,邏輯調整,左旋右旋之類的。而跳錶只要改指針
    • 查找單個key,hash的時間複雜度是O(1),是最快的。
    • 算法實現上,跳錶簡單得多。

範圍查詢,而平衡樹skiplist能夠實現範圍查詢。

  • 在做範圍查找的時候,平衡樹操作相對複雜,先查找出小值,之後中序遍歷查找不超過範圍最大值的節點。如果不是個完全平衡二叉樹,這裏的中序遍歷並不簡單。而跳錶找到最小值以後,直接進行若干個遍歷就ok找到範圍最大值。
  • 平衡樹插入刪除操作都可能引發子樹的調整,邏輯調整,左旋右旋之類的。而跳錶只要改指針
  • 查找單個key,hash的時間複雜度是O(1),是最快的。
  • 算法實現上,跳錶簡單得多。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章