Redis最佳化經驗

來源:互聯網
上載者:User

標籤:

記憶體管理最佳化

        Redis Hash是value內部為一個HashMap,如果該Map的成員數比較少,則會採用類似一維線性緊湊格式來儲存該Map, 即省去了大量指標的記憶體開銷,這個參數控制對應在redis.conf設定檔中下面2項:

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

        當value這個Map內部不超過多少個成員時會採用線性緊湊格式儲存,預設是64,即value內部有64個以下的成員就是使用線性緊湊儲存,超過該值自動轉成真正的HashMap。

        hash-max-zipmap-value 含義是當 value這個Map內部的每個成員值長度不超過多少位元組就會採用線性緊湊儲存來節省空間的。

        以上2個條件任意一個條件超過設定值都會轉換成真正的HashMap,也就不會再節省記憶體了,那麼這個值是不是設定的越大越好呢,答案當然是否定的,HashMap的優勢就是尋找和操作的時間複雜度都是O(1)的,而放棄Hash採用一維儲存則是O(n)的時間複雜度,如果

成員數量很少,則影響不大,否則會嚴重影響效能,所以要權衡好這個值的設定,總體上還是最根本的時間成本和空間成本上的權衡。

 

list-max-ziplist-value 64 list-max-ziplist-entries 512

        list資料類型節點值大小小於多少位元組會採用緊湊儲存格式、list資料類型多少節點以下會採用去指標的緊湊儲存格式。

 

記憶體預分配:

        Redis內部實現沒有對記憶體配置方面做過多的最佳化(對比Memcache),在一定程度上會存在記憶體片段,不過大多數情況下這個不會成為Redis的效能瓶頸,不過如果在Redis內部儲存的大部分資料是數值型的話,Redis內部採用了一個shared integer的 方式來省去分配記憶體的開銷,即在系統啟動時先分配一個從1~n 那麼多個數值對象放在一個池子中,如果儲存的資料恰好是這個數值範圍內的資料,則直接從池子裡取出該對象,並且通過引用計數的方式來共用,這樣在系統儲存 了大量數值下,也能一定程度上節省記憶體並且提高效能,這個參數值n的設定需要修改原始碼中的一行宏定義REDIS_SHARED_INTEGERS,該值 預設是10000,可以根據自己的需要進行修改,修改後重新編譯就可以了。

 

持久化機制:

定時快照方式(snapshot):

        該持久化方式實際是在Redis內部一個定時器事件,每隔固定時間去檢查當前資料發生的改變次數與時間是否滿足配置的持久化觸發的條件,如果滿足則通 過作業系統fork調用來建立出一個子進程,這個子進程預設會與父進程共用相同的地址空間,這時就可以通過子進程來遍曆整個記憶體來進行儲存操作,而主進程 則仍然可以提供服務,當有寫入時由作業系統按照記憶體頁(page)為單位來進行copy-on-write保證父子進程之間不會互相影響。

        該持久化的主要缺點是定時快照只是代表一段時間內的記憶體映像,所以系統重啟會丟失上次快照與重啟之間所有的資料。

基於語句追加方式(aof):

        aof方式實際類似mysql的基於語句的binlog方式,即每條會使Redis記憶體資料發生改變的命令都會追加到一個log檔案中,也就是說這個log檔案就是Redis的持久化資料。

        aof的方式的主要缺點是追加log檔案可能導致體積過大,當系統重啟恢複資料時如果是aof的方式則載入資料會非常慢,幾十G的資料可能需要幾小時才能載入完,當然這個耗時並不是因為磁碟檔案讀取速度慢,而是由於讀取的所有命令都要在記憶體中執行一遍。另外由於每條命令都要寫log,所以使用aof的方式,Redis的讀寫效能也會有所下降。

        可以考慮將資料儲存到不同的Redis執行個體中,每個執行個體的記憶體大小在2G左右,避免將雞蛋放到一個籃子裡,既可以減少緩衝失效給系統帶來的影響,又可以加快資料恢複的速度,不過同時也給系統設計帶來了一定的複雜性。

 

Redis持久化崩潰問題:

        有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時就會開始比較危險了。

 

總結:
  1. 根據業務需要選擇合適的資料類型,並為不同的應用情境設定相應的緊湊儲存參數。
  2. 當業務情境不需要資料持久化時,關閉所有的持久化方式可以獲得最佳的效能以及最大的記憶體使用量量。
  3. 如果需要使用持久化,根據是否可以容忍重啟丟失部分資料在快照方式與語句追加方式之間選擇其一,不要使用虛擬記憶體以及diskstore方式。
  4. 不要讓你的Redis所在機器實體記憶體使用超過實際記憶體總量的3/5。

 

redis.conf中的maxmemory選項,該選項是告訴Redis當使用了多少實體記憶體後就開始拒絕後續的寫入請求,該參數能很好的保護好你的Redis不會因為使用了過多的實體記憶體而導致swap,最終嚴重影響效能甚至崩潰。

redis.conf檔案中 vm-enabled 為 no

 

常用記憶體最佳化手段與參數

通過我們上面的一些實現上的分析可以看出redis實際上的記憶體管理成本非常高,即佔用了過多的記憶體,作者對這點也非常清楚,所以提供了一系列的參數和手段來控制和節省記憶體,我們分別來討論下。

首先最重要的一點是不要開啟Redis的VM選項,即虛擬記憶體功能,這個本來是作為Redis儲存超出實體記憶體資料的一種資料在記憶體與磁碟換入換出的一個持久化策略,但是其記憶體管理成本也非常的高,並且我們後續會分析此種持久化策略並不成熟,所以要關閉VM功能,請檢查你的redis.conf檔案中 vm-enabled 為 no。

其次最好設定下redis.conf中的maxmemory選項,該選項是告訴Redis當使用了多少實體記憶體後就開始拒絕後續的寫入請求,該參數能很好的保護好你的Redis不會因為使用了過多的實體記憶體而導致swap,最終嚴重影響效能甚至崩潰。

另外Redis為不同資料類型分別提供了一組參數來控制記憶體使用量,我們在前面詳細分析過Redis Hash是value內部為一個HashMap,如果該Map的成員數比較少,則會採用類似一維線性緊湊格式來儲存該Map, 即省去了大量指標的記憶體開銷,這個參數控制對應在redis.conf設定檔中下面2項:

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,也就不會再節省記憶體了,那麼這個值是不是設定的越大越好呢,答案當然是否定的,HashMap的優勢就是尋找和操作的時間複雜度都是O(1)的,而放棄Hash採用一維儲存則是O(n)的時間複雜度,如果

成員數量很少,則影響不大,否則會嚴重影響效能,所以要權衡好這個值的設定,總體上還是最根本的時間成本和空間成本上的權衡。

同樣類似的參數還有:

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的持久化方式與傳統資料庫的方式有比較多的差別,Redis一共支援四種持久化方式,分別是:

  • 定時快照方式(snapshot)
  • 基於語句追加檔案的方式(aof)
  • 虛擬記憶體(vm)
  • Diskstore方式

在設計思路上,前兩種是基於全部資料都在記憶體中,即小資料量下提供磁碟落地功能,而後兩種方式則是作者在嘗試儲存資料超過實體記憶體時,即大資料量的資料存放區,截止到本文,後兩種持久化方式仍然是在實驗階段,並且vm方式基本已經被作者放棄,所以實際能在生產環境用的只有前兩種,換句話說Redis目前還只能作為小資料量儲存(全部資料能夠載入在記憶體中),海量資料存放區方面並不是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持久化磁碟IO方式及其帶來的問題

有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時就會開始比較危險了。

是Redis在讀取或者寫入快照檔案dump.rdb後的記憶體資料圖:

總結:
  1. 根據業務需要選擇合適的資料類型,並為不同的應用情境設定相應的緊湊儲存參數。
  2. 當業務情境不需要資料持久化時,關閉所有的持久化方式可以獲得最佳的效能以及最大的記憶體使用量量。
  3. 如果需要使用持久化,根據是否可以容忍重啟丟失部分資料在快照方式與語句追加方式之間選擇其一,不要使用虛擬記憶體以及diskstore方式。
  4. 不要讓你的Redis所在機器實體記憶體使用超過實際記憶體總量的3/5。

Redis最佳化經驗

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.