redis存儲二進制
A. Redis的內存優化
一. redisObject對象
二. 縮減鍵值對象
三. 共享對象池
四. 字元串優化
五. 編碼優化
六. 控制key的數量
Redis存儲的所有值對象在內部定義為redisObject結構體,內部結構如下圖所示。
表示當前對象使用的數據類型,Redis主要支持5種數據類型:string,hash,list,set,zset。可以使用type {key}命令查純茄答看對象所屬類型,type命令返回的是值對象類型,鍵都是string類型。
表示Redis內部編碼類型,encoding在Redis內部使用,代表當前對象內部採用哪種數據結構實現。理解Redis內部編碼方式對於優化內存非常重要 ,同一個對象採用不同的編碼實現內存佔用存在明顯差異,具體細節見之後編碼優化部分。
記錄對象最後一次被訪問的時間,當配置了 maxmemory和maxmemory-policy=volatile-lru | allkeys-lru 時, 用於輔助LRU演算法刪除鍵數據。可以使用object idletime {key}命令在不更新lru欄位情況下查看當前鍵的空閑時間。
記錄當前對象被引用的次數,用於通過引用次數回收內存,當refcount=0時,可以安全回收當前對象空間。使用object refcount {key}獲取當前對象引用。當對象為整數且范圍在[0-9999]時,Redis可以使用共享對象的方式來節省內存。具體細節見之後共享對象池部分。
與對象的數據內容相關,如果是整數直接存儲數據,否則表示指向數據的指針。Redis在3.0之後對值對象是字元納爛串且長度<=39位元組的數據,內部編碼為embstr類型,字元串sds和redisObject一起分配,從而只要一次內存操作。
降低Redis內存使用最直接的方式就是縮減鍵(key)和值(value)的長度。
其中java-built-in-serializer表示JAVA內置序列化方式,更多數據見jvm-serializers項目: https://github.com/eishay/jvm-serializers/wiki,其它語言也有各自對應的高效序列化工具。
值對象除了存儲二進制數據之外,通常還會使用通用格式存儲數據比如:json,xml等作為字元串存儲在Redis中。這種方式優點是方便調試和跨語言,但是同樣的數據相比位元組數組所需的空間更大,在內存緊張的情況下,可以使用通用壓縮演算法壓縮json,xml後再存入Redis,從而降低內存佔用,例如使用GZIP壓縮後的json可降低約60%的空間。
對象共享池指Redis內部維護[0-9999]的整數對象池。創建大量的整數類型redisObject存在內存開銷,每個redisObject內部結構至少佔16位元組,甚至超過了整數自身空間消耗。所以Redis內存維護一個[0-9999]的整數對象池,用於節約內存。 除了整數值對象,其他類型如list,hash,set,zset內部元素也可以使用整數對象池。因此開發中在滿足需求的前提下,盡量使用整數對象以節省內存。
整數對象池在Redis中通過變數REDIS_SHARED_INTEGERS定義,不能通過配置修改。可以通過object refcount 命令查看對象引用數驗證是否啟用整數對象池技術,如下:
設置鍵foo等於100時,直接使用共享池內整數對象,因此引用數是2,再設置鍵bar等於100時,引用數又變為3,如下圖所示。
使用整數對象池究竟做慧能降低多少內存?讓我們通過測試來對比對象池的內存優化效果,如下表所示。
使用共享對象池後,相同的數據內存使用降低30%以上。可見當數據大量使用[0-9999]的整數時,共享對象池可以節約大量內存。需要注意的是對象池並不是只要存儲[0-9999]的整數就可以工作。當設置maxmemory並啟用LRU相關淘汰策略如:volatile-lru,allkeys-lru時,Redis禁止使用共享對象池,測試命令如下:
LRU演算法需要獲取對象最後被訪問時間,以便淘汰最長未訪問數據,每個對象最後訪問時間存儲在redisObject對象的lru欄位。對象共享意味著多個引用共享同一個redisObject,這時lru欄位也會被共享,導致無法獲取每個對象的最後訪問時間。如果沒有設置maxmemory,直到內存被用盡Redis也不會觸發內存回收,所以共享對象池可以正常工作。
綜上所述,共享對象池與maxmemory+LRU策略沖突,使用時需要注意。 對於ziplist編碼的值對象,即使內部數據為整數也無法使用共享對象池,因為ziplist使用壓縮且內存連續的結構,對象共享判斷成本過高,ziplist編碼細節後面內容詳細說明。
首先整數對象池復用的幾率最大,其次對象共享的一個關鍵操作就是判斷相等性,Redis之所以只有整數對象池,是因為整數比較演算法時間復雜度為O(1),只保留一萬個整數為了防止對象池浪費。如果是字元串判斷相等性,時間復雜度變為O(n),特別是長字元串更消耗性能(浮點數在Redis內部使用字元串存儲)。對於更復雜的數據結構如hash,list等,相等性判斷需要O(n2)。對於單線程的Redis來說,這樣的開銷顯然不合理,因此Redis只保留整數共享對象池。
字元串對象是Redis內部最常用的數據類型。所有的鍵都是字元串類型, 值對象數據除了整數之外都使用字元串存儲。比如執行命令:lpush cache:type 「redis」 「memcache」 「tair」 「levelDB」 ,Redis首先創建」cache:type」鍵字元串,然後創建鏈表對象,鏈表對象內再包含四個字元串對象,排除Redis內部用到的字元串對象之外至少創建5個字元串對象。可見字元串對象在Redis內部使用非常廣泛,因此深刻理解Redis字元串對於內存優化非常有幫助:
Redis沒有採用原生c語言的字元串類型而是自己實現了字元串結構,內部簡單動態字元串(simple dynamic string),簡稱SDS。結構下圖所示。
Redis自身實現的字元串結構有如下特點:
因為字元串(SDS)存在預分配機制,日常開發中要小心預分配帶來的內存浪費,例如下表的測試用例。
從測試數據可以看出,同樣的數據追加後內存消耗非常嚴重,下面我們結合圖來分析這一現象。階段1每個字元串對象空間佔用如下圖所示。
階段1插入新的字元串後,free欄位保留空間為0,總佔用空間=實際佔用空間+1位元組,最後1位元組保存『\0』標示結尾,這里忽略int類型len和free欄位消耗的8位元組。在階段1原有字元串上追加60位元組數據空間佔用如下圖所示。
追加操作後字元串對象預分配了一倍容量作為預留空間,而且大量追加操作需要內存重新分配,造成內存碎片率(mem_fragmentation_ratio)上升。直接插入與階段2相同數據的空間佔用,如下圖所示。
階段3直接插入同等數據後,相比階段2節省了每個字元串對象預分配的空間,同時降低了碎片率。
字元串之所以採用預分配的方式是防止修改操作需要不斷重分配內存和位元組數據拷貝。但同樣也會造成內存的浪費。字元串預分配每次並不都是翻倍擴容,空間預分配規則如下:
字元串重構:指不一定把每份數據作為字元串整體存儲,像json這樣的數據可以使用hash結構,使用二級結構存儲也能幫我們節省內存。同時可以使用hmget,hmset命令支持欄位的部分讀取修改,而不用每次整體存取。例如下面的json數據:
分別使用字元串和hash結構測試內存表現,如下表所示。
根據測試結構,第一次默認配置下使用hash類型,內存消耗不但沒有降低反而比字元串存儲多出2倍,而調整hash-max-ziplist-value=66之後內存降低為535.60M。因為json的videoAlbumPic屬性長度是65,而hash-max-ziplist-value默認值是64,Redis採用hashtable編碼方式,反而消耗了大量內存。調整配置後hash類型內部編碼方式變為ziplist,相比字元串更省內存且支持屬性的部分操作。下一節將具體介紹ziplist編碼優化細節。
Redis對外提供了string,list,hash,set,zet等類型,但是Redis內部針對不同類型存在編碼的概念,所謂編碼就是具體使用哪種底層數據結構來實現。編碼不同將直接影響數據的內存佔用和讀寫效率。使用object encoding {key}命令獲取編碼類型。如下:
Redis針對每種數據類型(type)可以採用至少兩種編碼方式來實現,下表表示type和encoding的對應關系。
了解編碼和類型對應關系之後,我們不禁疑惑Redis為什麼需要對一種數據結構實現多種編碼方式?
主要原因是Redis作者想通過不同編碼實現效率和空間的平衡。比如當我們的存儲只有10個元素的列表,當使用雙向鏈表數據結構時,必然需要維護大量的內部欄位如每個元素需要:前置指針,後置指針,數據指針等,造成空間浪費,如果採用連續內存結構的壓縮列表(ziplist),將會節省大量內存,而由於數據長度較小,存取操作時間復雜度即使為O(n2)性能也可滿足需求。
Redis內存優化
編碼類型轉換在Redis寫入數據時自動完成,這個轉換過程是不可逆的,轉換規則只能從小內存編碼向大內存編碼轉換。例如:
以上命令體現了list類型編碼的轉換過程,其中Redis之所以不支持編碼回退,主要是數據增刪頻繁時,數據向壓縮編碼轉換非常消耗CPU,得不償失。以上示例用到了list-max-ziplist-entries參數,這個參數用來決定列表長度在多少范圍內使用ziplist編碼。當然還有其它參數控制各種數據類型的編碼,如下表所示:
掌握編碼轉換機制,對我們通過編碼來優化內存使用非常有幫助。下面以hash類型為例,介紹編碼轉換的運行流程,如下圖所示。
理解編碼轉換流程和相關配置之後,可以使用config set命令設置編碼相關參數來滿足使用壓縮編碼的條件。對於已經採用非壓縮編碼類型的數據如hashtable,linkedlist等,設置參數後即使數據滿足壓縮編碼條件,Redis也不會做轉換,需要重啟Redis重新載入數據才能完成轉換。
ziplist編碼主要目的是為了節約內存,因此所有數據都是採用線性連續的內存結構。ziplist編碼是應用范圍最廣的一種,可以分別作為hash、list、zset類型的底層數據結構實現。首先從ziplist編碼結構開始分析,它的內部結構類似這樣:<….>。一個ziplist可以包含多個entry(元素),每個entry保存具體的數據(整數或者位元組數組),內部結構如下圖所示。
ziplist結構欄位含義:
根據以上對ziplist欄位說明,可以分析出該數據結構特點如下:
下面通過測試展示ziplist編碼在不同類型中內存和速度的表現,如下表所示。
測試數據採用100W個36位元組數據,劃分為1000個鍵,每個類型長度統一為1000。從測試結果可以看出:
intset編碼是集合(set)類型編碼的一種,內部表現為存儲有序,不重復的整數集。當集合只包含整數且長度不超過set-max-intset-entries配置時被啟用。執行以下命令查看intset表現:
以上命令可以看出intset對寫入整數進行排序,通過O(log(n))時間復雜度實現查找和去重操作,intset編碼結構如下圖所示。
intset的欄位結構含義:
根據以上測試結果發現intset表現非常好,同樣的數據內存佔用只有不到hashtable編碼的十分之一。intset數據結構插入命令復雜度為O(n),查詢命令為O(log(n)),由於整數佔用空間非常小,所以在集合長度可控的基礎上,寫入命令執行速度也會非常快,因此當使用整數集合時盡量使用intset編碼。上表測試第三行把ziplist-hash類型也放入其中,主要因為intset編碼必須存儲整數,當集合內保存非整數數據時,無法使用intset實現內存優化。這時可以使用ziplist-hash類型對象模擬集合類型,hash的field當作集合中的元素,value設置為1位元組佔位符即可。使用ziplist編碼的hash類型依然比使用hashtable編碼的集合節省大量內存。
當使用Redis存儲大量數據時,通常會存在大量鍵,過多的鍵同樣會消耗大量內存。Redis本質是一個數據結構伺服器,它為我們提供多種數據結構,如hash,list,set,zset 等結構。使用Redis時不要進入一個誤區,大量使用get/set這樣的API,把Redis當成Memcached使用。對於存儲相同的數據內容利用Redis的數據結構降低外層鍵的數量,也可以節省大量內存。如下圖所示,通過在客戶端預估鍵規模,把大量鍵分組映射到多個hash結構中降低鍵的數量。
hash結構降低鍵數量分析:
通過這個測試數據,可以說明:
關於hash鍵和field鍵的設計:
使用hash結構控制鍵的規模雖然可以大幅降低內存,但同樣會帶來問題,需要提前做好規避處理。如下:
本文主要講解Redis內存優化技巧,Redis的數據特性是」ALL IN MEMORY」,優化內存將變得非常重要。對於內存優化建議讀者先要掌握Redis內存存儲的特性比如字元串,壓縮編碼,整數集合等,再根據數據規模和所用命令需求去調整,從而達到空間和效率的最佳平衡。建議使用Redis存儲大量數據時,把內存優化環節加入到前期設計階段,否則數據大幅增長後,開發人員需要面對重新優化內存所帶來開發和數據遷移的雙重成本。當Redis內存不足時,首先考慮的問題不是加機器做水平擴展,應該先嘗試做內存優化。當遇到瓶頸時,再去考慮水平擴展。即使對於集群化方案,垂直層面優化也同樣重要,避免不必要的資源浪費和集群化後的管理成本。
B. redis的五種數據類型
Redis五種數據類型分別是string(字元串),hash(哈希),list(列表),set(集合)及sortset(有序集合)。
字元串string字元串類型是Redis中最基本的數據存儲類型,它是一個由位元組組成的序列,在Rediss中是二進制安全的。這意味著該類型可以接受任何格式數據。
字元串
主要用於編程,概念說明、函數解釋、用法詳述見正文,這里補充一點:字元串在存儲上類似字元數組,所以它每一位的單個元素都是可以提取的,如s=「abcdefghij」,則s[1]=「b」,s[9]="j",這可以給我們提供很多方便,如高精度運算時每一位都可以轉化為數字存入數組。
C. redis怎麼存儲list對象
方案一:直接使用List結構,List裡面存儲二進制的任務Bean信息,這樣做查詢全部任務很方便,查詢單條任務速度較慢,並且刪除和修改狀態很麻煩;方案二:直接使用Hash結構,Hash的key存儲任務ID,value存儲二進制的Bean信息,這樣做查詢所有任務、查詢單條任務以及刪除任務都很快,但是修改狀態也必須先取出數據再修改再插入!
D. Redis的持久化機制 (RDB&amp;AOF&amp;混合模式)
RDB(Redis DataBase,快照方式) 是將某一個時刻的內存數據,以二進制的方式寫入磁碟。 AOF(備跡Append Only File,文件追加方式) 是指將所有的操作命令,以文本的形式追加到文件中。
RDB
RDB 默認的保存文件為 mp.rdb,優點是以二進制存儲的,因此 佔用的空間更小 、數據存儲更緊湊,並且與 AOF 相比,RDB 具備 更快的重啟恢復能力 。
AOF
AOF 默認的保存文件為 appendonly.aof,它的優點是存儲頻率更高,因此 丟失數據的風險就越低 ,並且 AOF 並不是以二進制存儲的,所以它的存儲信息更易懂。缺點是 佔用空間大 , 重啟之後的數據恢復速度比較慢 。
混合
在 Redis 4.0 就推出了混合持久化的功能。Redis 混合持久化的存儲模式是, 開始的數據以 RDB 的格式進行存儲 ,因此只會佔用少量的空間, 並且之後的命令會以 AOF 的方式進行數據追加 ,這樣就可以減低數據丟失的風險,同時可以提高數據恢復的速度。
Fork
Redis會單獨創建(fork)一個子進程來進行持久化,會先將數據寫入到一個臨時文件中,待持久化過程都結束了,再用這個臨時文件替換上次持久化好的文件。Fork的作用是復制一個與當前進程一樣的進程。新進程的所有數據(變數、環境變數、程序計數器等)數值都和原進程一致,但是是一個全新的進程,遲滾明並作為原進程的子進程。
AOF採用文件追加方式,文件會越來越大為避免出現此種情況,新增了重寫機制,當AOF文件的大小超過所設定的閾值時( 默認值 64M ),Redis就會啟動AOF文件的內容壓縮,只保留可以恢復數據的最小指令集。Redis 會fork出一條新進程來將 文件重寫Rewrite (也是先寫臨時文件最後再rename),遍歷新進程的內存中數據,每條記碼告錄有一條的set語句。重寫aof文件的操作,並沒有讀取舊的aof文件, 而是將整個內存中的資料庫內容用命令的方式重寫了一個新的aof文件,這點和快照有點類似。
E. redis有哪些存儲模式
Redis支持多種數據結構和存儲模式,其中包括:
字元串(String):字元串類型是Redis最基本的數據類型,它可以包含任何數據,比如文本、整數或二進制數據滾辯等。
哈希(Hash):哈希類型存儲的是鍵值對集合,這些鍵值對可以是字元串類型的,也可以是數字類型的。
列表(List):列表類型是一個有序的字元串列表,可以添加、刪除和插入元素。
集合(Set):集合類型存搏模儲的是一組唯一的無序元素,支持添加、刪除和查詢操作。
有序集合(Sorted Set):有序集合類型存儲的是一組有序的元素,每個元素都有一個分數(score),可以根據分數進行排序。
RDB持久化模式:在指定時間間隔內將內存中的數據保存到磁碟中。
AOF持久化模式:將所有對Redis資料庫的寫操作記錄下來,可以通過回放這些日誌文件來恢復資料庫。
混合持久化模式:同時使用RDB和AOF兩種持久化模式,以保證數大銀缺據的可靠性和恢復速度。
此外,Redis還支持多種不同的持久化模式,包括:
F. redis rdb和aof的區別
able. We seldom think of it. The days str
G. redis支持哪些數據類型
redis提供五種數據類型:string,hash,list,set及zset(sorted set)。
redis是一個key-value存儲系統。和Memcached類似,它支持存儲的value類型相對更多,包括string(字元串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。陸李在此基礎上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數據都是緩存在內存中。區別的是redis會周期性的把更新的數據寫入磁碟或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。
Redis 是一個高性能的key-value資料庫。 redis的出現,很大程度補償了memcached這類key/value存儲的不足,在部 分場合可以對關系資料庫起到很好的補充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客戶端,使用很方便。
Redis支持主從同步。數據可以從主伺服器向任意數量的從伺服器上同步,從伺服器可以是關聯其他從伺服器的主伺服器。這使得Redis可執行單層樹復制。存檔可以有意無意的對數據進行寫操作。由於完全實現了發布/訂閱機制,使得從資料庫在任何地方同步樹時,可訂閱一個頻道並接收主伺服器完整的消息發布記錄。同步對讀取森悉州操此蔽作的可擴展性和數據冗餘很有幫助。
H. redis數據類型和應用場景
Redis是當前比較熱門的NOSQL系統之一,它是一個開源的使用ANSI c語言編寫的key-value存儲系統(區別於MySQL的二維表格的形式存儲。),Redis數據都是緩存在計算機內存中並且它會周期性的把更新的數據寫入磁碟或者把修改操作寫入追加的記錄文件,實現數據的持久化。談到存儲數據,那麼必然要涉及到相關的數據類型,redis主要有以下數據類型:
描述:string 是 redis 最基本的類型,你可以理解成與 Memcached 一模一樣的類型,一個 key 對應一個 value。value其實不僅是String,也可以是數字。string 類型是二進制安全的。意思是 redis 的 string 可以包含任何數據。比如jpg圖片或者序列化的對象。string 類型是 Redis 最基本的數據類型,string 類型的值最大能存儲 512MB。
常用命令:get、set、incr、decr、mget等。
應用場景:規key-value緩存應用。常規計數: 點贊數, 粉絲數。
描述: hash 是一個鍵值(key => value)對集合。Redis hash 是一個 string 類型的 field 和 value 的映射表,hash 特別適合用於存儲對象。
常用命令:hget,hset,hgetall 等。
應用場景:存儲部分變更數據,如商品信息等。
描述:list 列表是簡單的字元串列表,按照插入順序排序。你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)。列表最多可存儲 232 - 1 元素 (4294967295, 每個列表可存儲40多億)。
常用命令:lpush(添加左邊元素),rpush,lpop(移除左邊第一個元素),rpop,lrange(獲取列表片段,LRANGE key start stop)等。
應用場景:消息隊列,關注列表,粉絲列表等都可以用Redis的list結構來實現。
描述: set是string類型的無序集合。集合是通過hashtable實現的,概念和數學中個的集合基本類似,可以交集,並集,差集等等,set中的元素是沒有順序的。所以添加,刪除,查找的復雜度都是O(1)。
常用命令:sadd,spop,smembers,sunion 等。
應用場景:交集,並集,差集(微博中,可以將一個用戶所有的關注人存在一個集合中,將其所有粉絲存在一個集合。Redis還為集合提供了求交集、並集、差集等操作,可以非常方便的實現如共同關注、共同喜好、二度好友等功能,對上面的所有集合操作,你還可以使用不同的命令選擇將結果返回給客戶端還是存集到一個新的集合中)
描述:zset 和 set 一樣也是string類型元素的集合,且不允許重復的成員。不同是可以打分(排序)
常用命令:zadd,zrange,zrem,zcard等
應用場景:排行榜,帶權重的消息隊列
描述:Bitmaps這個「數據結構」可以實現對位的操作。 把數據結構加上引號主要因為:
Bitmaps本身不是一種數據結構, 實際上它就是字元串 , 但是它可以對字元串的位進行操作。
Bitmaps單獨提供了一套命令, 所以在Redis中使用Bitmaps和使用字元串的方法不太相同。 可以把Bitmaps想像成一個以位為單位的數組, 數組的每個單元只能存儲0和1, 數組的下標在Bitmaps中叫做偏移量。其實大多數Bitmaps的應用場景可以用其他數據類型來實現,用Bitmaps主要是存儲空間佔用特別少
常用命令:getbit key offset;setbit key offset value
應用場景:統計用戶訪問,統計電影某天的的播放量
描述:Redis 在 2.8.9 版本添加了 HyperLogLog 結構。Redis HyperLogLog 是用來做基數統計的演算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定 的、並且是很小的。在 Redis 裡面,每個 HyperLogLog 鍵只需要花費 12 KB 內存,就可以計算接近 2^64 個不同元素的基 數。這和計算基數時,元素越多耗費內存就越多的集合形成鮮明對比。但是,因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。這類數據結構的基本大的思路就是使用統計概率上的演算法,犧牲數據的精準性來節省內存的佔用空間及提升相關操作的性能
常用命令:pfadd, pfcount,pfmerge
應用場景:統計網站的每日UV
描述:GEO功能在Redis3.2版本提供,支持存儲地理位置信息用來實現諸如附近位置、搖一搖這類依賴於地理位置信息的功能.geo的數據類型為zset.
常用命令:geoadd,geopos, geodist
應用場景:附近位置、搖一搖
參考列表:
Redis五種數據類型及應用場景