日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis内存使用优化与存储

發(fā)布時間:2024/4/11 数据库 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis内存使用优化与存储 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

歡迎支持筆者新作:《深入理解Kafka:核心設(shè)計與實踐原理》和《RabbitMQ實戰(zhàn)指南》,同時歡迎關(guān)注筆者的微信公眾號:朱小廝的博客。


##Redis數(shù)據(jù)類型
?最為常用的數(shù)據(jù)類型主要有五種:String, Hash, List, Set和SortedSet. redis內(nèi)部使用一個redisObject對象來表示所有的key和value。redisObject最主要的信息如下圖所示:

?type代表一個value對象具體是何種數(shù)據(jù)類型,encoding是不同數(shù)據(jù)類型在redis內(nèi)部的存儲方式,比如:type=string代表value存儲的是一個普通字符串,那么對應(yīng)的encoding可以是raw或者是int,如果是int則代表實際redis內(nèi)部是按數(shù)值類型存儲和表示這個字符串的,當然前提是這個字符串本身可以用數(shù)值表示。
?這里需要特殊說明下vm字段,只有打開了redis的虛擬內(nèi)存功能,此字段才會真真的分配內(nèi)存,該功能默認是關(guān)閉的。vm的功能我們在稍后討論,通過上圖可以發(fā)現(xiàn)redis使用redisObject來表示所有的key-value數(shù)據(jù)是比較浪費內(nèi)存的,當然這些內(nèi)存管理的成本的付出也是為了給redis不同數(shù)據(jù)類型提供一個統(tǒng)一的管理接口,實際作者也提供了許多方法幫助我們盡量節(jié)省內(nèi)存使用。這個也在稍后探討。下面我們先來逐一分析下這五種數(shù)據(jù)類型的使用和內(nèi)部實現(xiàn)方式。

###String
常用命令:set,get,decr,incr, mset, mget等。
應(yīng)用場景:String是最常用的一種數(shù)據(jù)類型,普通的key-value存儲都可以歸為此類。
實現(xiàn)方式:String在redis內(nèi)部存儲默認就是一個字符串,被redisObject所引用,當遇到incr,decr等操作時,會轉(zhuǎn)成數(shù)值型進行計算,此時redisObject的encoding字段為int。

###Hash
常用命令: hmset, hmget, hdel, hlen等。
應(yīng)用場景
?我們簡單舉個實例來描述下Hash的應(yīng)用場景,比如我們要存儲一個用戶信息對象數(shù)據(jù),包含以下信息:
?用戶ID為查找的key,存儲的value用戶對象包含姓名,年齡,生日等信息,如果用普通的key/value結(jié)構(gòu)來存儲,主要有以下2種存儲方式:

?第一種方式將用戶ID作為查找key,把其他信息封裝成一個對象以序列化的方式存儲,這種方式的缺點是,增加了序列化/反序列化的開銷,并且在需要修改其中一項信息時,需要把整個對象取回,并且修改操作需要對并發(fā)進行保護,引入CAS等復(fù)雜問題。

?第二種方法是這個用戶信息對象有多少成員就存成多少個key-value對兒,用用戶ID+對應(yīng)屬性的名稱作為唯一標識來取得對應(yīng)屬性的值,雖然省去了序列化開銷和并發(fā)問題,但是用戶ID為重復(fù)存儲,如果存在大量這樣的數(shù)據(jù),內(nèi)存浪費還是非常可觀的。
?那么Redis提供的Hash很好的解決了這個問題,Redis的Hash實際是內(nèi)部存儲的Value為一個HashMap,并提供了直接存取這個Map成員的接口,如下圖:

?也就是說,Key仍然是用戶ID, value是一個Map,這個Map的key是成員的屬性名,value是屬性值,這樣對數(shù)據(jù)的修改和存取都可以直接通過其內(nèi)部Map的Key(Redis里稱內(nèi)部Map的key為field), 也就是通過 key(用戶ID) + field(屬性標簽) 就可以操作對應(yīng)屬性數(shù)據(jù)了,既不需要重復(fù)存儲數(shù)據(jù),也不會帶來序列化和并發(fā)修改控制的問題。很好的解決了問題。
?這里同時需要注意,Redis提供了接口(hgetall)可以直接取到全部的屬性數(shù)據(jù),但是如果內(nèi)部Map的成員很多,那么涉及到遍歷整個內(nèi)部Map的操作,由于Redis單線程模型的緣故,這個遍歷操作可能會比較耗時,而另其它客戶端的請求完全不響應(yīng),這點需要格外注意。
?實現(xiàn)方式:上面已經(jīng)說到Redis Hash對應(yīng)Value內(nèi)部實際就是一個HashMap,實際這里會有2種不同實現(xiàn),這個Hash的成員比較少時Redis為了節(jié)省內(nèi)存會采用類似一維數(shù)組的方式來緊湊存儲,而不會采用真正的HashMap結(jié)構(gòu),對應(yīng)的value redisObject的encoding為zipmap,當成員數(shù)量增大時會自動轉(zhuǎn)成真正的HashMap,此時encoding為ht。

###List
常用命令: lpush,rpush,lpop,rpop,lrange等。
應(yīng)用場景: Redis list的應(yīng)用場景非常多,也是Redis最重要的數(shù)據(jù)結(jié)構(gòu)之一,比如twitter的關(guān)注列表,粉絲列表等都可以用Redis的list結(jié)構(gòu)來實現(xiàn),比較好理解,這里不再重復(fù)。
實現(xiàn)方式:Redis list的實現(xiàn)為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內(nèi)存開銷,Redis內(nèi)部的很多實現(xiàn),包括發(fā)送緩沖隊列等也都是用的這個數(shù)據(jù)結(jié)構(gòu)。

###Set
常用命令:sadd,spop,smembers,sunion 等。
應(yīng)用場景:Redis set對外提供的功能與list類似是一個列表的功能,特殊之處在于set是可以自動排重的,當你需要存儲一個列表數(shù)據(jù),又不希望出現(xiàn)重復(fù)數(shù)據(jù)時,set是一個很好的選擇,并且set提供了判斷某個成員是否在一個set集合內(nèi)的重要接口,這個也是list所不能提供的。
實現(xiàn)方式:set 的內(nèi)部實現(xiàn)是一個 value永遠為null的HashMap(ps:這點和java中set的實現(xiàn)基本相同),實際就是通過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內(nèi)的原因。

###Sorted Set
常用命令:zadd,zrange,zrem,zcard等.
使用場景:Redis sorted set的使用場景與set類似,區(qū)別是set不是自動有序的,而sorted set可以通過用戶額外提供一個優(yōu)先級(score)的參數(shù)來為成員排序,并且是插入有序的,即自動排序。當你需要一個有序的并且不重復(fù)的集合列表,那么可以選擇sorted set數(shù)據(jù)結(jié)構(gòu),比如twitter 的public timeline可以以發(fā)表時間作為score來存儲,這樣獲取時就是自動按時間排好序的。
實現(xiàn)方式:Redis sorted set的內(nèi)部使用HashMap和跳躍表(SkipList)來保證數(shù)據(jù)的存儲和有序,HashMap里放的是成員到score的映射,而跳躍表里存放的是所有的成員,排序依據(jù)是HashMap里存的score,使用跳躍表的結(jié)構(gòu)可以獲得比較高的查找效率,并且在實現(xiàn)上比較簡單。


##常用內(nèi)存優(yōu)化手段與參數(shù)
?通過我們上面的一些實現(xiàn)上的分析可以看出redis實際上的內(nèi)存管理成本非常高,即占用了過多的內(nèi)存,作者對這點也非常清楚,所以提供了一系列的參數(shù)和手段來控制和節(jié)省內(nèi)存,我們分別來討論下。
?首先最重要的一點是不要開啟Redis的VM選項,即虛擬內(nèi)存功能,這個本來是作為Redis存儲超出物理內(nèi)存數(shù)據(jù)的一種數(shù)據(jù)在內(nèi)存與磁盤換入換出的一個持久化策略,但是其內(nèi)存管理成本也非常的高,并且我們后續(xù)會分析此種持久化策略并不成熟,所以要關(guān)閉VM功能,請檢查你的redis.conf文件中 vm-enabled 為 no。
?其次最好設(shè)置下redis.conf中的maxmemory選項,該選項是告訴Redis當使用了多少物理內(nèi)存后就開始拒絕后續(xù)的寫入請求,該參數(shù)能很好的保護好你的Redis不會因為使用了過多的物理內(nèi)存而導(dǎo)致swap,最終嚴重影響性能甚至崩潰。
?另外Redis為不同數(shù)據(jù)類型分別提供了一組參數(shù)來控制內(nèi)存使用,我們在前面詳細分析過Redis Hash是value內(nèi)部為一個HashMap,如果該Map的成員數(shù)比較少,則會采用類似一維線性的緊湊格式來存儲該Map, 即省去了大量指針的內(nèi)存開銷,這個參數(shù)控制對應(yīng)在redis.conf配置文件中下面2項:

hash-max-zipmap-entries 64 hash-max-zipmap-value 512 hash-max-zipmap-entries

?含義是當value這個Map內(nèi)部不超過多少個成員時會采用線性緊湊格式存儲,默認是64,即value內(nèi)部有64個以下的成員就是使用線性緊湊存儲,超過該值自動轉(zhuǎn)成真正的HashMap。
?hash-max-zipmap-value 含義是當 value這個Map內(nèi)部的每個成員值長度不超過多少字節(jié)就會采用線性緊湊存儲來節(jié)省空間。
?以上2個條件任意一個條件超過設(shè)置值都會轉(zhuǎn)換成真正的HashMap,也就不會再節(jié)省內(nèi)存了,那么這個值是不是設(shè)置的越大越好呢,答案當然是否定的,HashMap的優(yōu)勢就是查找和操作的時間復(fù)雜度都是O(1)的,而放棄Hash采用一維存儲則是O(n)的時間復(fù)雜度,如果成員數(shù)量很少,則影響不大,否則會嚴重影響性能,所以要權(quán)衡好這個值的設(shè)置,總體上還是最根本的時間成本和空間成本上的權(quán)衡。
?同樣類似的參數(shù)還有:

list-max-ziplist-entries 512

?說明:list數(shù)據(jù)類型多少節(jié)點以下會采用去指針的緊湊存儲格式。

list-max-ziplist-value 64

?說明:list數(shù)據(jù)類型節(jié)點值大小小于多少字節(jié)會采用緊湊存儲格式。

set-max-intset-entries 512

?說明:set數(shù)據(jù)類型內(nèi)部數(shù)據(jù)如果全部是數(shù)值型,且包含多少節(jié)點以下會采用緊湊格式存儲。
?最后想說的是Redis內(nèi)部實現(xiàn)沒有對內(nèi)存分配方面做過多的優(yōu)化,在一定程度上會存在內(nèi)存碎片,不過大多數(shù)情況下這個不會成為Redis的性能瓶頸,不過如果在Redis內(nèi)部存儲的大部分數(shù)據(jù)是數(shù)值型的話,Redis內(nèi)部采用了一個shared integer的方式來省去分配內(nèi)存的開銷,即在系統(tǒng)啟動時先分配一個從1~n 那么多個數(shù)值對象放在一個池子中,如果存儲的數(shù)據(jù)恰好是這個數(shù)值范圍內(nèi)的數(shù)據(jù),則直接從池子里取出該對象,并且通過引用計數(shù)的方式來共享,這樣在系統(tǒng)存儲了大量數(shù)值下,也能一定程度上節(jié)省內(nèi)存并且提高性能,這個參數(shù)值n的設(shè)置需要修改源代碼中的一行宏定義REDIS_SHARED_INTEGERS,該值默認是10000,可以根據(jù)自己的需要進行修改,修改后重新編譯就可以了。


##redis持久化機制
?Redis由于支持非常豐富的內(nèi)存數(shù)據(jù)結(jié)構(gòu)類型,如何把這些復(fù)雜的內(nèi)存組織方式持久化到磁盤上是一個難題,所以Redis的持久化方式與傳統(tǒng)數(shù)據(jù)庫的方式有比較多的差別,Redis一共支持四種持久化方式,分別是:

  • 時快照方式(snapshot)
  • 基于語句追加文件的方式(aof)
  • 虛擬內(nèi)存(vm)
  • Diskstore方式

?在設(shè)計思路上,前兩種是基于全部數(shù)據(jù)都在內(nèi)存中,即小數(shù)據(jù)量下提供磁盤落地功能,而后兩種方式則是作者在嘗試存儲數(shù)據(jù)超過物理內(nèi)存時,即大數(shù)據(jù)量的數(shù)據(jù)存儲,截止到本文,后兩種持久化方式仍然是在實驗階段,并且vm方式基本已經(jīng)被作者放棄,所以實際能在生產(chǎn)環(huán)境用的只有前兩種,換句話說Redis目前還只能作為小數(shù)據(jù)量存儲(全部數(shù)據(jù)能夠加載在內(nèi)存中),海量數(shù)據(jù)存儲方面并不是Redis所擅長的領(lǐng)域。下面分別介紹下這幾種持久化方式:

定時快照方式(snapshot):
?該持久化方式實際是在Redis內(nèi)部一個定時器事件,每隔固定時間去檢查當前數(shù)據(jù)發(fā)生的改變次數(shù)與時間是否滿足配置的持久化觸發(fā)的條件,如果滿足則通過操作系統(tǒng)fork調(diào)用來創(chuàng)建出一個子進程,這個子進程默認會與父進程共享相同的地址空間,這時就可以通過子進程來遍歷整個內(nèi)存來進行存儲操作,而主進程則仍然可以提供服務(wù),當有寫入時由操作系統(tǒng)按照內(nèi)存頁(page)為單位來進行copy-on-write保證父子進程之間不會互相影響。
?該持久化的主要缺點是定時快照只是代表一段時間內(nèi)的內(nèi)存映像,所以系統(tǒng)重啟會丟失上次快照與重啟之間所有的數(shù)據(jù)。

基于語句追加方式(aof):
?aof方式實際類似mysql的基于語句的binlog方式,即每條會使Redis內(nèi)存數(shù)據(jù)發(fā)生改變的命令都會追加到一個log文件中,也就是說這個log文件就是Redis的持久化數(shù)據(jù)。
?aof的方式的主要缺點是追加log文件可能導(dǎo)致體積過大,當系統(tǒng)重啟恢復(fù)數(shù)據(jù)時如果是aof的方式則加載數(shù)據(jù)會非常慢,幾十G的數(shù)據(jù)可能需要幾小時才能加載完,當然這個耗時并不是因為磁盤文件讀取速度慢,而是由于讀取的所有命令都要在內(nèi)存中執(zhí)行一遍。另外由于每條命令都要寫log,所以使用aof的方式,Redis的讀寫性能也會有所下降。

虛擬內(nèi)存方式:
?虛擬內(nèi)存方式是Redis來進行用戶空間的數(shù)據(jù)換入換出的一個策略,此種方式在實現(xiàn)的效果上比較差,主要問題是代碼復(fù)雜,重啟慢,復(fù)制慢等等,目前已經(jīng)被作者放棄。

diskstore方式:
?diskstore方式是作者放棄了虛擬內(nèi)存方式后選擇的一種新的實現(xiàn)方式,也就是傳統(tǒng)的B-tree的方式,目前仍在實驗階段,后續(xù)是否可用我們可以拭目以待。


##Redis持久化磁盤IO方式及其帶來的問題
?有Redis線上運維經(jīng)驗的人會發(fā)現(xiàn)Redis在物理內(nèi)存使用比較多,但還沒有超過實際物理內(nèi)存總?cè)萘繒r就會發(fā)生不穩(wěn)定甚至崩潰的問題,有人認為是基于快照方式持久化的fork系統(tǒng)調(diào)用造成內(nèi)存占用加倍而導(dǎo)致的,這種觀點是不準確的,因為fork 調(diào)用的copy-on-write機制是基于操作系統(tǒng)頁這個單位的,也就是只有有寫入的臟頁會被復(fù)制,但是一般你的系統(tǒng)不會在短時間內(nèi)所有的頁都發(fā)生了寫入而導(dǎo)致復(fù)制,那么是什么原因?qū)е翿edis崩潰的呢?
?答案是Redis的持久化使用了Buffer IO造成的,所謂Buffer IO是指Redis對持久化文件的寫入和讀取操作都會使用物理內(nèi)存的Page Cache,而大多數(shù)數(shù)據(jù)庫系統(tǒng)會使用Direct IO來繞過這層Page Cache并自行維護一個數(shù)據(jù)的Cache,而當Redis的持久化文件過大(尤其是快照文件),并對其進行讀寫時,磁盤文件中的數(shù)據(jù)都會被加載到物理內(nèi)存中作為操作系統(tǒng)對該文件的一層Cache,而這層Cache的數(shù)據(jù)與Redis內(nèi)存中管理的數(shù)據(jù)實際是重復(fù)存儲的,雖然內(nèi)核在物理內(nèi)存緊張時會做Page Cache的剔除工作,但內(nèi)核很可能認為某塊Page Cache更重要,而讓你的進程開始Swap ,這時你的系統(tǒng)就會開始出現(xiàn)不穩(wěn)定或者崩潰了。我們的經(jīng)驗是當你的Redis物理內(nèi)存使用超過內(nèi)存總?cè)萘康?/5時就會開始比較危險了。
下圖是Redis在讀取或者寫入快照文件dump.rdb后的內(nèi)存數(shù)據(jù)圖:


##總結(jié)

  • 根據(jù)業(yè)務(wù)需要選擇合適的數(shù)據(jù)類型,并為不同的應(yīng)用場景設(shè)置相應(yīng)的緊湊存儲參數(shù)。
  • 當業(yè)務(wù)場景不需要數(shù)據(jù)持久化時,關(guān)閉所有的持久化方式可以獲得最佳的性能以及最大的內(nèi)存使用量。
  • 如果需要使用持久化,根據(jù)是否可以容忍重啟丟失部分數(shù)據(jù)在快照方式與語句追加方式之間選擇其一,不要使用虛擬內(nèi)存以及diskstore方式。
  • 不要讓你的Redis所在機器物理內(nèi)存使用超過實際內(nèi)存總量的3/5。
  • 轉(zhuǎn)載地址:http://www.infoq.com/cn/articles/tq-redis-memory-usage-optimization-storage#anch104989


    歡迎支持筆者新作:《深入理解Kafka:核心設(shè)計與實踐原理》和《RabbitMQ實戰(zhàn)指南》,同時歡迎關(guān)注筆者的微信公眾號:朱小廝的博客。


    總結(jié)

    以上是生活随笔為你收集整理的Redis内存使用优化与存储的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。