Redis儲存優化--小物件壓縮

生病的毛毛蟲發表於2020-11-14

小物件壓縮

  • 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 的元素個數超過 512 就必須用標準
hash- max-ziplist-value 64 超過 64 就必須用標準結構儲存  	# hash 的任意元素的 key/value 的長度超過64 就必須用標準結構儲存
list-max-ziplist-entries 512 結構儲存  					# list 的元素個數超過 512 就必須用標準結構儲存
list-max-z 工plist - value 64 用標準結構儲存  			# list 的任意元素的長度超過 64 就必須用標準結構儲存
zset-max-ziplist-entries 128 結構儲存  					# zset 的元素個數超過 128 就必須用標準結構儲存
zset-max- ziplist-value 64 用標準結構儲存  				# zset 的任意元素的長度超過 64 就必須用標準結構儲存
set-max-intset-entries 512 標準結構儲存  					# set 的整數元素個數超過 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–事務理解

相關文章