數據類型
html
Redis最爲經常使用的數據類型主要有如下五種:mysql
Stringredis
Hashsql
List數據庫
Set數組
Sorted set緩存
在具體描述這幾種數據類型以前,咱們先經過一張圖瞭解下Redis內部內存管理中是如何描述這些不一樣數據類型的:服務器
首先Redis內部使用一個redisObject對象來表示全部的key和value,redisObject最主要的信息如上圖所示:type 表明一個value對象具體是何種數據類型,encoding是不一樣數據類型在redis內部的存儲方式,好比:type=string表明value存 儲的是一個普通字符串,那麼對應的encoding能夠是raw或者是int,若是是int則表明實際redis內部是按數值型類存儲和表示這個字符串 的,固然前提是這個字符串自己能夠用數值表示,好比:"123" "456"這樣的字符串。網絡
這裏須要特殊說明一下vm字段,只有打開了Redis的虛擬內存功能,此字段纔會真正的分配內存,該功能默認是關閉狀態的,該功能會在後面具體描 述。經過上圖咱們能夠發現Redis使用redisObject來表示全部的key/value數據是比較浪費內存的,固然這些內存管理成本的付出主要也 是爲了給Redis不一樣數據類型提供一個統一的管理接口,實際做者也提供了多種方法幫助咱們儘可能節省內存使用,咱們隨後會具體討論。數據結構
下面咱們先來逐一的分析下這五種數據類型的使用和內部實現方式:
String
經常使用命令:
set,get,decr,incr,mget 等。
應用場景:
String是最經常使用的一種數據類型,普通的key/value存儲均可以歸爲此類,這裏就不所作解釋了。
實現方式:
String在redis內部存儲默認就是一個字符串,被redisObject所引用,當遇到incr,decr等操做時會轉成數值型進行計算,此時redisObject的encoding字段爲int。
Hash
經常使用命令:
hget,hset,hgetall 等。
應用場景:
咱們簡單舉個實例來描述下Hash的應用場景,好比咱們要存儲一個用戶信息對象數據,包含如下信息:
用戶ID爲查找的key,存儲的value用戶對象包含姓名,年齡,生日等信息,若是用普通的key/value結構來存儲,主要有如下2種存儲方式:
第一種方式將用戶ID做爲查找key,把其餘信息封裝成一個對象以序列化的方式存儲,這種方式的缺點是,增長了序列化/反序列化的開銷,而且在須要修改其中一項信息時,須要把整個對象取回,而且修改操做須要對併發進行保護,引入CAS等複雜問題。
第二種方法是這個用戶信息對象有多少成員就存成多少個key-value對兒,用用戶ID+對應屬性的名稱做爲惟一標識來取得對應屬性的值,雖然省去了序列化開銷和併發問題,可是用戶ID爲重複存儲,若是存在大量這樣的數據,內存浪費仍是很是可觀的。
那麼Redis提供的Hash很好的解決了這個問題,Redis的Hash實際是內部存儲的Value爲一個HashMap,並提供了直接存取這個Map成員的接口。
也就是說,Key仍然是用戶ID, value是一個Map,這個Map的key是成員的屬性名,value是屬性值,這樣對數據的修改和存取均可以直接經過其內部Map的 Key(Redis裏稱內部Map的key爲field), 也就是經過 key(用戶ID) + field(屬性標籤) 就能夠操做對應屬性數據了,既不須要重複存儲數據,也不會帶來序列化和併發修改控制的問題。很好的解決了問題。
這裏同時須要注意,Redis提供了接口(hgetall)能夠直接取到所有的屬性數據,可是若是內部Map的成員不少,那麼涉及到遍歷整 個內部Map的操做,因爲Redis單線程模型的緣故,這個遍歷操做可能會比較耗時,而另其它客戶端的請求徹底不響應,這點須要格外注意。
上面已經說到Redis Hash對應Value內部實際就是一個HashMap,實際這裏會有2種不一樣實現,這個Hash的成員比較少時Redis爲了節省內存會採用相似一維數組的方式來緊湊存儲,而不會採用真正的HashMap結構,對應的value redisObject的encoding爲zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding爲ht。
List
經常使用命令:
lpush,rpush,lpop,rpop,lrange等。
應用場景:
Redis list的應用場景很是多,也是Redis最重要的數據結構之一,好比twitter的關注列表,粉絲列表等均可以用Redis的list結構來實現,比較好理解,這裏再也不重複。
實現方式:
Redis list的實現爲一個雙向鏈表,便可以支持反向查找和遍歷,更方便操做,不過帶來了部分額外的內存開銷,Redis內部的不少實現,包括髮送緩衝隊列等也都是用的這個數據結構。
Set
經常使用命令:
sadd,spop,smembers,sunion 等。
應用場景:
Redis set對外提供的功能與list相似是一個列表的功能,特殊之處在於set是能夠自動排重的,當你須要存儲一個列表數據,又不但願出現重複數據時,set 是一個很好的選擇,而且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。
實現方式:
set 的內部實現是一個 value永遠爲null的HashMap,實際就是經過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的緣由。
Sorted set
經常使用命令:
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,使用跳躍表的結構能夠得到比較高的查找效率,而且在實現上比較簡單。
vm-enabled=no
關閉Redis的虛擬內存功能,並不成熟。
maxmemory
早前版本次參數是告訴Redis當使用了多少物理內存後就開始拒絕後續的寫入請求,新一點的版本應該有這個參數maxmemory-policy來執行置換策略,具體以下:
volatile-lru:在使用了過時設置的集合中,嘗試刪除一個最近沒在用的鍵。
volatile-tt:在使用了過時設置的集合中,嘗試刪除一個有較短expire時間的鍵。
volatile-random:在使用了過時設置的集合中隨機刪除一個鍵。
allkeys-lru:跟volatile-lru相似,但它會將每一種類型鍵都移除,無論是有效仍是過時的只要設置了過時時間。
allkeys-random:跟volatile-random相似,但它會將每一種類型鍵都移除,無論是有效仍是過時的只要設置了過時時間。
hash-max-zipmap-entries 64 hash-max-zipmap-value 512 hash-max-zipmap-entries
當value這個Map內部不超過多少個成員時會採用線性緊湊格式存儲,默認是64,即value內部有64個如下的成員就是使用線性緊湊存儲,超過該值自動轉成真正的HashMap。hash-max-zipmap-value 含義是當 value這個Map內部的每一個成員值長度不超過多少字節就會採用線性緊湊存儲來節省空間。以上2個條件任意一個條件超過設置值都會轉換成真正的HashMap,也就不會再節省內存了。
list-max-ziplist-entries 512
list數據類型多少節點如下會採用去指針的緊湊存儲格式。
list-max-ziplist-value 64
list數據類型節點值大小小於多少字節會採用緊湊存儲格式。
set-max-intset-entries 512
set數據類型內部數據若是所有是數值型,且包含多少節點如下會採用緊湊格式存儲。
最後想說的是Redis內部實現沒有對內存分配方面作過多的優化,在必定程度上會存在內存碎片,不過大多數狀況下這個不會成爲Redis的性能瓶頸,不過若是在Redis內部存儲的大部分數據是數值型的話,Redis內部採用了一個shared integer的方式來省去分配內存的開銷,即在系統啓動時先分配一個從1~n 那麼多個數值對象放在一個池子中,若是存儲的數據剛好是這個數值範圍內的數據,則直接從池子裏取出該對象,而且經過引用計數的方式來共享,這樣在系統存儲了大量數值下,也能必定程度上節省內存而且提升性能,這個參數值n的設置須要修改源代碼中的一行宏定義REDIS_SHARED_INTEGERS,該值 默認是10000,能夠根據本身的須要進行修改,修改後從新編譯就能夠了。
Redis因爲支持很是豐富的內存數據結構類型,如何把這些複雜的內存組織方式持久化到磁盤上是一個難題,因此Redis的持久化方式與傳統數據庫的方式有比較多的差異,Redis一共支持四種持久化方式,分別是:
定時快照方式(snapshot)
基於語句追加文件的方式(aof)
虛擬內存(vm)
Diskstore方式
redis支持小量數據落地功能,後兩種種方式並不成熟,下面分別介紹下這幾種持久化方式:
定時快照方式(snapshot):
該持久化方式實際是在Redis內部一個定時器事件,每隔固定時間去檢查當前數據發生的改變次數與時間是否知足配置的持久化觸發的條件,若是知足則 經過操做系統fork調用來建立出一個子進程,這個子進程默認會與父進程共享相同的地址空間,這時就能夠經過子進程來遍歷整個內存來進行存儲操做,而主進 程則仍然能夠提供服務,當有寫入時由操做系統按照內存頁(page)爲單位來進行copy-on-write保證父子進程之間不會互相影響。
該持久化的主要缺點是定時快照只是表明一段時間內的內存映像,因此係統重啓會丟失上次快照與重啓之間全部的數據。
基於語句追加方式(aof):
aof方式實際相似mysql的基於語句的binlog方式,即每條會使Redis內存數據發生改變的命令都會追加到一個log文件中,也就是說這個log文件就是Redis的持久化數據。
aof的方式的主要缺點是追加log文件可能致使體積過大,當系統重啓恢復數據時若是是aof的方式則加載數據會很是慢,幾十G的數據可能須要幾小 時才能加載完,固然這個耗時並非由於磁盤文件讀取速度慢,而是因爲讀取的全部命令都要在內存中執行一遍。另外因爲每條命令都要寫log,因此使用aof 的方式,Redis的讀寫性能也會有所降低。
虛擬內存方式:
虛擬內存方式是Redis來進行用戶空間的數據換入換出的一個策略,此種方式在實現的效果上比較差,主要問題是代碼複雜,重啓慢,複製慢等等,目前已經被做者放棄。
diskstore方式:
diskstore方式是做者放棄了虛擬內存方式後選擇的一種新的實現方式,也就是傳統的B-tree的方式,目前仍在實驗階段,後續是否可用咱們能夠拭目以待。
有Redis線上運維經驗的人會發現Redis在物理內存使用比較多,但尚未超過實際物理內存總容量時就會發生不穩定甚至崩潰的問題,有人認爲是 基於快照方式持久化的fork系統調用形成內存佔用加倍而致使的,這種觀點是不許確的,由於fork 調用的copy-on-write機制是基於操做系統頁這個單位的,也就是隻有有寫入的髒頁會被複制,可是通常你的系統不會在短期內全部的頁都發生了寫 入而致使複製,那麼是什麼緣由致使Redis崩潰的呢?
答案是Redis的持久化使用了Buffer IO形成的,所謂Buffer IO是指Redis對持久化文件的寫入和讀取操做都會使用物理內存的Page Cache,而大多數數據庫系統會使用Direct IO來繞過這層Page Cache並自行維護一個數據的Cache,而當Redis的持久化文件過大(尤爲是快照文件),並對其進行讀寫時,磁盤文件中的數據都會被加載到物理內 存中做爲操做系統對該文件的一層Cache,而這層Cache的數據與Redis內存中管理的數據實際是重複存儲的,雖然內核在物理內存緊張時會作 Page Cache的剔除工做,但內核極可能認爲某塊Page Cache更重要,而讓你的進程開始Swap ,這時你的系統就會開始出現不穩定或者崩潰了。咱們的經驗是當你的Redis物理內存使用超過內存總容量的3/5時就會開始比較危險了。
以上內容原文:http://www.infoq.com/cn/articles/tq-redis-memory-usage-optimization-storage/
Redis的複製功能是徹底創建在基於內存快照的持久化策略基礎上的,也就是說不管你的持久化策略選擇的是什麼,只要用到了 Redis的複製功能,就必定會有內存快照發生。
Redis複製流程在Slave和Master端各自是一套狀態機流轉,涉及的狀態信息是:
Slave 端:
REDIS_REPL_NONE REDIS_REPL_CONNECT REDIS_REPL_CONNECTED
Master端:
REDIS_REPL_WAIT_BGSAVE_START REDIS_REPL_WAIT_BGSAVE_END REDIS_REPL_SEND_BULK REDIS_REPL_ONLINE
整個狀態機流程過程以下:
Slave端在配置文件中添加了slave of指令,因而Slave啓動時讀取配置文件,初始狀態爲REDIS_REPL_CONNECT。
Slave端在定時任務serverCron(Redis內部的定時器觸發事件)中鏈接Master,發送sync命令,而後阻塞等待master發送回其內存快照文件(最新版的Redis已經不須要讓Slave阻塞)。
Master端收到sync命令簡單判斷是否有正在進行的內存快照子進程,沒有則當即開始內存快照,有則等待其結束,當快照完成後會將該文件發送給Slave端。
Slave端接收Master發來的內存快照文件,保存到本地,待接收完成後,清空內存表,從新讀取Master發來的內存快照文件,重建整個內存表數據結構,並最終狀態置位爲 REDIS_REPL_CONNECTED狀態,Slave狀態機流轉完成。
Master端在發送快照文件過程當中,接收的任何會改變數據集的命令都會暫時先保存在Slave網絡鏈接的發送緩存隊列裏(list數據結構),待快照完成後,依次發給Slave,以後收到的命令相同處理,並將狀態置位爲 REDIS_REPL_ONLINE。
整個複製過程完成,流程以下圖所示:
從上面的流程能夠看出,Slave從庫在鏈接Master主庫時,Master會進行內存快照,而後把整個快照文件發給Slave,也就是沒有象MySQL那樣有複製位置的概念,即無增量複製,這會給整個集羣搭建帶來很是多的問題。
好比一臺線上正在運行的Master主庫配置了一臺從庫進行簡單讀寫分離,這時Slave因爲網絡或者其它緣由與Master斷開了鏈接,那麼當 Slave進行從新鏈接時,須要從新獲取整個Master的內存快照,Slave全部數據跟着所有清除,而後從新創建整個內存表,一方面Slave恢復的時間會很是慢,另外一方面也會給主庫帶來壓力。
因此基於上述緣由,若是你的Redis集羣須要主從複製,那麼最好事先配置好全部的從庫,避免中途再去增長從庫。
在咱們分析過了Redis的複製與持久化功能後,咱們不可貴出一個結論,實際上Redis目前發佈的版本還都是一個單機版的思路,主要的問題集中在,持久化方式不夠成熟,複製機制存在比較大的缺陷,這時咱們又開始從新思考Redis的定位:Cache仍是Storage?若是做爲Cache的話,彷佛除了有些很是特殊的業務場景,必需要使用Redis的某種數據結構以外,咱們使用Memcached可能更合適,畢竟Memcached不管客戶端包和服務器自己更久經考驗。若是是做爲存儲Storage的話,咱們面臨的最大的問題是不管是持久化仍是複製都沒有辦法解決Redis單點問題,即一臺Redis掛掉了,沒有太好的辦法可以快速的恢復,一般幾十G的持久化數據,Redis重啓加載須要幾個小時的時間,而複製又有缺陷,如何解決呢?
以上內容原文:http://www.infoq.com/cn/articles/tq-redis-memory-usage-optimization-storage/
一、讀多於寫且對實時性要求不高:用Sentinel實現讀寫分離;
二、讀寫均勻且實時性要求高:採用一致性哈稀分片(Shard)
參考資料:
http://blog.csdn.net/freebird_lb/article/details/7778999
http://www.tuicool.com/articles/naeEJbv
http://www.cnblogs.com/lulu/archive/2013/06/10/3130878.html
快照和AOF的優缺點對比:http://my.oschina.net/davehe/blog/174662