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/BV1LJ411t7Ai 和https://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
壓縮表是用字節組成雙向鏈表,在內存中一起佔用一塊區域.下圖是一個真實的結構
解釋- 這個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),是最快的。
- 算法實現上,跳錶簡單得多。