Redis 的作者 Salvatore Sanfilippo 曾經對這兩種基於記憶體的資料儲存系統進行過比較:
Redis 支援站群伺服器端的資料操作:Redis 相比 Memcached 來說,擁有更多的資料結構和並支援更豐富的資料操作,通常在 Memcached 裡,你需要將資料拿到客戶端來進行類似的修改再 set 回去。這大大增加了網際網路 IO 的次數和資料體積。在 Redis 中,這些複雜的操作通常和一般的 GET/SET 一樣高效。所以,如果需要 WordPress 加速快取能夠支援更復雜的結構和操作,那麼 Redis 會是不錯的選擇。
記憶體使用效率對比:使用簡單的 key-value 儲存的話,Memcached 的記憶體利用率更高,而如果 Redis 採用 hash 結構來做 key-value 儲存,由於其組合式的壓縮,其記憶體利用率會高於 Memcached 。
效能對比:由於 Redis 只使用單核,而 Memcached 可以使用多核,所以平均每一個核上 Redis 在儲存小資料時比 Memcached 效能更高。而在 100k 以上的資料中,Memcached 效能要高於 Redis,雖然 Redis 最近也在儲存大資料的效能上進行最佳化,但是比起 Memcached,還是稍有遜色。
具體為什麼會出現上面的結論,以下為收集到的資料:
1 、資料型別支援不同
與 Memcached 僅支援簡單的 key-value 結構的資料記錄不同,Redis 支援的資料型別要豐富得多。最為常用的資料型別主要由五種:String 、 Hash 、 List 、 Set 和 Sorted Set 。 Redis 內部使用一個 redisObject 物件來表示所有的 key 和 value 。 redisObject 最主要的資訊如圖所示:
type 代表一個 value 物件具體是何種資料型別,encoding 是不同資料型別在 redis 內部的儲存方式,比如:type=string 代表 value 儲存的是一個普通字串,那麼對應的 encoding 可以是 raw 或者是 int,如果是 int 則代表實際 redis 內部是按數值型類儲存和表示這個字串的,當然前提是這個字串本身可以用數值表示,比如:”123″ “456” 這樣的字串。只有開啟了 Redis 的虛擬記憶體功能,vm 欄位欄位才會真正的分配記憶體,該功能預設是關閉狀態的。
1)String
常用命令:set/get/decr/incr/mget 等;
應用場景:String 是最常用的一種資料型別,普通的 key/value 儲存都可以歸為此類;
實現方式:String 在 redis 內部儲存預設就是一個字串,被 redisObject 所引用,當遇到 incr 、 decr 等操作時會轉成數值型進行計算,此時 redisObject 的 encoding 欄位為 int 。
2)Hash
常用命令:hget/hset/hgetall 等
應用場景:我們要儲存一個使用者資訊物件資料,其中包括使用者 ID 、使用者姓名、年齡和生日,透過使用者 ID 我們希望獲取該使用者的姓名或者年齡或者生日;
實現方式:Redis 的 Hash 實際是內部儲存的 Value 為一個 HashMap,並提供了直接存取這個 Map 成員的介面。如圖所示,Key 是使用者 ID, value 是一個 Map 。這個 Map 的 key 是成員的屬性名,value 是屬性值。這樣對資料的修改和存取都可以直接透過其內部 Map 的 Key(Redis 裡稱內部 Map 的 key 為 field), 也就是透過 key(使用者 ID) + field(屬性標籤) 就可以操作對應屬性資料。當前 HashMap 的實現有兩種方式:當 HashMap 的成員比較少時 Redis 為了節省記憶體會採用類似一維陣列的方式來緊湊儲存,而不會採用真正的 HashMap 結構,這時對應的 value 的 redisObject 的 encoding 為 zipmap,當成員數量增大時會自動轉成真正的 HashMap, 此時 encoding 為 ht 。
3)List
常用命令:lpush/rpush/lpop/rpop/lrange 等;
應用場景:Redis list 的應用場景非常多,也是 Redis 最重要的資料結構之一,比如 twitter 的關注列表,粉絲列表等都可以用 Redis 的 list 結構來實現;
實現方式:Redis list 的實現為一個雙向連結串列,即可以支援反向查詢和遍歷,更方便操作,不過帶來了部分額外的記憶體開銷,Redis 內部的很多實現,包括髮送緩衝佇列等也都是用的這個資料結構。
4)Set
常用命令:sadd/spop/smembers/sunion 等;
應用場景:Redis set 對外提供的功能與 list 類似是一個列表的功能,特殊之處在於 set 是可以自動排重的,當你需要儲存一個列表資料,又不希望出現重複資料時,set 是一個很好的選擇,並且 set 提供了判斷某個成員是否在一個 set 集合內的重要介面,這個也是 list 所不能提供的;
實現方式:set 的內部實現是一個 value 永遠為 null 的 HashMap,實際就是透過計算 hash 的方式來快速排重的,這也是 set 能提供判斷一個成員是否在集合內的原因。
5)Sorted Set
常用命令:zadd/zrange/zrem/zcard 等;
應用場景:Redis sorted set 的使用場景與 set 類似,區別是 set 不是自動有序的,而 sorted set 可以透過使用者額外提供一個優先順序 (score) 的引數來為成員排序,並且是插入有序的,即自動排序。當你需要一個有序的並且不重複的集合列表,那麼可以選擇 sorted set 資料結構,比如 twitter 的 public timeline 可以以發表時間作為 score 來儲存,這樣獲取時就是自動按時間排好序的。
實現方式:Redis sorted set 的內部使用 HashMap 和跳躍表 (SkipList) 來保證資料的儲存和有序,HashMap 裡放的是成員到 score 的對映,而跳躍表裡存放的是所有的成員,排序依據是 HashMap 裡存的 score, 使用跳躍表的結構可以獲得比較高的查詢效率,並且在實現上比較簡單。
2 、記憶體管理機制不同
在 Redis 中,並不是所有的資料都一直儲存在記憶體中的。這是和 Memcached 相比一個最大的區別。當實體記憶體用完時,Redis 可以將一些很久沒用到的 value 交換到磁碟。 Redis 只會 WordPress 加速快取所有的 key 的資訊,如果 Redis 發現記憶體的使用量超過了某一個閥值,將觸發 swap 的操作,Redis 根據 “swappability = age*log(size_in_memory)” 計算出哪些 key 對應的 value 需要 swap 到磁碟。然後再將這些 key 對應的 value 持久化到磁碟中,同時在記憶體中清除。這種特性使得 Redis 可以保持超過其機器本身記憶體大小的資料。當然,機器本身的記憶體必須要能夠保持所有的 key,畢竟這些資料是不會進行 swap 操作的。同時由於 Redis 將記憶體中的資料 swap 到磁碟中的時候,提供服務的主執行緒和進行 swap 操作的子執行緒會共享這部分記憶體,所以如果更新需要 swap 的資料,Redis 將阻塞這個操作,直到子執行緒完成 swap 操作後才可以進行修改。當從 Redis 中讀取資料的時候,如果讀取的 key 對應的 value 不在記憶體中,那麼 Redis 就需要從 swap 檔案中載入相應資料,然後再返回給請求方。 這裡就存在一個 I/O 執行緒池的問題。在預設的情況下,Redis 會出現阻塞,即完成所有的 swap 檔案載入後才會相應。這種策略在客戶端的數量較小,進行批次操作的時候比較合適。但是如果將 Redis 應用在一個大型的網站應用程式中,這顯然是無法滿足大併發的情況的。所以 Redis 執行我們設定 I/O 執行緒池的大小,對需要從 swap 檔案中載入相應資料的讀取請求進行併發操作,減少阻塞的時間。
對於像 Redis 和 Memcached 這種基於記憶體的資料庫系統來說,記憶體管理的效率高低是影響系統效能的關鍵因素。傳統 C 語言中的 malloc/free 函式是最常用的分配和釋放記憶體的方法,但是這種方法存在著很大的缺陷:首先,對於開發人員來說不匹配的 malloc 和 free 容易造成記憶體洩露;其次頻繁呼叫會造成大量記憶體碎片無法回收重新利用,降低記憶體利用率;最後作為系統呼叫,其系統開銷遠遠大於一般函式呼叫。所以,為了提高記憶體的管理效率,高效的記憶體管理方案都不會直接使用 malloc/free 呼叫。 Redis 和 Memcached 均使用了自身設計的記憶體管理機制,但是實現方法存在很大的差異,下面將會對兩者的記憶體管理機制分別進行介紹。
Memcached 預設使用 Slab Allocation 機制管理記憶體,其主要思想是按照預先規定的大小,將分配的記憶體分割成特定長度的塊以儲存相應長度的 key-value 資料記錄,以完全解決記憶體碎片問題。 Slab Allocation 機制只為儲存外部資料而設計,也就是說所有的 key-value 資料都儲存在 Slab Allocation 系統裡,而 Memcached 的其它記憶體請求則透過普通的 malloc/free 來申請,因為這些請求的數量和頻率決定了它們不會對整個系統的效能造成影響 Slab Allocation 的原理相當簡單。 如圖所示,它首先從作業系統申請一大塊記憶體,並將其分割成各種尺寸的塊 Chunk,並把尺寸相同的塊分成組 Slab Class 。其中,Chunk 就是用來儲存 key-value 資料的最小單位。每個 Slab Class 的大小,可以在 Memcached 啟動的時候透過制定 Growth Factor 來控制。假定圖中 Growth Factor 的取值為 1.25,如果第一組 Chunk 的大小為 88 個位元組,第二組 Chunk 的大小就為 112 個位元組,依此類推。
當 Memcached 接收到客戶端傳送過來的資料時首先會根據收到資料的大小選擇一個最合適的 Slab Class,然後透過查詢 Memcached 儲存著的該 Slab Class 內空閒 Chunk 的列表就可以找到一個可用於儲存資料的 Chunk 。當一條資料庫過期或者丟棄時,該記錄所佔用的 Chunk 就可以回收,重新新增到空閒列表中。
從以上過程我們可以看出 Memcached 的記憶體管理制效率高,而且不會造成記憶體碎片,但是它最大的缺點就是會導致空間浪費。因為每個 Chunk 都分配了特定長度的記憶體空間,所以變長資料無法充分利用這些空間。如圖 所示,將 100 個位元組的資料 WordPress 加速快取到 128 個位元組的 Chunk 中,剩餘的 28 個位元組就浪費掉了。
Redis 的記憶體管理主要透過原始碼中 zmalloc.h 和 zmalloc.c 兩個檔案來實現的。 Redis 為了方便記憶體的管理,在分配一塊記憶體之後,會將這塊記憶體的大小存入記憶體塊的頭部。如圖所示,real_ptr 是 redis 呼叫 malloc 後返回的指標。 redis 將記憶體塊的大小 size 存入頭部,size 所佔據的記憶體大小是已知的,為 size_t 型別的長度,然後返回 ret_ptr 。當需要釋放記憶體的時候,ret_ptr 被傳給記憶體管理程式。透過 ret_ptr,程式可以很容易的算出 real_ptr 的值,然後將 real_ptr 傳給 free 釋放記憶體。
Redis 透過定義一個陣列來記錄所有的記憶體分配情況,這個陣列的長度為 ZMALLOC_MAX_ALLOC_STAT 。陣列的每一個元素代表當前程式所分配的記憶體塊的個數,且記憶體塊的大小為該元素的下標。在原始碼中,這個陣列為 zmalloc_allocations 。 zmalloc_allocations[16] 代表已經分配的長度為 16bytes 的記憶體塊的個數。 zmalloc.c 中有一個靜態變數 used_memory 用來記錄當前分配的記憶體總大小。所以,總的來看,Redis 採用的是包裝的 mallc/free,相較於 Memcached 的記憶體管理方法來說,要簡單很多。
3 、資料持久化支援
Redis 雖然是基於記憶體的儲存系統,但是它本身是支援記憶體資料的持久化的,而且提供兩種主要的持久化策略:RDB 快照和 AOF 日誌。而 memcached 是不支援資料持久化操作的。
1)RDB 快照
Redis 支援將當前資料的快照存成一個資料檔案的持久化機制,即 RDB 快照。但是一個持續寫入的資料庫如何生成快照呢?Redis 藉助了 fork 命令的 copy on write 機制。在生成快照時,將當前程式 fork 出一個子程式,然後在子程式中迴圈所有的資料,將資料寫成為 RDB 檔案。我們可以透過 Redis 的 save 指令來配置 RDB 快照生成的時機,比如配置 10 分鐘就生成快照,也可以配置有 1000 次寫入就生成快照,也可以多個規則一起實施。這些規則的定義就在 Redis 的配置檔案中,你也可以透過 Redis 的 CONFIG SET 命令在 Redis 執行時設定規則,不需要重啟 Redis 。
Redis 的 RDB 檔案不會壞掉,因為其寫操作是在一個新程式中進行的,當生成一個新的 RDB 檔案時,Redis 生成的子程式會先將資料寫到一個臨時檔案中,然後透過原子性 rename 系統呼叫將臨時檔案重新命名為 RDB 檔案,這樣在任何時候出現故障,Redis 的 RDB 檔案都總是可用的。同時,Redis 的 RDB 檔案也是 Redis 主從同步內部實現中的一環。 RDB 有他的不足,就是一旦資料庫出現問題,那麼我們的 RDB 檔案中儲存的資料並不是全新的,從上次 RDB 檔案生成到 Redis 停機這段時間的資料全部丟掉了。在某些業務下,這是可以忍受的。
2)AOF 日誌
AOF 日誌的全稱是 append only file,它是一個追加寫入的日誌檔案。與一般資料庫的 binlog 不同的是,AOF 檔案是可識別的純文字,它的內容就是一個個的 Redis 標準命令。只有那些會導致資料發生修改的命令才會追加到 AOF 檔案。每一條修改資料的命令都生成一條日誌,AOF 檔案會越來越大,所以 Redis 又提供了一個功能,叫做 AOF rewrite 。其功能就是重新生成一份 AOF 檔案,新的 AOF 檔案中一條記錄的操作只會有一次,而不像一份老檔案那樣,可能記錄了對同一個值的多次操作。其生成過程和 RDB 類似,也是 fork 一個程式,直接遍歷資料,寫入新的 AOF 臨時檔案。在寫入新檔案的過程中,所有的寫操作日誌還是會寫到原來老的 AOF 檔案中,同時還會記錄在記憶體緩衝區中。當重完操作完成後,會將所有緩衝區中的日誌一次性寫入到臨時檔案中。然後呼叫原子性的 rename 命令用新的 AOF 檔案取代老的 AOF 檔案。
AOF 是一個寫檔案操作,其目的是將操作日誌寫到磁碟上,所以它也同樣會遇到我們上面說的寫操作的流程。在 Redis 中對 AOF 呼叫 write 寫入後,透過 appendfsync 選項來控制呼叫 fsync 將其寫到磁碟上的時間,下面 appendfsync 的三個設定項,安全強度逐漸變強。
appendfsync no 當設定 appendfsync 為 no 的時候,Redis 不會主動呼叫 fsync 去將 AOF 日誌內容同步到磁碟,所以這一切就完全依賴於作業系統的除錯了。對大多數 Linux 作業系統,是每 30 秒進行一次 fsync,將緩衝區中的資料寫到磁碟上。
appendfsync everysec 當設定 appendfsync 為 everysec 的時候,Redis 會預設每隔一秒進行一次 fsync 呼叫,將緩衝區中的資料寫到磁碟。但是當這一次的 fsync 呼叫時長超過 1 秒時。 Redis 會採取延遲 fsync 的策略,再等一秒鐘。也就是在兩秒後再進行 fsync,這一次的 fsync 就不管會執行多長時間都會進行。這時候由於在 fsync 時檔案描述符會被阻塞,所以當前的寫操作就會阻塞。所以結論就是,在絕大多數情況下,Redis 會每隔一秒進行一次 fsync 。在最壞的情況下,兩秒鐘會進行一次 fsync 操作。這一操作在大多數資料庫系統中被稱為 group commit,就是組合多次寫操作的資料,一次性將日誌寫到磁碟。
appednfsync always 當設定 appendfsync 為 always 時,每一次寫操作都會呼叫一次 fsync,這時資料是最安全的,當然,由於每次都會執行 fsync,所以其效能也會受到影響。
對於一般性的業務需求,建議使用 RDB 的方式進行持久化,原因是 RDB 的開銷並相比 AOF 日誌要低很多,對於那些無法忍資料丟失的應用,建議使用 AOF 日誌。
4 、叢集管理的不同
Memcached 是全記憶體的資料緩衝系統,Redis 雖然支援資料的持久化,但是全記憶體畢竟才是其高效能的本質。作為基於記憶體的儲存系統來說,機器實體記憶體的大小就是系統能夠容納的最大資料量。如果需要處理的資料量超過了單臺機器的實體記憶體大小,就需要構建分散式叢集來擴充套件儲存能力。
Memcached 本身並不支援分散式,因此只能在客戶端透過像一致性雜湊這樣的分散式演演算法來實現 Memcached 的分散式儲存。下圖給出了 Memcached 的分散式儲存實現架構。當客戶端向 Memcached 叢集傳送資料之前,首先會透過內建的分散式演演算法計算出該條資料的目標節點,然後資料會直接傳送到該節點上儲存。但客戶端查詢資料時,同樣要計算出查詢資料所在的節點,然後直接向該節點傳送查詢請求以獲取資料。
相較於 Memcached 只能採用客戶端實現分散式儲存,Redis 更偏向於在站群伺服器端構建分散式儲存。最新版本的 Redis 已經支援了分散式儲存功能。 Redis Cluster 是一個實現了分散式且允許單點故障的 Redis 高階版本,它沒有中心節點,具有線性可伸縮的功能。下圖給出 Redis Cluster 的分散式儲存架構,其中節點與節點之間透過二進位制協議進行通訊,節點與客戶端之間透過 ascii 協議進行通訊。在資料的放置策略上,Redis Cluster 將整個 key 的數值域分成 4096 個雜湊槽,每個節點上可以儲存一個或多個雜湊槽,也就是說當前 Redis Cluster 支援的最大節點數就是 4096 。 Redis Cluster 使用的分散式演演算法也很簡單:crc16( key ) % HASH_SLOTS_NUMBER 。
為了保證單點故障下的資料可用性,Redis Cluster 引入了 Master 節點和 Slave 節點。在 Redis Cluster 中,每個 Master 節點都會有對應的兩個用於冗餘的 Slave 節點。這樣在整個叢集中,任意兩個節點的宕機都不會導致資料的不可用。當 Master 節點退出後,叢集會自動選擇一個 Slave 節點成為新的 Master 節點。
參考資料:
http://www.redisdoc.com/en/latest/
http://memcached.org/
原文來自:http://h2ex.com/1223
本文地址:http://www.linuxprobe.com/redisVSmemcached.html