小物件壓縮
Redis是一種記憶體資料庫,記憶體是計算機中一種比較寶貴的資源,如果我們不注意節約,Redis很可能出現記憶體不足,最終導致崩潰。Redis為了優化資料結構的記憶體佔用,增加了非常多的優化點,這些優化也是犧牲程式碼的可讀性為代價,但是對於解決的記憶體問題來說是非常值得的,尤其是對於Redis這種記憶體資料庫
32bit VS 64bit
Redis如果使用32bit版本,內部儲存所有資料結構使用的指標空間佔用會少一半,如果Redis使用記憶體不會超過2^32 = 4GB,可以考慮32bit進行編譯,能節約大量記憶體。4GB容量對小站點來說戳戳由於。不足可以增加節點
指標是定址,儲存的是地址資訊,32bit系統能表示的總大小是2^32 位,所以指標能表示的定址最大值也就是 2 ^32,同理64bit 系統佔用2 ^ 64
小物件壓縮
如果Redis內部管理的集合資料結構很小,他會使用緊湊型儲存形式壓縮儲存,只有當集合資料達到一定量級的時候,Redis才會將資料儲存稱為正常我們熟知的狀態。 例如HashMap本來是二維結構,如果內部元素少,使用陣列+連結串列的形式反而浪費空間,因為指標佔用空間比資料還要多,還不如使用一維陣列進行儲存,查詢時候元素少,遍歷也快,只不過程式碼略複雜而已。以下案例用一維陣列儲存HashMap的CUD
public class ArrayMap < K, V> {
private List< K> keys = new ArrayList < > ( ) ;
private List< V> values = new ArrayList < > ( ) ;
public V put ( K k, V v) {
for ( int i = 0 ; i < keys. size ( ) ; i++ ) {
if ( keys. get ( i) . equals ( k) ) {
V oldv = values. get ( i) ;
values. set ( i, v) ;
return oldv;
}
}
keys. add ( k) ;
values. add ( v) ;
return null;
}
public V get ( K k) {
for ( int i = 0 ; i < keys. size ( ) ; i++ ) {
if ( keys. get ( i) . equals ( k) ) {
return values. get ( i) ;
}
}
return null;
}
public V delete ( K k) {
for ( int i = 0 ; i < keys. size ( ) ; i++ ) {
if ( keys. get ( i) . equals ( k) ) {
keys. remove ( k) ;
return values. remove ( i) ;
}
}
return null;
}
}
同樣Redis的ziplist是一個緊湊型的位元組陣列結構,如下圖中所示,每個元素都是緊密相連,中間沒有指標用來定址操作,通過三個全域性指標來完成邏輯處理,zlbytes, zltail和zlend。如下: 如果如上結果儲存的是hash結構,那麼key和value作為兩個entry,被相鄰的兩個entry儲存,通過如下方式驗證,輸出型別是ziplist:
新docker- redis: 0 > hset hello a 1
"1"
新docker- redis: 0 > hset hello b 2
"1"
新docker- redis: 0 > object encoding hello
"ziplist"
如果儲存的型別是zset,那麼value和score會作為兩個entry被相鄰儲存
新docker- redis: 0 > zadd world 1 a
"1"
新docker- redis: 0 > zadd world 2 b
"1"
新docker- redis: 0 > object encoding world
"ziplist"
整數的儲存Redis有一個叫intset的整數陣列結構,用於存放元素都是整數且元素個數較少的set集合。 如果整數可以用uint16標識,那麼intset的元素就是16位的陣列,如果新加入的整數超過uint16,那麼久用uint32,還不夠則用uint64,Redis支援set集合動態從unit16升級到unit32,在到unit64
如下程式碼,我們在set集合中儲存的數字,他的型別就是intset
新docker- redis: 0 > sadd hello 1 2
"2"
新docker- redis: 0 > object encoding hello
"intset"
如果set儲存的字串,那麼sadd會升級為hashtable結構,
新docker- redis: 0 > sadd hello yes no
"2"
新docker- redis: 0 > object encoding hello
"hashtable"
總結
hash - max - z 工pl 工 st entries 512 結構儲存
hash - max - ziplist- value 64 超過 64 就必須用標準結構儲存
list - max - ziplist- entries 512 結構儲存
list - max - z 工plist - value 64 用標準結構儲存
zset- max - ziplist- entries 128 結構儲存
zset- max - ziplist- value 64 用標準結構儲存
set - max - intset- entries 512 標準結構儲存
public static void main ( String[ ] args) {
Jedis jedis = JedisPoolTools. getJedis ( ) ;
jedis. del ( "books" ) ;
for ( int i = 0 ; i < 512 ; i++ ) {
jedis. hset ( "books" , String. valueOf ( i) , String. valueOf ( i) ) ;
}
System. out. println ( jedis. objectEncoding ( "books" ) ) ;
jedis. hset ( "books" , "hello" , "512" ) ;
System. out. println ( jedis. objectEncoding ( "books" ) ) ;
}
ziplist
hashtable
當Hash結構元素超過512 ,儲存結構發生變化其他的也都同理,我們通過如上總結資料看出,當hash結構的任意entry的value超過64 ,儲存結構就升級成標準結構。
記憶體回收機制
Redis並不總是將空閒記憶體理解歸還作業系統 例如Redis記憶體10GB,當刪除1GBkey後,觀察記憶體,並不會太大變化,因為作業系統是以頁為單位回收記憶體,這個頁內只要還有一個key,就不會被回收。Redis雖然刪除了1GBkey但是這些key是分散到各個page中,每個page都還有其他key,這就導致內不會被立刻回收。 我們執行flushdb,觀察記憶體,他就會重新使用那些尚未回收的空閒記憶體。就比如一個page中只有一個key,次數set另一個key,他會儲存在這個page的空閒記憶體中。
記憶體分配演算法
記憶體分配是比較複雜的,需要是的劃分記憶體頁,考慮記憶體碎片,需要平衡效能與效率 Redis為保證自身結構簡單,目前沒有自己做記憶體分配策略,由第三方庫完成此部分內容,
Redis可以用jemalloc,facebook的庫來管理記憶體 Redis也可以切換到tcmalloc,google的庫來管理 jemalloc效能優於tcmalloc,Redis預設使用jemalloc
新docker- redis: 0 > info memory
"# Memory
used_memory: 5110711968
. . . . .
mem_fragmentation_ratio: 1.33
mem_allocator: jemalloc- 4.0 .3
active_defrag_running: 0
lazyfree_pending_objects: 0
通過info memory指令可以看到Redis的 mem_allocator:jemalloc-4.0.3使用的jemalloc-4.0.3
上一篇:Redis–事務理解