Redis PK Memcached,哪个更牛叉
轉(zhuǎn)載自?Redis PK Memcached,哪個更牛叉
說到 redis 就會聯(lián)想到 memcached,反之亦然。了解過兩者的同學(xué)有那么個大致的印象:
-
redis 與 memcached 相比,不僅支持簡單的 key-value 數(shù)據(jù)類型,同時還提供 list,set,zset,hash 等數(shù)據(jù)結(jié)構(gòu)的存儲;
-
redis 支持?jǐn)?shù)據(jù)的備份,即 master-slave 模式的數(shù)據(jù)備份;
-
redis 支持?jǐn)?shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保持在磁盤中,重啟的時候可以再次加載進(jìn)行使用等等。
這似乎看起來 redis 比 memcached 更加牛X一些,那么事實上是不是這樣的呢?
存在即合理,我們來根據(jù)幾個不同點來一一比較一下。
網(wǎng)絡(luò)IO模型
memcached 是多線程,非阻塞 IO 復(fù)用的網(wǎng)絡(luò)模型,分為監(jiān)聽主線程和 worker子線程,監(jiān)聽線程監(jiān)聽網(wǎng)絡(luò)連接,接受請求后,將連接描述字 pipe 傳遞給 worker 線程,進(jìn)行讀寫 IO。
網(wǎng)絡(luò)層使用 libevent 封裝的事件庫,多線程模型可以發(fā)揮多核作用,但是引入了 cache coherency 和鎖的問題,比如:memcached 最常用的 stats 命令,實際 memcached 所有操作都要對這個全局變量加鎖,進(jìn)行技術(shù)等工作,帶來了性能損耗。
redis 使用單線程的 IO 復(fù)用模型,自己封裝了一個簡單的 AeEvent 事件處理框架,主要實現(xiàn)了 epoll, kqueue 和 select,對于單存只有 IO 操作來說,單線程可以將速度優(yōu)勢發(fā)揮到最大。
但是 redis 也提供了一些簡單的計算功能,比如排序、聚合等,對于這些操作,單線程模型施加會嚴(yán)重影響整體吞吐量,CPU 計算過程中,整個 IO 調(diào)度都是被阻塞的。
數(shù)據(jù)支持類型
memcached 使用 key-value 形式存儲和訪問數(shù)據(jù),在內(nèi)存中維護(hù)一張巨大的 HashTable,使得對數(shù)據(jù)查詢的時間復(fù)雜度降低到O(1),保證了對數(shù)據(jù)的高性能訪問。
正如開篇所說:redis 與 memcached 相比,比僅支持簡單的 key-value 數(shù)據(jù)類型,同時還提供 list,set,zset,hash 等數(shù)據(jù)結(jié)構(gòu)的存儲;詳細(xì)可以翻閱《Redis 內(nèi)存使用優(yōu)化與存儲》
http://blog.csdn.net/u013256816/article/details/51133134
內(nèi)存管理機(jī)制
對于像 Redis 和 Memcached 這種基于內(nèi)存的數(shù)據(jù)庫系統(tǒng)來說,內(nèi)存管理的效率高低是影響系統(tǒng)性能的關(guān)鍵因素。
傳統(tǒng)C語言中的 malloc/free 函數(shù)是最常用的分配和釋放內(nèi)存的方法,但是這種方法存在著很大的缺陷:
-
首先,對于開發(fā)人員來說不匹配的 malloc 和 free 容易造成內(nèi)存泄露;
-
其次頻繁調(diào)用會造成大量內(nèi)存碎片無法回收重新利用,降低內(nèi)存利用率;
-
最后作為系統(tǒng)調(diào)用,其系統(tǒng)開銷遠(yuǎn)遠(yuǎn)大于一般函數(shù)調(diào)用。
所以,為了提高內(nèi)存的管理效率,高效的內(nèi)存管理方案都不會直接使用 malloc/free 調(diào)用。Redis 和 Memcached 均使用了自身設(shè)計的內(nèi)存管理機(jī)制,但是實現(xiàn)方法存在很大的差異,下面將會對兩者的內(nèi)存管理機(jī)制分別進(jìn)行介紹。
Memcached 默認(rèn)使用 Slab Allocation 機(jī)制管理內(nèi)存,其主要思想是按照預(yù)先規(guī)定的大小,將分配的內(nèi)存分割成特定長度的塊以存儲相應(yīng)長度的 key-value 數(shù)據(jù)記錄,以完全解決內(nèi)存碎片問題。
Slab Allocation 機(jī)制只為存儲外部數(shù)據(jù)而設(shè)計,也就是說所有的 key-value 數(shù)據(jù)都存儲在 Slab Allocation 系統(tǒng)里,而 Memcached 的其它內(nèi)存請求則通過普通的 malloc/free 來申請,因為這些請求的數(shù)量和頻率決定了它們不會對整個系統(tǒng)的性能造成影響 Slab Allocation 的原理相當(dāng)簡單。
如圖所示,它首先從操作系統(tǒng)申請一大塊內(nèi)存,并將其分割成各種尺寸的塊 Chunk,并把尺寸相同的塊分成組 Slab Class。其中,Chunk 就是用來存儲 key-value 數(shù)據(jù)的最小單位。
每個 Slab Class 的大小,可以在 Memcached 啟動的時候通過制定 Growth Factor 來控制。假定圖中 Growth Factor 的取值為1.25,如果第一組 Chunk 的大小為88個字節(jié),第二組 Chunk 的大小就為112個字節(jié),依此類推。
?
當(dāng) Memcached 接收到客戶端發(fā)送過來的數(shù)據(jù)時首先會根據(jù)收到數(shù)據(jù)的大小選擇一個最合適的 Slab Class,然后通過查詢 Memcached 保存著的該 Slab Class 內(nèi)空閑 Chunk 的列表就可以找到一個可用于存儲數(shù)據(jù)的 Chunk。
當(dāng)一條數(shù)據(jù)庫過期或者丟棄時,該記錄所占用的 Chunk 就可以回收,重新添加到空閑列表中。
從以上過程我們可以看出 Memcached 的內(nèi)存管理制效率高,而且不會造成內(nèi)存碎片,但是它最大的缺點就是會導(dǎo)致空間浪費(fèi)。因為每個 Chunk 都分配了特定長度的內(nèi)存空間,所以變長數(shù)據(jù)無法充分利用這些空間。
如圖 所示,將100個字節(jié)的數(shù)據(jù)緩存到128個字節(jié)的 Chunk 中,剩余的28個字節(jié)就浪費(fèi)掉了。
?
Redis 的內(nèi)存管理主要通過源碼中 zmalloc.h 和 zmalloc.c 兩個文件來實現(xiàn)的。
Redis 為了方便內(nèi)存的管理,在分配一塊內(nèi)存之后,會將這塊內(nèi)存的大小存入內(nèi)存塊的頭部。
如圖所示,real_ptr 是 redis 調(diào)用 malloc 后返回的指針。redis 將內(nèi)存塊的大小size 存入頭部,size 所占據(jù)的內(nèi)存大小是已知的,為 size_t 類型的長度,然后返回 ret_ptr。當(dāng)需要釋放內(nèi)存的時候,ret_ptr 被傳給內(nèi)存管理程序。
通過 ret_ptr,程序可以很容易的算出 real_ptr 的值,然后將 real_ptr 傳給 free 釋放內(nèi)存。
?
Redis 通過定義一個數(shù)組來記錄所有的內(nèi)存分配情況,這個數(shù)組的長度為ZMALLOC_MAX_ALLOC_STAT。數(shù)組的每一個元素代表當(dāng)前程序所分配的內(nèi)存塊的個數(shù),且內(nèi)存塊的大小為該元素的下標(biāo)。在源碼中,這個數(shù)組為 zmalloc_allocations。
zmalloc_allocations[16]代表已經(jīng)分配的長度為16bytes的內(nèi)存塊的個數(shù)。zmalloc.c 中有一個靜態(tài)變量 used_memory 用來記錄當(dāng)前分配的內(nèi)存總大小。
所以,總的來看,Redis 采用的是包裝的 mallc/free,相較于 Memcached 的內(nèi)存管理方法來說,要簡單很多。
在 Redis 中,并不是所有的數(shù)據(jù)都一直存儲在內(nèi)存中的。這是和 Memcached 相比一個最大的區(qū)別。當(dāng)物理內(nèi)存用完時,Redis 可以將一些很久沒用到的 value 交換到磁盤。
Redis 只會緩存所有的 key 的信息,如果 Redis 發(fā)現(xiàn)內(nèi)存的使用量超過了某一個閥值,將觸發(fā) swap 的操作,Redis 根據(jù)“swappability = age*log(size_in_memory)”計算出哪些 key 對應(yīng)的 value 需要 swap 到磁盤。
然后再將這些 key 對應(yīng)的 value 持久化到磁盤中,同時在內(nèi)存中清除。
這種特性使得 Redis 可以保持超過其機(jī)器本身內(nèi)存大小的數(shù)據(jù)。
當(dāng)然,機(jī)器本身的內(nèi)存必須要能夠保持所有的 key,畢竟這些數(shù)據(jù)是不會進(jìn)行 swap 操作的。
同時由于 Redis 將內(nèi)存中的數(shù)據(jù) swap 到磁盤中的時候,提供服務(wù)的主線程和進(jìn)行 swap 操作的子線程會共享這部分內(nèi)存,所以如果更新需要 swap 的數(shù)據(jù),Redis 將阻塞這個操作,直到子線程完成 swap 操作后才可以進(jìn)行修改。
當(dāng)從 Redis 中讀取數(shù)據(jù)的時候,如果讀取的 key 對應(yīng)的 value 不在內(nèi)存中,那么 Redis 就需要從 swap 文件中加載相應(yīng)數(shù)據(jù),然后再返回給請求方。這里就存在一個 I/O 線程池的問題。
在默認(rèn)的情況下,Redis 會出現(xiàn)阻塞,即完成所有的 swap 文件加載后才會相應(yīng)。這種策略在客戶端的數(shù)量較小,進(jìn)行批量操作的時候比較合適。
但是如果將 Redis 應(yīng)用在一個大型的網(wǎng)站應(yīng)用程序中,這顯然是無法滿足大并發(fā)的情況的。所以 Redis 運(yùn)行我們設(shè)置 I/O 線程池的大小,對需要從 swap 文件中加載相應(yīng)數(shù)據(jù)的讀取請求進(jìn)行并發(fā)操作,減少阻塞的時間。
Memcached 使用預(yù)分配的內(nèi)存池的方式,使用 slab 和大小不同的 chunk 來管理內(nèi)存,Item 根據(jù)大小選擇合適的 chunk 存儲,內(nèi)存池的方式可以省去申請/釋放內(nèi)存的開銷,并且能減小內(nèi)存碎片產(chǎn)生,但這種方式也會帶來一定程度上的空間浪費(fèi),并且在內(nèi)存仍然有很大空間時,新的數(shù)據(jù)也可能會被剔除,原因可以參考 Timyang 的文章:http://timyang.net/data/Memcached-lru-evictions/
Redis 使用現(xiàn)場申請內(nèi)存的方式來存儲數(shù)據(jù),并且很少使用 free-list 等方式來優(yōu)化內(nèi)存分配,會在一定程度上存在內(nèi)存碎片,Redis 跟據(jù)存儲命令參數(shù),會把帶過期時間的數(shù)據(jù)單獨(dú)存放在一起,并把它們稱為臨時數(shù)據(jù),非臨時數(shù)據(jù)是永遠(yuǎn)不會被剔除的,即便物理內(nèi)存不夠,導(dǎo)致 swap 也不會剔除任何非臨時數(shù)據(jù)(但會嘗試剔除部分臨時數(shù)據(jù)),這點上 Redis 更適合作為存儲而不是 cache。
數(shù)據(jù)存儲及持久化
memcached 不支持內(nèi)存數(shù)據(jù)的持久化操作,所有的數(shù)據(jù)都以 in-memory 的形式存儲。
redis 支持持久化操作。redis 提供了兩種不同的持久化方法來講數(shù)據(jù)存儲到硬盤里面,一種是快照(snapshotting),它可以將存在于某一時刻的所有數(shù)據(jù)都寫入硬盤里面。另一種方法叫只追加文件(append-only file, AOF),它會在執(zhí)行寫命令時,將被執(zhí)行的寫命令復(fù)制到硬盤里面。
數(shù)據(jù)一致性問題
Memcached 提供了 cas 命令,可以保證多個并發(fā)訪問操作同一份數(shù)據(jù)的一致性問題。
Redis 沒有提供 cas 命令,并不能保證這點,不過 Redis 提供了事務(wù)的功能,可以保證一串 命令的原子性,中間不會被任何操作打斷。
集群管理不同
Memcached 是全內(nèi)存的數(shù)據(jù)緩沖系統(tǒng),Redis 雖然支持?jǐn)?shù)據(jù)的持久化,但是全內(nèi)存畢竟才是其高性能的本質(zhì)。作為基于內(nèi)存的存儲系統(tǒng)來說,機(jī)器物理內(nèi)存的大小就是系統(tǒng)能夠容納的最大數(shù)據(jù)量。如果需要處理的數(shù)據(jù)量超過了單臺機(jī)器的物理內(nèi)存大小,就需要構(gòu)建分布式集群來擴(kuò)展存儲能力。
Memcached 本身并不支持分布式,因此只能在客戶端通過像一致性哈希這樣的分布式算法來實現(xiàn) Memcached 的分布式存儲。下圖給出了 Memcached 的分布式存儲實現(xiàn)架構(gòu)。
當(dāng)客戶端向 Memcached 集群發(fā)送數(shù)據(jù)之前,首先會通過內(nèi)置的分布式算法計算出該條數(shù)據(jù)的目標(biāo)節(jié)點,然后數(shù)據(jù)會直接發(fā)送到該節(jié)點上存儲。但客戶端查詢數(shù)據(jù)時,同樣要計算出查詢數(shù)據(jù)所在的節(jié)點,然后直接向該節(jié)點發(fā)送查詢請求以獲取數(shù)據(jù)。
相較于 Memcached 只能采用客戶端實現(xiàn)分布式存儲,Redis 更偏向于在服務(wù)器端構(gòu)建分布式存儲。最新版本的 Redis 已經(jīng)支持了分布式存儲功能。
Redis Cluster 是一個實現(xiàn)了分布式且允許單點故障的 Redis 高級版本,它沒有中心節(jié)點,具有線性可伸縮的功能。
Redis Cluster 的分布式存儲架構(gòu),節(jié)點與節(jié)點之間通過二進(jìn)制協(xié)議進(jìn)行通信,節(jié)點與客戶端之間通過 ascii 協(xié)議進(jìn)行通信。
在數(shù)據(jù)的放置策略上,Redis Cluster 將整個 key 的數(shù)值域分成4096個哈希槽,每個節(jié)點上可以存儲一個或多個哈希槽,也就是說當(dāng)前 Redis Cluster 支持的最大節(jié)點數(shù)就是4096。
Redis Cluster 使用的分布式算法也很簡單:crc16( key ) % HASH_SLOTS_NUMBER。
為了保證單點故障下的數(shù)據(jù)可用性,Redis Cluster 引入了 Master 節(jié)點和 Slave 節(jié)點。
在 Redis Cluster 中,每個 Master 節(jié)點都會有對應(yīng)的兩個用于冗余的 Slave 節(jié)點。這樣在整個集群中,任意兩個節(jié)點的宕機(jī)都不會導(dǎo)致數(shù)據(jù)的不可用。當(dāng) Master 節(jié)點退出后,集群會自動選擇一個 Slave 節(jié)點成為新的 Master 節(jié)點。
總結(jié)
以上是生活随笔為你收集整理的Redis PK Memcached,哪个更牛叉的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 仅 1579.05 元:追觅 H12 无
- 下一篇: Tomcat 的 Server 文件配置