Memcached與Redis比較

  上一篇文章我們詳細介紹了分佈式緩存服務器Memcached,本文比較了Memcached與另一個緩存服務器Redis。

一、服務方式

  Memcached和Redis均可在本地作爲獨立進程提供服務,也可以在遠端提供服務。在本地服務時,支持進程間通信,在遠端服務時,支持tcp和udp協議。

二、事件模型

  本質上,Memcached和Redis都使用epoll做事件循環,Redis是單線程的,而Memcached是多線程的。Redis的事件模型很簡單,只有一個event loop,是簡單的reactor實現。而Memcached是多線程的,使用master-worker的方式,主線程監聽端口,建立連接,然後順序分配給各個工作線程。 每一個工作線程都有一個event loop,它們服務不同的客戶端。master線程和worker線程之間使用管道通信,每一個工作線程都會創建一個管道,然後保存寫端和讀端,並且將讀端加入event loop,監聽可讀事件。

三、內存分配

  Memcached有自己的內存池 ,即預先分配一大塊內存,然後接下來分配內存就從內存池中分配,這樣可以減少內存分配的次數,提高效率。 Redis沒有自己的內存池,直接使用時分配,即什麼時候需要什麼時候分配,內存管理的事交給操作系統,自己只負責取和釋放。不過Redis支持使用tcmalloc來替換glibc的malloc,前者是Google的產品,比glibc的malloc快。
  由於Redis沒有自己的內存池,所以內存申請和釋放的管理就簡單很多,直接malloc和free即可,十分方便。而Memcached是支持內存池的,所以內存申請是從內存池中獲取,而free也是還給內存池,所以需要很多額外的管理操作。

四、數據庫實現

4.1 Memcached數據庫實現

4.1.1 key-value對存儲

  Memcached只支持存儲key-value對,一個key對應一個value,在內存中也是以key-value對的形式存儲。如圖1,Memcached將key-value對存儲在一個item結構中。


圖1 存儲key-value對的item結構
圖1 存儲key-value對的item結構

  當有多個key-value對時,需要快速查找對應的item。Memcached通過維護一個hash表實現item的快速查找。hash表使用開鏈法解決衝突,每一個hash表節點存儲一個鏈表,鏈表的節點就是item的指針,如圖1中的h_next就是指向下一個節點的指針。當item的數量是hash節點數量的1.5倍以上時,hash表需要擴容。將old_hashtable=primary_hashtable,然後primary_hashtable設置爲新的hash表,依次將old_hashtable中的節點移動到新的primary_hashtable,同時用expand_bucket記錄移動了多少個節點,最後free old_hashtable。在擴容過程中,查找一個item可能會在old_hashtable也可能會在primary_hashtable,需要根據item對應的節點位置和expand_bucket大小來確定在哪個hashtable。

4.1.2 slab分配內存

  上一篇博客中,我們介紹了Memcached的Slab Allocation機制。如圖2,每個slab中的chunk大小相同,不同的slab中chunk的大小按比例遞增,爲新的item分配一個最小的能放下item的chunk。


圖2 Slab分配內存
圖2 Slab分配內存

  slabclass有一個指針slot,保存未分配的item和已經free的item,有item被free時,就放在slot頭部,在需要slab分配item時,就從slot中取。
  slabclass還對應一個鏈表,有頭尾節點head和tail。鏈表中的節點即該slabclass已分配的item,新的放在頭部,越靠近tail表示越沒用被使用。當slabclass內存不足時,可從尾部開始刪除節點,該鏈表實現了LRU(least recently used)。鏈表中的節點通過指針與hash表中的節點互指,查找item時使用hash,刪除item時使用該鏈表。

4.1.3 多線程

  每次需要新分配item時,先從slabclass對應的鏈表尾部往前找,看是否有item過期,有則直接分配,沒有則從slab中分配chunk,如果slab中沒有chunk,則根據LRU釋放已分配的chunk。
  Memcached支持多線程,因此需要進行併發控制,維持數據一致性。比如,A改了數據,之後B也改了數據,但A不知道B改了,認爲數據是他改完後的值,繼續執行任務就可能產生問題。Memcached使用CAS協議解決多線程的問題。

4.2 Redis數據庫實現

4.2.1 key-value對存儲

  Redis也存儲key-value對,但Memcached的value只支持sting,而Redis支持string、list、set、sorted set、hash table五種數據結構。
  如圖3,爲了實現五種數據結構,Redis定義了抽象的對象Redis Object。每個對象有類型,表示string、list、set、sorted set、hash table中的一種。爲了提高效率,Redis爲每種類型準備了多種實現方式,根據應用場景選擇合適的實現,encoding即表示隊形的實現方式。


圖3 Redis抽象對象數據結構
圖3 Redis抽象對象數據結構

4.2.2 內存管理與多線程

  與Memcached相比,Redis不支持內存管理和多線程。Redis直接通過malloc和free操作內存,將內存管理的任務交給操作系統完成。Redis只有一個event loop,不支持多線程。

4.2.3 數據持久化

  Redis最突出的特點,就是支持數據持久化。

  • RDB持久化——Redis database

  RDB持久化的核心思想就是把整個數據庫保存在文件裏。

REDIS db_version database EOF check_sum

  首先寫入一個REDIS字符串,起驗證作用,表示是RDB文件,然後保存Redis的版本信息,然後是具體的數據庫,然後存儲結束符EOF,最後用檢驗和。databases中,每個數據庫存儲方式如下。

SELECTDB db_number key_value_pairs

  首先一個1字節的常量SELECTDB,表示切換DB了,然後是數據庫的編號,它的長度是可變的,然後接下來就是具體的key-value對的數據了。
  key_value_pairs存儲方式如下。

EXPIRETIME_MS ms TYPE key value

  首先用EXPIRETIME_MS和ms存儲expire time,然後存儲value的類型,然後存儲key和value。
  保存了RDB文件,在Redis啓動的時候,可根據RDB文件恢復數據庫。

  • AOF持久化——ApeendOnly File

  AOF持久化的核心思想就是保存所有建立數據庫的命令。相比於RDB持久化,AOF在持久化過程中開銷很小,但在恢復的數據庫的時候開銷很大。

4.2.4 Redis事務

  Redis另一個比Memcached強大的地方,是它支持簡單的事務。Redis保證一個client發起的事務中的命令可以連續的執行,而中間不會插入其他client的命令。但不同於關係型數據庫,Redis的事務機制不支持回滾,它的事務只保證命令依次被執行,即使中間一條命令出錯也會繼續往下執行。
  執行Redis事務,首先執行multi命令,表示開始事務,然後輸入需要執行的命令,最後輸入exec執行事務。一般情況下Redis在接受到一個client發來的命令後會立即處理並返回處理結果,但是當一個client在一個連接中發出multi命令後,這個連接會進入一個事務上下文,該連接後續的命令並不是立即執行,而是先放到一個隊列中。當收到exec命令後,Redis會順序的執行隊列中的所有命令。並將所有命令的運行結果打包到一起返回給client。

五、小結

  本文從服務方式、事件模型、內存分配、數據庫實現四個方面比較了Memcached和Redis,着重比較了它們在數據庫實現上的異同。總而言之,Memcached專注與存儲key-value數據,而Redis能提供更豐富的數據結構和其他功能。

參考文獻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章