Redis的作者Salvatore Sanfilippo曾對這兩種基于內存的數據存儲系統進行過比較:
具體為何會出現上面的結論,以下為搜集到的資料:
1、數據類型支持不同
與Memcached僅支持簡單的key-value結構的數據記錄不同,Redis支持的數據類型要豐富很多。最為經常使用的數據類型主要由5種:String、Hash、List、Set和Sorted Set。Redis內部使用1個redisObject對象來表示所有的key和value。redisObject最主要的信息如圖所示:
type代表1個value對象具體是何種數據類型,encoding是不同數據類型在redis內部的存儲方式,比如:type=string代表value存儲的是1個普通字符串,那末對應的encoding可以是raw或是int,如果是int則代表實際redis內部是按數值型類存儲和表示這個字符串的,固然條件是這個字符串本身可以用數值表示,比如:”123″ “456”這樣的字符串。只有打開了Redis的虛擬內存功能,vm字段字段才會真實的分配內存,該功能默許是關閉狀態的。
1)String
2)Hash
5)Sorted Set
2、內存管理機制不同
在Redis中,其實不是所有的數據都1直存儲在內存中的。這是和Memcached相比1個最大的區分。當物理內存用完時,Redis可以將1些很久沒用到的value交換到磁盤。Redis只會緩存所有的key的信息,如果Redis發現內存的使用量超過了某1個閥值,將觸發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文件中加載相應數據,然后再返回給要求方。 這里就存在1個I/O線程池的問題。在默許的情況下,Redis會出現阻塞,即完成所有的swap文件加載后才會相應。這類策略在客戶真個數量較小,進行批量操作的時候比較適合。但是如果將Redis利用在1個大型的網站利用程序中,這明顯是沒法滿足大并發的情況的。所以Redis運行我們設置I/O線程池的大小,對需要從swap文件中加載相應數據的讀取要求進行并發操作,減少阻塞的時間。
對像Redis和Memcached這類基于內存的數據庫系統來講,內存管理的效力高低是影響系統性能的關鍵因素。傳統C語言中的malloc/free函數是最經常使用的分配和釋放內存的方法,但是這類方法存在著很大的缺點:首先,對開發人員來講不匹配的malloc和free容易造成內存泄漏;其次頻繁調用會造成大量內存碎片沒法回收重新利用,下降內存利用率;最后作為系統調用,其系統開消遠遠大于1般函數調用。所以,為了提高內存的管理效力,高效的內存管理方案都不會直接使用malloc/free調用。Redis和Memcached均使用了本身設計的內存管理機制,但是實現方法存在很大的差異,下面將會對二者的內存管理機制分別進行介紹。
Memcached默許使用Slab Allocation機制管理內存,其主要思想是依照預先規定的大小,將分配的內存分割成特定長度的塊以存儲相應長度的key-value數據記錄,以完全解決內存碎片問題。Slab Allocation機制只為存儲外部數據而設計,也就是說所有的key-value數據都存儲在Slab Allocation系統里,而Memcached的其它內存要求則通過普通的malloc/free來申請,由于這些要求的數量和頻率決定了它們不會對全部系統的性能造成影響Slab Allocation的原理相當簡單。 如圖所示,它首先從操作系統申請1大塊內存,并將其分割成各種尺寸的塊Chunk,并把尺寸相同的塊分成組Slab Class。其中,Chunk就是用來存儲key-value數據的最小單位。每一個Slab Class的大小,可以在Memcached啟動的時候通過制定Growth Factor來控制。假定圖中Growth Factor的取值為1.25,如果第1組Chunk的大小為88個字節,第2組Chunk的大小就為112個字節,依此類推。
當Memcached接收到客戶端發送過來的數據時首先會根據收到數據的大小選擇1個最適合的Slab Class,然后通過查詢Memcached保存著的該Slab Class內空閑Chunk的列表就能夠找到1個可用于存儲數據的Chunk。當1條數據庫過期或拋棄時,該記錄所占用的Chunk就能夠回收,重新添加到空閑列表中。從以上進程我們可以看出Memcached的內存管理制效力高,而且不會造成內存碎片,但是它最大的缺點就是會致使空間浪費。由于每一個Chunk都分配了特定長度的內存空間,所以變長數據沒法充分利用這些空間。如圖 所示,將100個字節的數據緩存到128個字節的Chunk中,剩余的28個字節就浪費掉了。
Redis的內存管理主要通過源碼中zmalloc.h和zmalloc.c兩個文件來實現的。Redis為了方便內存的管理,在分配1塊內存以后,會將這塊內存的大小存入內存塊的頭部。如圖所示,real_ptr是redis調用malloc后返回的指針。redis將內存塊的大小size存入頭部,size所占據的內存大小是已知的,為size_t類型的長度,然后返回ret_ptr。當需要釋放內存的時候,ret_ptr被傳給內存管理程序。通過ret_ptr,程序可以很容易的算出real_ptr的值,然后將real_ptr傳給free釋放內存。
Redis通過定義1個數組來記錄所有的內存分配情況,這個數組的長度為ZMALLOC_MAX_ALLOC_STAT。數組的每個元素代表當前程序所分配的內存塊的個數,且內存塊的大小為該元素的下標。在源碼中,這個數組為zmalloc_allocations。zmalloc_allocations[16]代表已分配的長度為16bytes的內存塊的個數。zmalloc.c中有1個靜態變量used_memory用來記錄當前分配的內存總大小。所以,總的來看,Redis采取的是包裝的mallc/free,相較于Memcached的內存管理方法來講,要簡單很多。
3、數據持久化支持
Redis雖然是基于內存的存儲系統,但是它本身是支持內存數據的持久化的,而且提供兩種主要的持久化策略:RDB快照和AOF日志。而memcached是不支持數據持久化操作的。
1)RDB快照
Redis支持將當前數據的快照存成1個數據文件的持久化機制,即RDB快照。但是1個延續寫入的數據庫如何生成快照呢?Redis借助了fork命令的copy on write機制。在生成快照時,將當前進程fork出1個子進程,然后在子進程中循環所有的數據,將數據寫成為RDB文件。我們可以通過Redis的save指令來配置RDB快照生成的時機,比如配置10分鐘就生成快照,也能夠配置有1000次寫入就生成快照,也能夠多個規則1起實行。這些規則的定義就在Redis的配置文件中,你也能夠通過Redis的CONFIG SET命令在Redis運行時設置規則,不需要重啟Redis。
Redis的RDB文件不會壞掉,由于其寫操作是在1個新進程中進行的,當生成1個新的RDB文件時,Redis生成的子進程會先將數據寫到1個臨時文件中,然后通過原子性rename系統調用將臨時文件重命名為RDB文件,這樣在任什么時候候出現故障,Redis的RDB文件都總是可用的。同時,Redis的RDB文件也是Redis主從同步內部實現中的1環。RDB有他的不足,就是1旦數據庫出現問題,那末我們的RDB文件中保存的數據其實不是全新的,從上次RDB文件生成到Redis停機這段時間的數據全部丟掉了。在某些業務下,這是可以忍耐的。
2)AOF日志
AOF日志的全稱是append only file,它是1個追加寫入的日志文件。與1般數據庫的binlog不同的是,AOF文件是可辨認的純文本,它的內容就是1個個的Redis標準命令。只有那些會致使數據產生修改的命令才會追加到AOF文件。每條修改數據的命令都生成1條日志,AOF文件會愈來愈大,所以Redis又提供了1個功能,叫做AOF rewrite。其功能就是重新生成1份AOF文件,新的AOF文件中1條記錄的操作只會有1次,而不像1份老文件那樣,可能記錄了對同1個值的屢次操作。其生成進程和RDB類似,也是fork1個進程,直接遍歷數據,寫入新的AOF臨時文件。在寫入新文件的進程中,所有的寫操作日志還是會寫到原來老的AOF文件中,同時還會記錄在內存緩沖區中。當重完操作完成后,會將所有緩沖區中的日志1次性寫入到臨時文件中。然后調用原子性的rename命令用新的AOF文件取代老的AOF文件。
AOF是1個寫文件操作,其目的是將操作日志寫到磁盤上,所以它也一樣會遇到我們上面說的寫操作的流程。在Redis中對AOF調用write寫入后,通過appendfsync選項來控制調用fsync將其寫到磁盤上的時間,下面appendfsync的3個設置項,安全強度逐步變強。
對1般性的業務需求,建議使用RDB的方式進行持久化,緣由是RDB的開消并相比AOF日志要低很多,對那些沒法忍數據丟失的利用,建議使用AOF日志。
4、集群管理的不同
Memcached是全內存的數據緩沖系統,Redis雖然支持數據的持久化,但是全內存畢竟才是其高性能的本質。作為基于內存的存儲系統來講,機器物理內存的大小就是系統能夠容納的最大數據量。如果需要處理的數據量超過了單臺機器的物理內存大小,就需要構建散布式集群來擴大存儲能力。
Memcached本身其實不支持散布式,因此只能在客戶端通過像1致性哈希這樣的散布式算法來實現Memcached的散布式存儲。下圖給出了Memcached的散布式存儲實現架構。當客戶端向Memcached集群發送數據之前,首先會通過內置的散布式算法計算出該條數據的目標節點,然后數據會直接發送到該節點上存儲。但客戶端查詢數據時,一樣要計算出查詢數據所在的節點,然后直接向該節點發送查詢要求以獲得數據。
相較于Memcached只能采取客戶端實現散布式存儲,Redis更偏向于在服務器端構建散布式存儲。最新版本的Redis已支持了散布式存儲功能。Redis Cluster是1個實現了散布式且允許單點故障的Redis高級版本,它沒有中心節點,具有線性可伸縮的功能。下圖給出Redis Cluster的散布式存儲架構,其中節點與節點之間通過2進制協議進行通訊,節點與客戶端之間通過ascii協議進行通訊。在數據的放置策略上,Redis Cluster將全部key的數值域分成4096個哈希槽,每一個節點上可以存儲1個或多個哈希槽,也就是說當前Redis Cluster支持的最大節點數就是4096。Redis Cluster使用的散布式算法也很簡單:crc16( key ) % HASH_SLOTS_NUMBER。
為了保證單點故障下的數據可用性,Redis Cluster引入了Master節點和Slave節點。在Redis Cluster中,每一個Master節點都會有對應的兩個用于冗余的Slave節點。這樣在全部集群中,任意兩個節點的宕機都不會致使數據的不可用。當Master節點退出后,集群會自動選擇1個Slave節點成為新的Master節點。
參考資料: