天天看點

【轉】談談 Memcached 與 Redis

截取文章的一小部分,原文位址:[url]http://blog.csdn.net/tonysz126/article/details/8280696/[/url]

[size=large][b]1. Memcached 簡介[/b][/size]

Memcached 是以 LiveJurnal 旗下 Danga Interactive 公司的 Bard Fitzpatric 為首開發的高性能分布式記憶體緩存伺服器。其本質上就是一個記憶體 key-value 資料庫,但是不支援資料的持久化,伺服器關閉之後資料全部丢失。 Memcached 使用 C 語言開發,在大多數像 Linux 、 BSD 和 Solaris 等 POSIX 系統上,隻要安裝了 libevent 即可使用。在 Windows 下,它也有一個可用的非官方版本(http://code.jellycan.com/memcached/)。 Memcached 的用戶端軟體實作非常多,包括 C/C++, PHP, Java, Python, Ruby, Perl, Erlang, Lua 等。目前 Memcached 使用廣泛,除了 LiveJournal 以外還有 Wikipedia, Flickr, Twitter, Youtube 和 WordPress 等。

在 Windows 系統下, Memcached 的安裝非常友善,隻需從以上給出的位址下載下傳可執行軟體然後運作 memcached.exe –d install 即可完成安裝。在 Linux 等系統下,我們首先需要安裝 libevent ,然後擷取源碼, make && make install 即可。預設情況下, Memcached 的伺服器啟動程式會安裝到 /usr/local/bin 目錄下。在啟動 Memcached 時,我們可以為其配置不同的啟動參數。

[b]1.1 Memcache配置(略)[/b]

[size=large][b]2. Redis簡介[/b][/size]

Redis 是一個開源的 key-value 存儲系統。與 Memcached 類似,Redis 将大部分資料存儲在記憶體中,支援的資料類型包括:字元串、哈希表、連結清單、集合、有序集合以及基于這些資料類型的相關操作。 Redis 使用 C 語言開發,在大多數像 Linux 、 BSD 和 Solaris 等 POSIX 系統上無需任何外部依賴就可以使用。 Redis 支援的用戶端語言也非常豐富,常用的計算機語言如 C, C#, C++, Object-C, PHP, Python, Java, Perl, Lua, Erlang 等均有可用的用戶端來通路 Redis 伺服器。目前 Redis 的應用已經非常廣泛,國内像新浪、淘寶,國外像Flickr, Github 等均在使用 Redis 的緩存服務。

Redis 的安裝非常友善,隻需從 [url]http://redis.io/download[/url] 擷取源碼,然後 make && make install 即可。預設情況下, Redis 的伺服器啟動程式和用戶端程式會安裝到 /usr/local/bin 目錄下。在啟動 Redis 伺服器時,我們需要為其指定一個配置檔案,預設情況下配置檔案在 Redis 的源碼目錄下,檔案名為 redis.conf 。

[b]2.1 Redis 配置檔案(略)[/b]

[b]2.2 Redis 的常用資料類型[/b]

與 Memcached 僅支援簡單的 key-value 結構的資料記錄不同, Redis 支援的資料類型要豐富得多。最為常用的資料類型主要由五種: String 、 Hash 、 List 、 Set 和 Sorted Set 。在具體描述這幾種資料類型之前,我們先通過一張圖來了解下 Redis 内部記憶體管理中是如何描述這些不同資料類型的。

[img]http://s4.sinaimg.cn/orignal/48c95a19gc513a3474e83&690[/img]

(圖1 Redis 對象)

Redis 内部使用一個 redisObject 對象來表示所有的 key 和 value 。 redisObject 最主要的資訊如圖1所示: type 代表一個 value 對象具體是何種資料類型, encoding 是不同資料類型在 redis 内部的存儲方式,比如: type=string 代表 value 存儲的是一個普通字元串,那麼對應的 encoding 可以是 raw 或者是 int ,如果是 int 則代表實際 redis 内部是按數值型類存儲和表示這個字元串的,當然前提是這個字元串本身可以用數值表示,比如“123”、“456”這樣的字元串。這裡需要特殊說明一下 vm 字段,隻有打開了 Redis 的虛拟記憶體功能,此字段才會真正的配置設定記憶體,該功能預設是關閉狀态的。通過 Figure1 我們可以發現 Redis 使用 redisObject 來表示所有的 key/value 資料是比較浪費記憶體的,當然這些記憶體管理成本的付出主要也是為了給 Redis 不同資料類型提供一個統一的管理接口,實際作者也提供了多種方法幫助我們盡量節省記憶體使用。下面我們先來逐一的分析下這五種資料類型的使用和内部實作方式。

[b]1) String[/b]

常用指令: set/get/decr/incr/mget 等;

應用場景: String 是最常用的一種資料類型,普通的 key/value 存儲都可以歸為此類;

實作方式: String 在 redis 内部存儲預設就是一個字元串,被 redisObject 所引用,當遇到 incr 、 decr 等操作時會轉成數值型進行計算,此時 redisObject 的 encoding 字段為int 。

[b]2) Hash[/b]

常用指令: hget/hset/hgetall 等;

應用場景:我們要存儲一個使用者資訊對象資料,其中包括使用者 ID 、姓名、年齡和生日,通過使用者 ID 我們希望擷取該使用者的姓名或者年齡或者生日;

實作方式: Redis 的 Hash 實際是内部存儲的 Value 為一個 HashMap ,并提供了直接存取這個 Map 成員的接口。如圖2所示, Key 是使用者 ID , value 是一個 Map 。這個 Map 的 key 是成員的屬性名, value 是屬性值。這樣對資料的修改和存取都可以直接通過其内部 Map 的 Key ( Redis 裡稱内部 Map 的 key 為 field ),也就是通過 key + field 就可以操作對應屬性資料。目前 HashMap 的實作有兩種方式:當 HashMap 的成員比較少時, Redis 為了節省記憶體會采用類似一維數組的方式來緊湊存儲,而不會采用真正的 HashMap 結構,這時對應的 value 的 redisObject 的 encoding 為 zipmap 。當成員數量增大時會自動轉成真正的 HashMap 時, encoding 為 ht 。

[img]http://s2.sinaimg.cn/orignal/48c95a19gc513abeb9bb1&690[/img]

(圖2 Redis的Hash資料類型)

[b]3) List[/b]

常用指令: lpush/rpush/lpop/rpop/lrange 等;

應用場景: Redis list 的應用場景非常多,也是 Redis 最重要的資料結構之一,比如 twitter 的關注清單、粉絲清單等都可以用 Redis 的 list 結構來實作;

實作方式: Redis list 的實作為一個雙向連結清單,即可以支援反向查找和周遊,更友善操作,不過帶來了部分額外的記憶體開銷, Redis 内部的很多實作,包括發送緩沖隊列等也都是用的這個資料結構。

[b]4) Set[/b]

常用指令: sadd/spop/smembers/sunion 等;

應用場景: Redis set 對外提供的功能與 list 類似是一個清單的功能,特殊之處在于 set 是可以自動排重的,當你需要存儲一個清單資料,又不希望出現重複資料時, set是一個很好的選擇,并且 set 提供了判斷某個成員是否在一個 set 集合内的重要接口,這個也是 list 所不能提供的;

實作方式: set 的内部實作是一個 value 永遠為 null 的 HashMap ,實際就是通過計算 hash 的方式來快速排重的,這也是 set 能提供判斷一個成員是否在集合内的原因。

[b]5) Sorted Set[/b]

常用指令: 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 。使用跳躍表的結構可以獲得比較高的查找效率,并且在實作上比較簡單。

[b]2.3 Redis 的持久化[/b]

Redis 雖然是基于記憶體的存儲系統,但是它本身是支援記憶體資料的持久化的,而且提供兩種主要的持久化政策: RDB 快照和 AOF 日志。我們會在下文分别介紹這兩種不同的持久化政策。

2.3.1 Redis 的 AOF 日志

Redis 支援将目前資料的快照存成一個資料檔案的持久化機制,即 RDB 快照。這種方法是非常好了解的,但是一個持續寫入的資料庫如何生成快照呢? Redis 借助了 fork 指令的 copy on write 機制。在生成快照時,将目前程序 fork 出一個子程序,然後在子程序中循環所有的資料,将資料寫成為 RDB 檔案。

我們可以通過 Redis 的 save 指令來配置 RDB 快照生成的時機,比如你可以配置當10分鐘以内有100次寫入就生成快照,也可以配置當1小時内有1000次寫入就生成快照,也可以多個規則一起實施。這些規則的定義就在 Redis 的配置檔案中,你也可以通過 Redis 的 CONFIG SET 指令在 Redis 運作時設定規則,不需要重新開機 Redis 。

Redis 的 RDB 檔案不會壞掉,因為其寫操作是在一個新程序中進行的,當生成一個新的 RDB 檔案時, Redis 生成的子程序會先将資料寫到一個臨時檔案中,然後通過原子性 rename 系統調用将臨時檔案重命名為 RDB 檔案,這樣在任何時候出現故障, Redis 的 RDB 檔案都總是可用的。同時, Redis 的 RDB 檔案也是 Redis 主從同步内部實作中的一環。

但是,我們可以很明顯的看到, RDB 有它的不足,就是一旦資料庫出現問題,那麼我們的 RDB 檔案中儲存的資料并不是全新的,從上次 RDB 檔案生成到 Redis 停機這段時間的資料全部丢掉了。在某些業務下,這是可以忍受的,我們也推薦這些業務使用 RDB 的方式進行持久化,因為開啟 RDB 的代價并不高。但是對于另外一些對資料安全性要求極高的應用,無法容忍資料丢失的應用, RDB 就無能為力了,是以 Redis 引入了另一個重要的持久化機制: AOF 日志。

【補充】什麼是 Linux 的程序 fork ?

一個現有程序可以調用 fork 函數建立一個新程序。由 fork 建立的新程序被稱為子程序( child process )。fork 函數被調用一次但傳回兩次。兩次傳回的唯一差別是子程序中傳回0值而父程序中傳回子程序 ID 。

[color=red]子程序是父程序的副本,它将獲得父程序資料空間、堆、棧等資源的副本。父子程序間共享的存儲空間隻有代碼段。[/color]

2.3.2 Redis 的 AOF 日志

AOF 日志的全稱是 append only file ,從名字上我們就能看出來,它是一個追加寫入的日志檔案。與一般資料庫的 binlog 不同的是, AOF 檔案是可識别的純文字,它的内容就是一個個的 Redis 标準指令。當然,并不是發送到 Redis 的所有指令都要記錄到 AOF 日志裡面,隻有那些會導緻資料發生修改的指令才會追加到 AOF 檔案。那麼每一條修改資料的指令都生成一條日志,那麼 AOF 檔案是不是會很大?答案是肯定的, AOF 檔案會越來越大,是以 Redis 又提供了一個功能,叫做 AOF rewrite 。其功能就是重新生成一份 AOF 檔案,新的AOF 檔案中一條記錄的操作隻會有一次,而不像一份老檔案那樣,可能記錄了對同一個值的多次操作。其生成過程和 RDB 類似,也是 fork 一個程序,直接周遊資料,寫入新的 AOF 臨時檔案。在寫入新檔案的過程中,所有的寫記錄檔還是會寫到原來老的 AOF 檔案中,同時還會記錄在記憶體緩沖區中。當重寫操作完成後,會将所有緩沖區中的日志一次性寫入到臨時檔案中。然後調用原子性的 rename 指令用新的 AOF 檔案取代老的 AOF 檔案。

AOF 是一個寫檔案操作,其目的是将記錄檔寫到磁盤上,是以它也同樣會遇到我們上面說的寫操作的5個流程。那麼寫 AOF 的操作安全性又有多高呢。實際上這是可以設定的,在 Redis 中對 AOF 調用 write(2) 寫入後,何時再調用 fsync 将其寫到磁盤上,通過 appendfsync 選項來控制,下面 appendfsync 的三個設定項,安全強度逐漸變強。

1) appendfsync no

當設定 appendfsync 為 no 的時候, Redis 不會主動調用 fsync 去将 AOF 日志内容同步到磁盤,是以這一切就完全依賴于作業系統的排程了。對大多數 Linux 作業系統,是每30秒進行一次 fsync ,将緩沖區中的資料寫到磁盤上。

2) appendfsync everysec

當設定 appendfsync 為 everysec 的時候, Redis 會預設每隔一秒進行一次 fsync 調用,将緩沖區中的資料寫到磁盤。但是當這一次的 fsync 調用時長超過1秒時, Redis 會采取延遲 fsync 的政策,再等一秒鐘。也就是在兩秒後再進行 fsync ,這一次的 fsync 就不管會執行多長時間都會進行。這時候由于在 fsync 時檔案描述符會被阻塞,是以目前的寫操作就會阻塞。是以結論就是,在絕大多數情況下, Redis 會每隔一秒進行一次 fsync 。在最壞的情況下,兩秒鐘會進行一次 fsync 操作。這一操作在大多數資料庫系統中被稱為 group commit ,就是組合多次寫操作的資料,一次性将日志寫到磁盤。

3) appendfsync always

當設定 appendfsync 為 always 時,每一次寫操作都會調用一次 fsync ,這時資料是最安全的,當然,由于每次都會執行 fsync ,是以其性能也會受到影響。

[b]3. Memcached 和 Redis 關鍵技術對比[/b]

作為記憶體資料緩沖系統, Memcached 和 Redis 均具有很高的性能,但是兩者在關鍵實作技術上具有很大差異,這種差異決定了兩者具有不同的特點和不同的适用條件。下面我們會對兩者的關鍵技術進行一些對比,以此來揭示兩者的差異。

[b]3.1 Memcached 和 Redis 的記憶體管理機制對比[/b]

對于像 Redis 和 Memcached 這種基于記憶體的資料庫系統來說,記憶體管理的效率高低是影響系統性能的關鍵因素。傳統 C 語言中的 malloc/free 函數是最常用的配置設定和釋放記憶體的方法,但是這種方法存在着很大的缺陷:首先,對于開發人員來說不比對的 malloc 和 free 容易造成記憶體洩露;其次,頻繁調用會造成大量記憶體碎片無法回收重新利用,降低記憶體使用率;最後,作為系統調用,其系統開銷遠遠大于一般函數調用。是以,為了提高記憶體的管理效率,高效的記憶體管理方案都不會直接使用 malloc/free 調用。 Redis 和 Memcached 均使用了自身設計的記憶體管理機制,但是實作方法存在很大的差異,下面将會對兩者的記憶體管理機制分别進行介紹。

3.1.1 Memcached 的記憶體管理機制

Memcached 預設使用 Slab Allocation 機制管理記憶體,其主要思想是按照預先規定的大小,将配置設定的記憶體分割成特定長度的塊以存儲相應長度的 key-value 資料記錄,以完全解決記憶體碎片問題。 Slab Allocation 機制隻為存儲外部資料而設計,也就是說所有的 key-value 資料都存儲在 Slab Allocation 系統裡,而 Memcached 的其它記憶體請求則通過普通的 malloc/free 來申請,因為這些請求的數量和頻率決定了它們不會對整個系統的性能造成太大影響。

Slab Allocation 的原理相當簡單。 如圖3所示,它首先從作業系統申請一大塊記憶體,并将其分割成各種尺寸的塊 Chunk ,并把尺寸相同的塊分成組 Slab Class 。其中, Chunk 就是用來存儲 key-value 資料的最小機關。每個 Slab Class 的大小,可以在 Memcached 啟動的時候通過制定 Growth Factor 來控制。假定 Figure1 中 Growth Factor 的取值為1.25,是以如果第一組 Chunk 的大小為 88 個位元組,第二組 Chunk 的大小就為 112 個位元組,依此類推。

[img]http://s16.sinaimg.cn/orignal/48c95a19g7a1b94e761ff&690[/img]

(圖3 Memcached 記憶體管理架構)

當 Memcached 接收到用戶端發送過來的資料時首先會根據收到資料的大小選擇一個最合适的 Slab Class ,然後通過查詢 Memcached 儲存着的該 Slab Class 内空閑 Chunk 的清單就可以找到一個可用于存儲資料的 Chunk 。當一條資料過期或者丢棄時,該記錄所占用的 Chunk 就可以回收,重新添加到空閑清單中。從以上過程我們可以看出 Memcached 的記憶體管理制效率高,而且不會造成記憶體碎片,但是它最大的缺點就是會導緻空間浪費。因為每個 Chunk 都配置設定了特定長度的記憶體空間,是以變長資料無法充分利用這些空間。如圖4所示,将100個位元組的資料緩存到128個位元組的 Chunk 中,剩餘的28個位元組就浪費掉了。

[img]http://s5.sinaimg.cn/orignal/48c95a19gc513c1448e64&690[/img]

(圖4 Memcached 的存儲空間浪費)

3.1.2 Redis 的記憶體管理機制

Redis 的記憶體管理主要通過源碼中 zmalloc.h 和 zmalloc.c 兩個檔案來實作的。 Redis 為了友善記憶體的管理,在配置設定一塊記憶體之後,會将這塊記憶體的大小存入記憶體塊的頭部。如圖5所示, real_ptr 是 redis 調用 malloc 後傳回的指針。 redis 将記憶體塊的大小 size 存入頭部, size 所占據的記憶體大小是已知的,為 size_t 類型的長度,然後傳回 ret_ptr 。當需要釋放記憶體的時候, ret_ptr 被傳給記憶體管理程式。通過 ret_ptr ,程式可以很容易的算出 real_ptr 的值,然後将 real_ptr 傳給 free 釋放記憶體。

[img]http://s12.sinaimg.cn/orignal/48c95a19gc513d4c6853b&690[/img]

(圖5 Redis 塊配置設定)

Redis 通過定義一個數組來記錄所有的記憶體配置設定情況,這個數組的長度為 ZMALLOC_MAX_ALLOC_STAT 。數組的每一個元素代表目前程式所配置設定的記憶體塊的個數,且記憶體塊的大小為該元素的下标。在源碼中,這個數組為 zmalloc_allocations 。 zmalloc_allocations[16]代表已經配置設定的長度為16 bytes 的記憶體塊的個數。 zmalloc.c 中有一個靜态變量 used_memory 用來記錄目前配置設定的記憶體總大小。是以,總的來看, Redis 采用的是包裝的 mallc/free ,相較于 Memcached 的記憶體管理方法來說,要簡單很多。

3.2 Redis 和 Memcached 的叢集實作機制對比

Memcached 是全記憶體的資料緩沖系統, Redis 雖然支援資料的持久化,但是全記憶體畢竟才是其高性能的本質。作為基于記憶體的存儲系統來說,機器實體記憶體的大小就是系統能夠容納的最大資料量。如果需要處理的資料量超過了單台機器的實體記憶體大小,就需要建構分布式叢集來擴充存儲能力。

3.2.1 Memcached 的分布式存儲

Memcached 本身并不支援分布式,是以隻能在用戶端通過像一緻性哈希這樣的分布式算法來實作 Memcached 的分布式存儲。圖6給出了 Memcached 的分布式存儲實作架構。當用戶端向 Memcached 叢集發送資料之前,首先會通過内置的分布式算法計算出該條資料的目标節點,然後資料會直接發送到該節點上存儲。但用戶端查詢資料時,同樣要計算出查詢資料所在的節點,然後直接向該節點發送查詢請求以擷取資料。

[img]http://s1.sinaimg.cn/orignal/48c95a19gc513ebf63bd0&690[/img]

(圖6 Memcached 用戶端分布式存儲實作)

3.2.2 Redis 的分布式存儲

相較于 Memcached 隻能采用用戶端實作分布式存儲, Redis 更偏向于在伺服器端建構分布式存儲。盡管 Redis 目前已經釋出的穩定版本還沒有添加分布式存儲功能,但 Redis 開發版中已經具備了 Redis Cluster 的基本功能。預計在2.6版本之後, Redis 就會釋出完全支援分布式的穩定版本,時間不晚于2012年底。下面我們會根據開發版中的實作,簡單介紹一下 Redis Cluster 的核心思想。

Redis Cluster 是一個實作了分布式且允許單點故障的 Redis 進階版本,它沒有中心節點,具有線性可伸縮的功能。圖7給出 Redis Cluster 的分布式存儲架構,其中節點與節點之間通過二進制協定進行通信,節點與用戶端之間通過 ascii 協定進行通信。在資料的放置政策上, Redis Cluster 将整個 key 的數值域分成 4096 個哈希槽,每個節點上可以存儲一個或多個哈希槽,也就是說目前 Redis Cluster 支援的最大節點數就是4096。 Redis Cluster 使用的分布式算法也很簡單: crc16( key ) % HASH_SLOTS_NUMBER 。

[img]http://s12.sinaimg.cn/orignal/48c95a19gc5140371835b&690[/img]

(圖7 Redis 分布式架構)

為了保證單點故障下的資料可用性, Redis Cluster 引入了 Master 節點和 Slave 節點。如圖8所示。在 Redis Cluster 中,每個 Master 節點都會有對應的兩個用于備援的 Slave 節點。這樣在整個叢集中,任意兩個節點的當機都不會導緻資料的不可用。當 Master 節點退出後,叢集會自動選擇一個 Slave 節點成為新的 Master 節點。

[img]http://s13.sinaimg.cn/orignal/48c95a19gc513f71c0c7c&690[/img]

3.3 Redis 和 Memcached 整體對比

Redis 的作者 Salvatore Sanfilippo 曾經對這兩種基于記憶體的資料存儲系統進行過比較,總體來看還是比較客觀的,現總結如下:

1) 性能對比:由于 Redis 隻使用單核,而 Memcached 可以使用多核,是以平均每一個核上 Redis 在存儲小資料時比 Memcached 性能更高。而在100k以上的資料中, Memcached 性能要高于 Redis ,雖然 Redis 最近也在存儲大資料的性能上進行優化,但是比起 Memcached ,還是稍有遜色。

2) 記憶體使用效率對比:使用簡單的 key-value 存儲的話, Memcached 的記憶體使用率更高,而如果 Redis 采用 hash 結構來做 key-value 存儲,由于其組合式的壓縮,其記憶體使用率會高于 Memcached 。

3) Redis 支援伺服器端的資料操作: Redis 相比 Memcached 來說,擁有更多的資料結構并支援更豐富的資料操作,通常在 Memcached 裡,你需要将資料拿到用戶端來進行類似的修改再 set 回去。這大大增加了網絡 IO 的次數和資料體積。在 Redis 中,這些複雜的操作通常和一般的 GET/SET 一樣高效。是以,如果需要緩存能夠支援更複雜的結構和操作,那麼 Redis 會是不錯的選擇。

繼續閱讀