1. 程式人生 > >高併發架構與分散式技術NoSQL -- Redis原理剖析

高併發架構與分散式技術NoSQL -- Redis原理剖析

首先奉獻出微信 java後端技術 公眾號裡的學習腦圖,接下來的內容將會按照該圖進行自學梳理。

redis原理剖析

Redis是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫。它可以用作資料庫、快取和訊息中介軟體。 

  1. /*
  2. * Redis 物件
  3. */
  4. typedef struct redisObject {
  5. // 型別
  6. unsigned type:4;
  7. // 不使用(對齊位)
  8. unsigned notused:2;
  9. // 編碼方式
  10. unsigned encoding:4;
  11. // LRU 時間(相對於 server.lruclock)
  12. unsigned lru:22;
  13. // 引用計數
  14. int refcount;
  15. // 指向物件的值
  16. void *ptr;
  17. } robj;

它支援多種型別的資料結構,如 字串(strings), 雜湊(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 與範圍查詢, bitmaps, hyperloglogs 和 地理空間(geospatial) 索引半徑查詢。 

字串

(strings)

一個很基礎的資料型別

如果一個字串的內容可以轉換為long,那麼該字串就會被轉換成為long型別,物件的ptr就會指向該long,並且物件型別也用int型別表示。

普通的字串有兩種,embstr和raw。

如果字串物件的長度小於39位元組,就用embstr(embstr 編碼的簡單動態字串)物件。

embstr建立只需要分配一次記憶體。

否則用傳統的raw(簡單動態字串)物件。

raw建立需要分配兩次記憶體

字串列表(lists)

redis的另一個重要的資料結構叫做lists,翻譯成中文叫做“列表”。

列表物件可以是ziplist和linkedlist

ziplist是一種壓縮連結串列儲存在連續的記憶體區域中,可以節省空間,但是資料量不能太大。

linkedlist是一種雙向連結串列

redis中的lists在底層實現是連結串列,在頭部和尾部插入一個新元素,其時間複雜度是常數級別的,

但是元素定位會比較慢 。

lists的常用操作包括LPUSH、RPUSH、LRANGE等。

用LPUSH在lists的左側插入一個新元素,

用RPUSH在lists的右側插入一個新元素,

用LRANGE命令從lists中指定一個範圍來提取元素。

        1.我們可以利用lists來實現一個訊息佇列,而且可以確保先後順序,不必像MySQL那樣還需要通過ORDER BY來進行排序。

        2.利用LRANGE還可以很方便的實現分頁的功能。        3.在部落格系統中,每片博文的評論也可以存入一個單獨的list中。
字串集合(sets)

redis的集合,是一種無序的集合,集合中的元素沒有先後順序。

集合相關的操作也很豐富,如新增新元素、刪除已有元素、取交集、取並集、取差集等

有序字串集合(sorted sets)

redis不但提供了無需集合(sets),還很體貼的提供了有序集合(sorted sets)。有序集合的編碼可能兩種,一種是ziplist,另一種是skiplist與dict的結合。ziplist作為集合和作為雜湊物件是一樣的,member和score順序存放。按照score從小到大順序排列。它的結構不再複述。skiplist是一種跳躍表,它實現了有序集合中的快速查詢,在大多數情況下它的速度都可以和平衡樹差不多。但它的實現比較簡單,可以作為平衡樹的替代品。它的結構比較特殊。

有序集合中的每個元素都關聯一個序號(score),這便是排序的依據。

很多時候,我們都將redis中的有序集合叫做zsets,這是因為在redis中,有序集合相關的操作指令都是以z開頭的,比如zrange、zadd、zrevrange、zrangebyscore等等

雜湊

(hashes)

雜湊物件的底層實現可以是ziplist或者hashtable。

ziplist中的雜湊物件是按照key1,value1,key2,value2這樣的順序存放來儲存的。當物件數目不多且內容不大時,這種方式效率是很高的。

hashtable的是由dict這個結構來實現的

dict是一個字典,其中的指標dicht ht[2] 指向了兩個雜湊表

dicht[0] 是用於真正存放資料,dicht[1]一般在雜湊表元素過多進行rehash的時候用於中轉資料。

hashes存的是字串和字串值之間的對映,比如一個使用者要儲存其全名、姓氏、年齡等等,就很適合使用雜湊。

評價雜湊演算法好壞的四個定義:

平衡性雜湊的結果可以儘可能的分佈到所有的緩衝中去。這樣可以使所有的快取空間都充分利用。
單調性單調性是指如果已經有一些內容通過雜湊分派到了相應的緩衝中,又有新的緩衝加入到系統中。雜湊的結果應能夠保證原有已分配的內容可以被對映到原有的或者新的緩衝中去,而不會被對映到舊的緩衝集合中的其他緩衝區。 
分散性在分散式環境中,終端有可能看不到所有的緩衝,而是隻能看到其中的一部分。當終端希望通過雜湊過程將內容對映到緩衝上時,由於不同終端所見的緩衝範圍有可能不同,從而導致雜湊的結果不一致。分散性的定義就是上述情況發生的嚴重程度。好的雜湊演算法應能夠儘量避免不一致的情況發生,也就是儘量降低分散性。
負載負載問題實際上是從另一個角度看待分散性問題。既然不同的終端可能將相同的內容對映到不同的緩衝區中,那麼對於一個特定的緩衝區而言,也可能被不同的使用者對映為不同 的內容。與分散性一樣,這種情況也是應當避免的,因此好的雜湊演算法應能夠儘量降低緩衝的負荷。
redis叢集方案:一致性hash演算法 設計目標是為了解決因特網中的熱點(Hot spot)問題

一致性雜湊演算法是如何設計:

環形Hash空間按照常用的hash演算法來將對應的key雜湊到一個具有2^32次方個桶的空間中,即0~(2^32)-1的數字空間中。現在我們可以將這些數字頭尾相連,想象成一個閉合的環形。如下圖                                                                         把資料通過一定的hash演算法處理後對映到環上現在我們將object1、object2、object3、object4四個物件通過特定的Hash函式計算出對應的key值,然後雜湊到Hash環上。如下圖:    Hash(object1) = key1;    Hash(object2) = key2;    Hash(object3) = key3;    Hash(object4) = key4;                                                           將機器通過hash演算法對映到環上在採用一致性雜湊演算法的分散式叢集中將新的機器加入,其原理是通過使用與物件儲存一樣的Hash演算法將機器也對映到環中(一般情況下對機器的hash計算是採用機器的IP或者機器唯一的別名作為輸入值),然後以順時針的方向計算,將所有物件儲存到離自己最近的機器中。假設現在有NODE1,NODE2,NODE3三臺機器,通過Hash演算法得到對應的KEY值,對映到環中,其示意圖如下:Hash(NODE1) = KEY1;Hash(NODE2) = KEY2;Hash(NODE3) = KEY3;                                                             通過上圖可以看出物件與機器處於同一雜湊空間中,這樣按順時針轉動object1儲存到了NODE1中,object3儲存到了NODE2中,object2、object4儲存到了NODE3中。在這樣的部署環境中,hash環是不會變更的,因此,通過算出物件的hash值就能快速的定位到對應的機器中,這樣就能找到物件真正的儲存位置了。機器的刪除與新增普通hash求餘演算法最為不妥的地方就是在有機器的新增或者刪除之後會照成大量的物件儲存位置失效,這樣就大大的不滿足單調性了。下面來分析一下一致性雜湊演算法是如何處理的。1. 節點(機器)的刪除    以上面的分佈為例,如果NODE2出現故障被刪除了,那麼按照順時針遷移的方法,object3將會被遷移到NODE3中,這樣僅僅是object3的對映位置發生了變化,其它的物件沒有任何的改動。如下圖:                                                              2. 節點(機器)的新增     如果往叢集中新增一個新的節點NODE4,通過對應的雜湊演算法得到KEY4,並對映到環中,如下圖:                                                                  通過按順時針遷移的規則,那麼object2被遷移到了NODE4中,其它物件還保持這原有的儲存位置。通過對節點的新增和刪除的分析,一致性雜湊演算法在保持了單調性的同時,還是資料的遷移達到了最小,這樣的演算法對分散式叢集來說是非常合適的,避免了大量資料遷移,減小了伺服器的的壓力。平衡性根據上面的圖解分析,一致性雜湊演算法滿足了單調性和負載均衡的特性以及一般hash演算法的分散性,但這還並不能當做其被廣泛應用的原由,因為還缺少了平衡性。下面將分析一致性雜湊演算法是如何滿足平衡性的。hash演算法是不保證平衡的,如上面只部署了NODE1和NODE3的情況(NODE2被刪除的圖),object1儲存到了NODE1中,而object2、object3、object4都儲存到了NODE3中,這樣就照成了非常不平衡的狀態。在一致性雜湊演算法中,為了儘可能的滿足平衡性,其引入了虛擬節點。    ——“虛擬節點”( virtual node )是實際節點(機器)在 hash 空間的複製品( replica ),一實際個節點(機器)對應了若干個“虛擬節點”,這個對應個數也成為“複製個數”,“虛擬節點”在 hash 空間中以hash值排列。以上面只部署了NODE1和NODE3的情況(NODE2被刪除的圖)為例,之前的物件在機器上的分佈很不均衡,現在我們以2個副本(複製個數)為例,這樣整個hash環中就存在了4個虛擬節點,最後物件對映的關係圖如下:                                                                 根據上圖可知物件的對映關係:object1->NODE1-1,object2->NODE1-2,object3->NODE3-2,object4->NODE3-1。通過虛擬節點的引入,物件的分佈就比較均衡了。那麼在實際操作中,正真的物件查詢是如何工作的呢?物件從hash到虛擬節點到實際節點的轉換如下圖:                                         “虛擬節點”的hash計算可以採用對應節點的IP地址加數字字尾的方式。例如假設NODE1的IP地址為192.168.1.100。引入“虛擬節點”前,計算 cache A 的 hash 值:Hash(“192.168.1.100”);引入“虛擬節點”後,計算“虛擬節”點NODE1-1和NODE1-2的hash值:Hash(“192.168.1.100#1”); // NODE1-1Hash(“192.168.1.100#2”); // NODE1-2