Redis優化經驗


千萬記住keys和smembers生產環境不要用, 有個網友的測試:

存取100萬數據,keys100萬數據,smembers 集合存放100萬數據  一起執行,顯示qps1757,cpu 使用11.0%,cpu馬上明顯飆高了。

 

項目 說明
List 數據類型 沒有 List 個數限制,單個元素最大值為 512 MB,推薦 list的元素個數小於 8192, value 最大長度不超過 1 MB。
Set 數據類型 沒有 set 個數限制,單個元素最大值為 512 MB,推薦 set 的元素個數小於 8192, value 最大長度不超過 1 MB。
Sorted set 數據類型 沒有 sorted set 個數限制,單個元素最大值為 512 MB,推薦 sorted set 的元素個數小於 8192, value 最大長度不超過 1 MB。
Hash 數據類型 沒有 field 個數限制,單個元素最大值為 512 MB,推薦元素個數小於 8192, value 最大長度不超過 1 MB。
DB 數限制 每個實例支持 256 個 DB。
Redis 命令支持 詳情請參見文檔
監控報警 雲數據庫 Redis 版未提供容量告警,需要用戶到雲監控中進行配置。配置方法請參見文檔
建議設置好以下監控的報警:實例故障、實例主備切換、已使用連接百分比、操作失敗數、已用容量百分比、寫入帶寬使用率、讀取帶寬使用率。
數據過期刪除策略 - 主動過期,系統后台會周期性的檢測,發現已過期的key時,會將其刪除。
- 被動過期,當用戶訪問某個key時,如果該key已經過期,則將其刪除。
空閑連接回收機制 服務端不主動回收 Redis 空閑連接,由用戶管理。
數據持久化策略 采用 AOF_FSYNC_EVERYSEC 方式,每秒 fysnc。

 

內存管理優化

        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時,你需要注意以下幾點:

1. 掌控儲存在Redis中的所有鍵

數據庫的主要功能是儲存數據,但是對於開發者來說,因為應用程序需求或者數據使用方法的改變,忽略存儲在數據庫中的某些數據是非常正常的,在Redis中同樣如此。你可能忽視期滿某些鍵,也可能因為應用程序的某個模塊棄用而忘掉這些數據。

無論哪種情況,Redis都存儲了一些不再使用的數據,平白無故的占用了一些空間。Redis的弱結構數據模式讓集中儲存的內容很難被弄清,除非你為鍵使用一套非常成熟的命名法則。使用合適的命名方法會簡化你的數據庫管理,當你通過你的應用程序或者服務做鍵的命名空間時(通常情況下是使用冒號來划分鍵名),你就可以在數據遷移、轉換或者刪除時輕松的識別。

Redis另一個常見用例是作為熱數據項作的第二數據存儲,大部分的數據被保存在其他的數據庫中,比如PostgreSQL或MongoDB。在這些用例中,當數據從主存儲移除時,開發者經常會忘記刪除Redis中對應的數據。這種存在跨數據存儲的情況下,通常需要做級聯刪除,這種情況下,可以通過在Redis配置保存特定數據項的所有識別符來實現,從而保證數據在主數據庫被刪除后,系統會調用一個清理程序來刪除所有相關副本和信息。

2. 控制所有鍵名的長度

在上文我們說過要使用合適的命名規則,並且添加前綴來識別數據走向,因此這一條看起來似乎與之違背。但是,請別忘記,Redis是個內存數據庫,鍵越短你需要的空間就越少。理所當然,當數據庫中擁有數百萬或者數十億鍵時,鍵名的長度將影響重大。

舉個例子:在一個32位的Redis服務器上,如果儲存一百萬個鍵,每個值的長度是32-character,那么在使用6-character長度鍵名時,將會消耗大約96MB的空間,但是如果使用12-character長度的鍵名時,空間消耗則會提升至111MB左右。隨着鍵的增多,15%的額外開銷將產生重大的影響。

3. 使用合適的數據結構

不管是內存使用或者是性能,有的時候數據結構將產生很大的影響,下面是一些可以參考的最佳實踐:

取代將數據存儲為數千(或者數百萬)獨立的字符串,可以考慮使用哈希數據結構將相關數據進行分組。哈希表是非常有效率的,並且可以減少你的內存使用;同時,哈希還更有益於細節抽象和代碼可讀。

合適時候,使用list代替set。如果你不需要使用set特性,List在使用更少內存的情況下可以提供比set更快的速度。

Sorted sets是最昂貴的數據結構,不管是內存消耗還是基本操作的復雜性。如果你只是需要一個查詢記錄的途徑,並不在意排序這樣的屬性,那么輕建議使用哈希表。

Redis中一個經常被忽視的功能就是bitmaps或者bitsets(V2.2之后)。Bitsets允許你在Redis值上執行多個bit-level操作,比如一些輕量級的分析。

4. 使用SCAN時別使用鍵

從Redis v2.8開始,SCAN命令已經可用,它允許使用游標從keyspace中檢索鍵。對比KEYS命令,雖然SCAN無法一次性返回所有匹配結果,但是卻規避了阻塞系統這個高風險,從而也讓一些操作可以放在主節點上執行。

需要注意的是,SCAN 命令是一個基於游標的迭代器。SCAN 命令每次被調用之后, 都會向用戶返回一個新的游標,用戶在下次迭代時需要使用這個新游標作為 SCAN 命令的游標參數, 以此來延續之前的迭代過程。同時,使用SCAN,用戶還可以使用keyname模式和count選項對命令進行調整。

SCAN相關命令還包括SSCAN 命令、HSCAN 命令和 ZSCAN 命令,分別用於集合、哈希鍵及有續集等。

5. 使用服務器端Lua腳本

在Redis使用過程中,Lua腳本的支持無疑給開發者提供一個非常友好的開發環境,從而大幅度解放用戶的創造力。如果使用得當,Lua腳本可以給性能和資源消耗帶來非常大的改善。取代將數據傳送給CPU,腳本允許你在最接近數據的地方執行邏輯,從而減少網絡延時和數據的冗余傳輸。

在Redis中,Lua一個非常經典的用例就是數據過濾或者將數據聚合到應用程序。通過將處理工作流封裝到一個腳本中,你只需要調用它就可以在更短的時間內使用很少的資源來獲取一個更小的答案。

專家提示:Lua確實非常棒,但是同樣也存在一些問題,比如很難進行錯誤報告和處理。一個明智的方法就是使用Redis的Pub/Sub功能,並且讓腳本通過專用信道來推送日志消息。然后建立一個訂閱者進程,並進行相應的處理。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM