Redis 原理及應用(2)--持久化方式、集羣管理、事務及與Memcached的對比

    在上一篇文章中,主要學習了一下Redis的5種數據結構的底層實現原理,在這一篇中,將介紹Redis的持久化方式,與Memcached的區別,Redis3.0的集羣部署以及廣泛的應用場景。

Redis持久化方式

redis默認創建16個數據庫,通過select語句可以切換數據庫。我們知道,Redis是一個內存數據庫,在內存中以K-V形式來存儲數據,但是它可以配置持久化選項來進行持久化,這也是Redis和Memecached的區別之一。Redis提供了兩種持久化方式:一種是RDB持久化(原理是將Reids在內存中的數據庫記錄定時dump到磁盤上的RDB持久化),另外一種是AOF持久化(原理是將Reids的操作日誌以追加的方式寫入文件)。
兩種的區別有:
RDB持久化
是指在指定的時間間隔內將內存中的數據集快照寫入磁盤,實際操作過程是fork一個子進程,先將數據集寫入臨時文件,寫入成功後,再替換之前的文件,用二進制壓縮存儲。
快照保存過程:
    1. redis調用fork,現在有了子進程和父進程。
    2. 父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。由於os的寫時複製機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時os會爲父進程要修改的頁面創建副本,而不是寫共享的頁面。所以子進程的地址空間內的數據是fork時刻整個數據庫的一個快照。
    3. 當子進程將快照寫入臨時文件完畢後,用臨時文件替換原來的快照文件,然後子進程退出(fork一個進程入內在也被複制了,即內存會是原來的兩倍)。
AOF持久化以日誌的形式記錄服務器所處理的每一個寫、刪除操作,查詢操作不會記錄,以文本的方式記錄,可以打開文件看到詳細的操作記錄。
用圖來表示爲:





其持久化的配置方式爲:
AOF:配置相關配置文件,寫操作會記錄到相應的文件中,配置多長時間存儲到硬盤:
AOF在Redis的配置文件中存在三種同步方式,它們分別是:
appendfsync always     #每次有數據修改發生時都會寫入AOF文件。
appendfsync everysec  #每秒鐘同步一次,該策略爲AOF的缺省策略。
appendfsync no          #從不同步。高效但是數據不會被持久化。

RDB快照存儲: 快照是默認的持久化方式。這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名 爲dump.rdb。可以通過配置設置自動做快照持久化的方式。我們可以配置redis在n秒內如果超過m個key被修改就自動做快照,下面是默認的快照保存配置:
save 900 1  #900秒內如果超過1個key被修改,則發起快照保存
save 300 10 #300秒內容如超過10個key被修改,則發起快照保存
save 60 10000 #60秒內容如超過10000個key被修改,則發起快照保存
也可以進行人工save rdb。

RDB與AOF的對比:
1. 相比於AOF機制,如果數據集很大,則RDB故障恢復的時間較短,啓動效率較高,而AOF需要執行其日誌,在數據量大地情況下,啓動效率會低。
2. RDB這種持久化方式可能會丟失部分數據,一旦系統在定時持久化之前出現宕機,那麼,距上一次持久化到現在的沒來得及寫入磁盤的數據都將會丟失。而AOF可以提高較高的一致性。
3.由於RDB是通過fork子進程來協助完成數據持久化工作的,因此,如果當數據集較大時,可能會導致整個服務器停止服務幾百毫秒,甚至是1秒鐘。造成其性能降低。

Redis與Memcached的區別

Memcached也是常用的NoSQL數據庫,與Redis很類似,接下來我們就來比較一下這兩種數據庫的差別。

1. redis與memcached相比,比僅支持簡單的key-value數據類型,同時還提供list,set,zset,hash等數據結構的存儲;
redis支持數據的備份,即master-slave模式的數據備份;
2. redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啓的時候可以再次加載進行使用等等
3. 性能對比:由於Redis只使用單核(單線程),而Memcached可以使用多核,所以平均每一個核上Redis在存儲小數據時比Memcached性能更高。而在100k以上的數據中,Memcached性能要高於Redis,雖然Redis最近也在存儲大數據的性能上進行優化,但是比起Memcached,還是稍有遜色。memcached是多線程,非阻塞IO複用的網絡模型,分爲監聽主線程和worker子線程。redis使用單線程的IO複用模型,自己封裝了一個簡單的AeEvent事件處理框架。
4 內存使用效率對比:使用簡單的key-value存儲的話,Memcached的內存利用率更高,而如果Redis採用hash結構來做key-value存儲,由於其組合式的壓縮,其內存利用率會高於Memcached。
5. 集羣管理的區別
對於每個點的解釋,我們可以參考此文章:Redis與Memcached區別。具體可以參考這篇文章,尤其是兩者在內存和網絡IO模型的解釋。

Redis Cluster分區的機制

Redis Cluster本身提供了自動將數據分散到Redis Cluster不同節點的能力,分區實現的關鍵點問題包括:如何將數據自動地打散到不同的節點,使得不同節點的存儲數據相對均勻;如何保證客戶端能夠訪問到正確的節點和數據;如何保證重新分片的過程中不影響正常服務。這篇文章通過了解這些問題來認識Redis Cluster分區實現原理。
Redis Cluster是由多個同時服務於一個數據集合的Redis實例組成的整體,對於用戶來說,用戶只關注這個數據集合,而整個數據集合的某個數據子集存儲在哪個節點對於用戶來說是透明的。Redis Cluster具有分佈式系統的特點,也具有分佈式系統如何實現高可用性與數據一致性的難點,由多個Redis實例組成的Redis Cluster結構通常如下:


部署多個節點,可以分佈式存儲數據,節點之間互相連接,Redis的每個節點複雜部分數據的存儲,同時每個節點爲了保證數據的高可用性,又會利用master-slaver結構來保證某個節點宕機後不丟失數據。(其中通過主從節點實現故障轉移與故障恢復,也master選舉等過程可以參考zookeeper的實現方式)。

關於Redis的容錯機制,先不在這討論,可以參考本博客後邊的系列:Zookeeper系列。

下邊主要分析一下Redis Cluser分區的實現原理
Redis Cluster分區實現原理主要關注三個問題,1)數據是如何被自動分散到不同的節點的;2)客戶端是如何能夠正確找到節點的;3)鍵空間遷移過程是怎麼樣的?其次是一些實現小細節,通過了解這些問題更好地了這些問題從而更好地認識Redis Cluster分區功能。

槽(slot)概念

Redis Cluster中有一個16384長度的槽的概念,他們的編號爲0、1、2、3……16382、16383。這個槽是一個虛擬的槽,並不是真正存在的。正常工作的時候,Redis Cluster中的每個Master節點都會負責一部分的槽,當有某個key被映射到某個Master負責的槽,那麼這個Master負責爲這個key提供服務,至於哪個Master節點負責哪個槽,這是可以由用戶指定的,也可以在初始化的時候自動生成(redis-trib.rb腳本)。這裏值得一提的是,在Redis Cluster中,只有Master才擁有槽的所有權,如果是某個Master的slave,這個slave只負責槽的使用,但是沒有所有權。

Redis Cluster怎麼知道哪些槽是由哪些節點負責的呢?某個Master又怎麼知道某個槽自己是不是擁有呢?

實現方式是:每個Master節點維護着一個(16384除8)個字節的位序列,Master節點用bit來標識對於某個槽自己是否擁有。擁有就將對應的位置1。

同時:集羣同時還維護着槽到集羣節點的映射,是由長度爲16384類型爲節點的數組實現的,槽編號爲數組的下標,數組內容爲集羣節點,這樣就可以很快地通過槽編號找到負責這個槽的master節點。位序列這個結構很精巧,即不浪費存儲空間,操作起來又很便捷。

鍵空間分佈基本算法

這裏講的是Redis Cluster如何將鍵空間分佈在不同的節點的,鍵空間意爲Redis Cluster所擁有用戶所有數據集合的鍵的取值範圍,這個範圍叫做鍵空間。提到空間分佈,必然會想到哈希算法,沒錯,通過哈希算法再加上取模運算可以將一個值固定地映射到某個區間,在這裏,這個區間叫做slots,區間由連續的slot組成。在Redis Cluster中,我們擁有16384個slot,這個數是固定的,我們存儲在Redis Cluster中的所有的鍵都會被映射到這些slot中。簡單來說就是和常規的hash算法一樣,也是hash取模。

客戶端訪問

Redis Cluster並不會代理查詢,那麼如果客戶端訪問了一個此master節點的key並不存在的節點,這個節點是怎麼處理的呢?比如我想獲取key爲msg的值,msg計算出來的槽編號爲254,當前節點正好不負責編號爲254的槽,那麼就會返回客戶端下面信息
GET msg
-MOVED 254 127.0.0.1:6381
表示客戶端想要的254槽由運行在IP爲127.0.0.1,端口爲6381的Master實例服務。如果根據key計算得出的槽恰好由當前節點負責,則當期節點會立即返回結果。這裏明確一下,沒有代理的Redis Cluster可能會導致客戶端兩次連接急羣中的節點才能找到正確的服務,推薦客戶端緩存連接,這樣最壞的情況是兩次往返通信。

重新分片(Resharding)

重新分片意爲槽到集羣節點的映射關係要改變,不變的是鍵到槽的映射關係,因此當重新分片的時候,如果槽中有鍵,那麼鍵也是要被移動到新的節點的。

槽遷移的過程中有一個不穩定狀態,這個不穩定狀態會有一些規則,這些規則定義客戶端的行爲,從而使得Redis Cluster不必宕機的情況下可以執行槽的遷移。下面這張圖描述了我們遷移編號爲1、2、3的槽的過程中,他們在MasterA節點和MasterB節點中的狀態。


MIGRATING狀態(遷移狀態)

本例中MIGRATING狀態是發生在MasterA節點中的一種槽的狀態,預備遷移槽的時候槽的狀態首先會變爲MIGRATING狀態,這種狀態的槽會實際產生什麼影響呢?當客戶端請求的某個Key所屬的槽處於MIGRATING狀態的時候,影響有下面幾條:

  1. 如果Key存在則成功處理
  2. 如果Key不存在,則返回客戶端ASK,僅當這次請求會轉向另一個節點,並不會刷新客戶端中node的映射關係,也就是說下次該客戶端請求該Key的時候,還會選擇MasterA節點
  3. 如果Key包含多個命令,如果都存在則成功處理,如果都不存在,則返回客戶端ASK,如果一部分存在,則返回客戶端TRYAGAIN,通知客戶端稍後重試,這樣當所有的Key都遷移完畢的時候客戶端重試請求的時候回得到ASK,然後經過一次重定向就可以獲取這批鍵

IMPORTING狀態(輸入狀態)

本例中的IMPORTING狀態是發生在MasterB節點中的一種槽的狀態,預備將槽從MasterA節點遷移到MasterB節點的時候,槽的狀態會首先變爲IMPORTING。IMPORTING狀態的槽對客戶端的行爲有下面一些影響:

  1. 正常命令會被MOVED重定向,如果是ASKING命令則命令會被執行,從而Key沒有在老的節點已經被遷移到新的節點的情況可以被順利處理;
  2. 如果Key不存在則新建;
  3. 沒有ASKING的請求和正常請求一樣被MOVED,這保證客戶端node映射關係出錯的情況下不會發生寫錯;

Redis事務實現

Redis作爲一個內存型數據庫,同樣支持傳統數據庫的事務特性。這篇文章會從源代碼角度來分析Redis中事務的實現原理。
事務的概念:Redis事務提供了一種將多個命令請求打包,然後一次性、按照順序地執行多個命令的機制,並且在事務執行的期間,服務器不會中斷事務而去執行其他不在事務中的命令請求,它會把事務中所有的命令都執行完畢纔會去執行其他的命令。
Redis中提供了multidiscardexecwatchunwatch這幾個命令來實現事務的功能。
Redis的事務始於multi命令,之後跟着要在事務中執行的命令,終於exec命令或者discard命令。加入事務中的所有命令會原子的執行,中間不會穿插執行其他沒有加入事務的命令。
multi命令告訴Redis客戶端要開始一個事物,然後Redis會返回一個OK,接下來所有的命令Redis都不會立即執行,只會返回QUEUED結果,直到遇到了exec命令纔會去執行之前的所有的命令,或者遇到了discard命令,會拋棄執行之前加入事務的命令。

127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> get gender
(nil)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name Slogen
QUEUED
127.0.0.1:6379> set gender male
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379> mget name gender
1) "Slogen"
2) "male"
watch命令是Redis提供的一個樂觀鎖,可以在exec執行之前,監視任意數量的數據庫key,並在exec命令執行的時候,檢測被監視的key是否至少有一個已經被修改,如果是的話,服務器將拒絕執行事務,並向客戶端返回代表事務執行失敗的空回覆。
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> watch name
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name slogen
QUEUED
127.0.0.1:6379> set gender male
QUEUED
127.0.0.1:6379> get name
QUEUED
這個時候client還沒有執行exec命令,接下來在client2下執行下面命令修改name:
127.0.0.1:6379> set name rio
OK
127.0.0.1:6379> get name
"rio"
接下來在client1下執行exec命令:
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get name
"rio"
從執行結果可以看到,在client1中執行exec命令的時候,Redis會檢測到name字段已經被其他客戶端修改了,所以拒絕執行事務中所有的命令,直接返回nil表示執行失敗。這個時候獲取到的name的值還是在client2中設置的rio

ps:在本篇博客中,主要分析了Redis的持久化,集羣分區,事務及與Memcached的比較。在下一篇,我們將重點分析一下Redis的緩存淘汰機制,以及其經典的應用場景,瞭解其應用非常重要。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章