在上一篇文章中,主要學習了一下Redis的5種數據結構的底層實現原理,在這一篇中,將介紹Redis的持久化方式,與Memcached的區別,Redis3.0的集羣部署以及廣泛的應用場景。
Redis持久化方式
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的區別
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 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取模。
客戶端訪問
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狀態的時候,影響有下面幾條:
- 如果Key存在則成功處理
- 如果Key不存在,則返回客戶端ASK,僅當這次請求會轉向另一個節點,並不會刷新客戶端中node的映射關係,也就是說下次該客戶端請求該Key的時候,還會選擇MasterA節點
- 如果Key包含多個命令,如果都存在則成功處理,如果都不存在,則返回客戶端ASK,如果一部分存在,則返回客戶端TRYAGAIN,通知客戶端稍後重試,這樣當所有的Key都遷移完畢的時候客戶端重試請求的時候回得到ASK,然後經過一次重定向就可以獲取這批鍵
IMPORTING狀態(輸入狀態)
本例中的IMPORTING狀態是發生在MasterB節點中的一種槽的狀態,預備將槽從MasterA節點遷移到MasterB節點的時候,槽的狀態會首先變爲IMPORTING。IMPORTING狀態的槽對客戶端的行爲有下面一些影響:
- 正常命令會被MOVED重定向,如果是ASKING命令則命令會被執行,從而Key沒有在老的節點已經被遷移到新的節點的情況可以被順利處理;
- 如果Key不存在則新建;
- 沒有ASKING的請求和正常請求一樣被MOVED,這保證客戶端node映射關係出錯的情況下不會發生寫錯;
Redis事務實現
Redis
作爲一個內存型數據庫,同樣支持傳統數據庫的事務特性。這篇文章會從源代碼角度來分析Redis
中事務的實現原理。事務的概念:Redis
事務提供了一種將多個命令請求打包,然後一次性、按照順序地執行多個命令的機制,並且在事務執行的期間,服務器不會中斷事務而去執行其他不在事務中的命令請求,它會把事務中所有的命令都執行完畢纔會去執行其他的命令。Redis
中提供了multi
、discard
、exec
、watch
、unwatch
這幾個命令來實現事務的功能。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
。