1. 程式人生 > >全面深入理解Redis與Memcached(深度好文)

全面深入理解Redis與Memcached(深度好文)

說到redis就會聯想到memcached,反之亦然。瞭解過兩者的同學有那麼個大致的印象:redis與memcached相比,比僅支援簡單的key-value資料型別,同時還提供list,set,zset,hash等資料結構的儲存;redis支援資料的備份,即master-slave模式的資料備份;redis支援資料的持久化,可以將記憶體中的資料保持在磁碟中,重啟的時候可以再次載入進行使用等等,這似乎看起來redis比memcached更加牛逼一些,那麼事實上是不是這樣的呢?存在即合理,我們來根據幾個不同點來一一比較一下。

網路IO模型

memcached是多執行緒,非阻塞IO複用的網路模型,分為監聽主執行緒和worker子執行緒,監聽執行緒監聽網路連線,接受請求後,將連線描述字pipe傳遞給worker執行緒,進行讀寫IO,網路層使用libevent封裝的事件庫,多執行緒模型可以發揮多核作用,但是引入了cache coherency和鎖的問題,比如:memcached最常用的stats命令,實際memcached所有操作都要對這個全域性變數加鎖,進行技術等工作,帶來了效能損耗。

redis使用單執行緒的IO複用模型,自己封裝了一個簡單的AeEvent事件處理框架,主要實現了epoll, kqueue和select,對於單存只有IO操作來說,單執行緒可以將速度優勢發揮到最大,但是redis也提供了一些簡單的計算功能,比如排序、聚合等,對於這些操作,單執行緒模型施加會嚴重影響整體吞吐量,CPU計算過程中,整個IO排程都是被阻塞的。

資料支援型別

memcached使用key-value形式儲存和訪問資料,在記憶體中維護一張巨大的HashTable,使得對資料查詢的時間複雜度降低到O(1),保證了對資料的高效能訪問。

正如開篇所說:redis與memcached相比,比僅支援簡單的key-value資料型別,同時還提供list,set,zset,hash等資料結構的儲存;詳細可以翻閱《Redis記憶體使用優化與儲存》

記憶體管理機制

對於像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個位元組的資料快取到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的記憶體管理方法來說,要簡單很多。

在Redis中,並不是所有的資料都一直儲存在記憶體中的。這是和Memcached相比一個最大的區別。當實體記憶體用完時,Redis可以將一些很久沒用到的value交換到磁碟。Redis只會快取所有的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檔案中載入相應資料的讀取請求進行併發操作,減少阻塞的時間。

Memcached使用預分配的記憶體池的方式,使用slab和大小不同的chunk來管理記憶體,Item根據大小選擇合適的chunk儲存,記憶體池的方式可以省去申請/釋放記憶體的開銷,並且能減小記憶體碎片產生,但這種方式也會帶來一定程度上的空間浪費,並且在記憶體仍然有很大空間時,新的資料也可能會被剔除,原因可以參考Timyang的文章:http://timyang.net/data/Memcached-lru-evictions/

Redis使用現場申請記憶體的方式來儲存資料,並且很少使用free-list等方式來優化記憶體分配,會在一定程度上存在記憶體碎片,Redis跟據儲存命令引數,會把帶過期時間的資料單獨存放在一起,並把它們稱為臨時資料,非臨時資料是永遠不會被剔除的,即便實體記憶體不夠,導致swap也不會剔除任何非臨時資料(但會嘗試剔除部分臨時資料),這點上Redis更適合作為儲存而不是cache。

資料儲存及持久化

memcached不支援記憶體資料的持久化操作,所有的資料都以in-memory的形式儲存。
redis支援持久化操作。redis提供了兩種不同的持久化方法來講資料儲存到硬盤裡面,一種是快照(snapshotting),它可以將存在於某一時刻的所有資料都寫入硬盤裡面。另一種方法叫只追加檔案(append-only file, AOF),它會在執行寫命令時,將被執行的寫命令複製到硬盤裡面。

資料一致性問題

Memcached提供了cas命令,可以保證多個併發訪問操作同一份資料的一致性問題。 Redis沒有提供cas 命令,並不能保證這點,不過Redis提供了事務的功能,可以保證一串 命令的原子性,中間不會被任何操作打斷。

叢集管理不同

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節點。

參考資料

1. Redis和Memcached的區別
2. 為什麼使用 Redis及其產品定位
3. Redis記憶體使用優化與儲存

4. 《Redis In Action》Josiah L. Carlson.

轉載連結:http://mp.weixin.qq.com/s/kcon9vUbByurcaR3gNmSWw

相關推薦

全面深入理解RedisMemcached深度

說到redis就會聯想到memcached,反之亦然。瞭解過兩者的同學有那麼個大致的印象:redis與memcached相比,比僅支援簡單的key-value資料型別,同時還提供list,set,zset,hash等資料結構的儲存;redis支援資料的備份,即master-s

http傳輸字元編碼轉義深度

一、引言         最近陸陸續續看了好多關於位元組、字元、字元編碼、字元轉義的文章,終於對這個過程有了比較清晰深刻的認識。和後臺開發相結合知道了亂碼出現的理論具體原因,和前臺開發結合知道了g

html——floatclear詳解深度

教程開始:        首先要知道,div是塊級元素,在頁面中獨佔一行,自上而下排列,也就是傳說中的流。如下圖:        可以看出,即使div1的寬度很小,頁面中一行可以容下d

思維的八層境界深度

導讀 有道 一個 曾經 大學 運用 常常 提高 必要條件 導讀:今天,越來越多的人認識到,學習最重要的目標並不是掌握一些特定的知識,也不是那些可以很容易通過移動互聯網借助"百度"、"谷歌"等工具搜索到的特定知識,而是發展自己的思維! 在世界快速變化的時代,思維是最重要的核心

獵豹傅盛:升維思考,降維攻擊!深度

      轉載地址:http://www.woshipm.com/it/218149.html         前不久,我讀完《三體》,幾乎幫我建立了一個更高維度的世界觀和科學觀。因為你突然意識到,這個世界不

Java執行緒詳解深度

Java執行緒:概念與原理 一、程序與執行緒         程序是指一個記憶體中執行的應用程式,每個程序都有自己獨立的一塊記憶體空間,即程序空間或(虛空間)。程序不依賴於執行緒而獨立存在,一個程序中可以啟動多個執行緒。比如在Windows系統中,一個執行的ex

高新技術企業認定,史上最詳細的申請攻略!深度

相關 str 直接 方式 項目 微信 組織 名片 數據 在市場廣闊的今天,基本每個省和市地×××府都會建立產業園區或高新技術企業優化孵化帶,提供各種政策支持、稅收優惠以及財政補助。同時,高新技術企業資質對企業來說是一張閃亮的名片,相當於中國的品牌馳名商標,不僅能夠為企業帶來

下一個七年你是誰?深度

一時心血來潮報了個口譯班,其中有一位老師大概30歲,長得很漂亮,打扮也很時尚,口譯功夫了得,每次都來去匆匆,中午就花5分鐘的時間泡一碗麵吃。後來才知道,她大學學的是歷史,她的本職工作是一家公司的公關部經理,兒子已經5歲,她每天要上班、做家務、帶孩子。 與我們不同的是,她

為什麼你看了很多書 ,卻依然沒有洞見 深度

前幾天有人在知乎上問: 今天就回答下很多人問了很久的這個問題,並且解釋一下如何構建系統化的知識體系。 我想很多人看到這個問題,期待的答案是一個書單,可是我要告訴你這並沒有什麼卵用。 我想大部分人都經歷過高中,你會發現高考650分的人和450分的人

值得推薦的C/C++框架和庫深度

【本文系外部轉貼,原文地址:http://www.cppblog.com/merlinfang/archive/2014/12/26/209311.html 公交車上看到的好文,忍不住轉發!下次造輪子前先看看現有的輪子吧--值得學習的C語言開源專案- 1. Webbench 

Form表單和ajax中的post請求&&後臺獲取資料方法深度

最近要做後臺資料接收,因為前臺傳來的資料太過於混亂,所以總結了一下前臺資料post請求方法,順便寫了下相對應的後臺接收方法。         前臺主要的contenttype為下面三種:(需要注意的是請求頭中datatype與contenttype的區別,dat

英語美文朗讀:真心朋友 深度

"A friend walks in when the rest of the world walks out." "別人都走開

SpringDataJpa入門案例及查詢詳細解析深度

 SpringDataJpaSpring Data JPA 讓我們解脫了DA0層的操作,基本上所有CRUD都可以依賴於它來實現,在實際的工作工程中,推薦使用Spring Data JPA+ORM(如:hibernate)完成操作,這樣在切換不同的ORM框架時提供了極大的方便,同時也使資料庫層操作更

深入理解maven應用:靈活的構建

 一個優秀的構建系統必須足夠靈活,應該能夠讓專案在不同的環境下都能成功構建。maven為了支援構建的靈活性,內建了三大特性,即:屬性、profile和資源過濾。 1、maven屬性  maven屬性分6類:     1、內建屬性:如${basedir}表示專案根目錄,$

深入理解Java Lambda表示式全網之最

本文將結合書本和網路教程,闡述自己對於Lambda表示式的理解,如有偏差,歡迎指正... 目錄 方法引用:  技術的進步,循序漸進;慢下來,紮紮實實;用過度的功夫,才能理解表面膚淺的深度 什麼是Lambda表示式? 可以將Lamb

深入理解共享元素變換Shared Element Transition-上

本文將深入分析共享元素變換(shared element transition)以及它在Activity 和Fragment Transitions API中所扮演的角色。這是這Transition系列文章的第三部分: 第三章(本章)將分為兩部分:上著重底層的原理,下

深入理解Java虛擬機器類檔案結構

深入理解Java虛擬機器(類檔案結構) 歡迎關注微信公眾號:BaronTalk,獲取更多精彩好文! 之前在閱讀 ASM 文件時,對於已編譯類的結構、方法描述符、訪問標誌、ACC_PUBLIC、ACC_PRIVATE、各種位元組碼指令等等許多概念聽起來都是雲山霧罩、一知半解,原因就在於對類檔案結構和

mysql什麼情況下會觸發表鎖深度!!!

https://www.cnblogs.com/sessionbest/articles/8689082.html   鎖是計算機協調多個程序或執行緒併發訪問某一資源的機制。在資料庫中,除傳統的計算資源(如CPU、RAM、I/O等)的爭用以外,資料也是一種供許多使用者共享的資源。如

房價是一定要跌的原創首發,深度 ——何學林中國房地產大策劃之二

原創作者:何學林中國策劃一人 目前是房地產整個都在跌,但筆者是在房價一片喊漲聲中說的。當時遭到了普遍一致的反對,反對者的觀點是房價一定要漲,不可能跌,因為地球只有一個,土地越來越少,所以房價一定是 漲的。現在看來這種觀點和理由很幼稚,但當時確實都持這種觀點,在房

Android Jetpack從入門到精通深度,值得收藏

前言 即學即用Android Jetpack系列Blog的目的是通過學習Android Jetpack完成一個簡單的Demo,本文是即學即用Android Jetpack系列Blog的第一篇。 記得去年第一次參加谷歌開發者大會的時候,就被Navigation的圖形導航介面給迷住了,一句臥槽就代表了小王的全部心