Redis性能調優策略


一、設計優化

1、估算Redis內存使用量

  要估算redis中的數據占據的內存大小,需要對redis的內存模型有比較全面的了解,下面以最簡單的字符串類型來舉例說明:

  假設有90000個鍵值對,每個key的長度是12個字節,每個value的長度也是12個字節,且鍵值都不是整數類型。

  然后我們可以預估一下這90000個鍵值對占用的空間,首先,我們可以判定字符串類型使用的是embstr;90000個鍵值對占用的空間只要可以分為兩部分,一部分是90000個dictEntry占據的字符串,一部分是鍵值對所需要的bucket空間。

  每個dictEntry占據的空間包括:一個dictEntry結構、一個key、一個redisObject、一個value

    dictEntry結構:24個字節,jemalloc會分配32個字節的內存塊(64位操作系統下,一個指針8字節,一個dictEntry由三個指針組成)

    key:12個字節,所以SDS需要 12 + 4 = 16 個字節(SDS的長度=4 + 字符串長度),jemalloc會分配16個字節的內存塊

    redisObject:16字節,jemalloc會分配16個字節的內存塊(固定長度:4bit + 4bit + 2bit + 4byte + 8byte = 16byte)

    value:12個字節,所以SDS占16個字節,原理同key

    綜上,一個dictEntry所占的空間為: 32+16+16+16 = 80 個字節。

  bucket空間:

    bucket數組大小為大於90000的最小的2的n次方,是131072,每個bucket元素(bucket中存儲的是指針元素)為8字節(因為64位操作系統中指針大小位8個字節)。

  因此可以推算出,90000個鍵值對占據的內存大小為  90000*80 + 131072*8 = 82488576

2、優化內存占用

  了解了redis的內存模型,對優化redis內存有很大的幫助,下面從4個角度來進行優化:

  (1)利用jemalloc特性進行優化

  以上面講述的90000個鍵值對的例子,由於jemalloc分配的空間是不連續的,因此KV字符串變化一個字節,可能會引起占用內存的很大變化,在設計時可以利用這一點。例如,如果key的長度是13,則SDS為17個字節,那么jemalloc就會分配32個字節;如果將key的長度縮減為12個字節,則SDS為16個字節,jemalloc也會分配16個字節,這樣每個key所占用的空間就可以縮小一半。

  (2)使用整型/長整型

  如果使用整型或長整型,Redis會使用int類型(8個字節)存儲來替代字符串類型,可以節省更多空間,因此在可以使用整型或長整型代替字符串的場景下,盡量使用整型或長整型。

  (3)共享對象

  利用共享對象,可以減少對象的創建,同時也減少了redisObject的創建,從而節省空間。

  目前Redis中的共享對象只包括10000個整數(0-9999),可以通過調整REDIS_SHARED_INTEGERS參數提高共享對象的個數;例如將REDIS_SHARED_INTEGERS提高到20000,那么0-19999就都是共享對象。例如文章的瀏覽次數,基本上都是20000次以內,因此就可以將REDIS_SHARED_INTEGERS設置為20000,以便節省空間。

  (4)縮短鍵值對的存儲長度

  鍵值對的長度是和性能成反比的,比如我們做一組寫入數據的性能測試,執行結果如下:

 

   從數據可以看出,在key不變的情況下,value值越大操作效率就越慢,因為Redis對於同一種數據類型會使用不同的內部編碼進行存儲,比如字符串的內部編碼就有三種:int、embstr、raw,這是因為Redis是想通過不同的編碼實現效率和空間的平衡,然而數據量越大使用的內部編碼就越復雜,而越復雜的內部編碼存儲的性能就越低。

  這還只是寫入時的速度,當鍵值對內容較大時,還會帶來以下幾個問題:

    內容越大,需要的持久化時間就越長,需要掛起的時間越長,Redis的性能就越低。

    內容越大,在網絡上傳輸的內容就越多,需要的時間就越長,整體的運行速度就越低

    內容越大,占用的內存就越多,就會更頻繁的觸發內存淘汰機制,從而給Redis帶來更多的運行負擔。

  綜上所示,在盡量保證完整語義的同時,我們要盡量的縮短鍵值對的存儲長度,必要時要對數據進行序列化和壓縮再存儲,以Java為例,序列化我們可以使用protostuff或kryo,壓縮我們可以使用snappy。

二、設置鍵值的過期時間

  我們應該根據實際的業務情況,對鍵值對設置合理的過期時間,這樣Redis會自動的清楚過期的鍵值對,從而達到節省內存占用的效果,可以避免鍵值對過多的堆積,頻繁觸發內存淘汰策略。

  Redis有四個命令可以設置鍵的存活時間或過期時間:

  expire:用於設置key還剩余多少秒過期

  pexpire:用於設置key還有多少毫秒過期

  expireat:用於設置key的過期時間(到期的具體秒數時間戳)

  pexpireat:用於設置key的過期時間(到期的具體毫秒時間戳)

127.0.0.1:6388> set d1 va
OK
127.0.0.1:6388> set d2 va
OK
127.0.0.1:6388> set d3 va
OK
127.0.0.1:6388> set d4 va
OK
127.0.0.1:6388> expire d1 100
(integer) 1
127.0.0.1:6388> pexpire d2 100000
(integer) 1
127.0.0.1:6388> ttl d1
(integer) 83
127.0.0.1:6388> ttl d2
(integer) 91
127.0.0.1:6388> expireat d3 1612363856
(integer) 1
127.0.0.1:6388> ttl d3
(integer) 87127.0.0.1:6388> pexpireat d4 1612364096000
(integer) 1
127.0.0.1:6388> ttl d4
(integer) 112

三、限制Redis內存大小

  需要使用maxmemory來設置Redis的最大內存,例如 maxmemory 1GB

  在64位操作系統中,Redis的內存大小是沒有限制的,因為maxmemory配置項是被注釋掉的,這樣就會導致在Redis內存不足時,Redis會使用磁盤作為其虛擬內存,而當操作系統將Redis所用的內存分配至磁盤時,將會阻塞Redis進程,到處Redis出現延遲,從而影響Redis的整體性能,因此我們要限制Redis的內存大小為一個固定的值,並且該值不能大於服務器的內存。當Redis的運行達到此值時會觸發內部的淘汰策略,從而將內存回收。

  首先說一下淘汰策略LRU和LFU:

  LRU(最近最少被使用):新數據插入鏈表頭部;當命中緩存時,將數據移動到鏈表頭部;當鏈表滿時,將鏈表尾部數據丟棄

  LFU(最少使用):新數據插入鏈表頭部,並設置訪問次數,並按照訪問次數排序;沒插入或者命中一次就記一次數,然后重新排序;如果鏈表滿時,從鏈表尾部刪除;

  二者對比:LFU和LRU的側重點不同,LRU側重的是最近被訪問的數據不刪除,而LFU是保證訪問頻率最高的數據不被刪除,但是LFU有一個缺點,就是如果在某一個時段,某個key的訪問頻率非常高,那么該KEY就會變為熱點數據,但是實際上該KEY在其他時間節點不會被用到,那么會造成這些數據不會被刪除。

  Redis4.0之后有8種淘汰策略:

淘汰策略 說明
noeviction 不淘汰任何數據,當內存不足時,新增操作會報錯,Redis默認淘汰策略
allkeys-lru 淘汰整個鍵值對中最久未使用的數據
allkeys-random 隨即淘汰任意值
volatile-lru 淘汰所有設置了過期時間的key中最久的key
volatile-random 隨機淘汰所有設置了過期時間的key
volatile-ttl 優先淘汰最早過期的key
volatile-lfu

淘汰所有設置了過期時間的key中使用最少的key(4.0之后新增)

allkeys-lfu 淘汰所有key中的最少被使用的key(4.0之后新增)

 

四、使用 lazy free 特性

  lazy free特性是Redis4.0新增的一個非常實用的功能,他可以理解為惰性刪除或延遲刪除,意思就是在刪除的時候提供異步刪除的功能,他把刪除key的操作放在BIO單獨的子線程處理中,以減少刪除操作對Redis主線程的阻塞,同時可以有效的避免刪除大的key時帶來的性能和可用性問題。

  lazy free對應四種場景,默認都是關閉的:

lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no

  lazyfree-lazy-eviction:表示當Redis運行內存超過maxmemory時,是否開啟 lazy free 機制刪除。

  lazyfree-lazy-expire:表示設置了過期時間的key,是否開啟 lazy free 機制刪除。

  lazyfree-lazy-server-del:有些指令在操作已存在的key時,是否開啟 lazy free機制刪除。這個需要特別說明一下,有些指令,會自帶一個隱式的del命令,例如rename指令,當目標key存在時,會先刪除目標key,再創建一個新的key,如果目標key是一個 big key 時,就會造成阻塞刪除的情況,因此使用該配置來設置是否要在該種情況下開啟 lazy free 機制刪除數據。

  slave-lazy-flush:針對slave進行全量同步數據時,slave在加載master上的RDB前,會使用flushall來清理自己的數據,該配置用來設置該種場景下是否要開啟 lazy free 機制刪除數據。

  這里比較建議開啟前三種 lazy free 配置,這樣就可以有效的提高主線程的執行效率。

五、禁用長耗時的查詢命令

  Redis絕大多數的讀寫命令的時間復雜度都是在O(1)到O(N)之間,其中O(1)就可以放心使用,但是O(N)就要當心了,因為N表示不確定性,數據越大,查詢的速度就會越慢,因為Redis只用一個線程來做數據查詢,如果這些指令非常耗時,就會造成Redis的阻塞,那么就會造成大量的延時。

  要避免O(N)類的命令對Redis性能造成的影響,就要從以下幾個方面進行改造:

  1、禁止使用key * 命令;

  2、避免一次查詢所有成員,要使用 scan 命令進行分批的、游標式的遍歷

  3、通過機制嚴格空指Hset、Set、Scorted Set 等數據結構的大小

  4、將排序、並集、交集等操作放在客戶端執行,以減少Redis服務器的運行壓力

  5、刪除一個大的數據時,可能會需要很長的時間,所以建議用異步刪除的方式unlink,他會啟動一個新的線程來刪除目標數據,而不是阻塞Redis主線程。

六、使用 slowlog 優化耗時命令

  我們可以使用slowlog功能,找出最耗時的Redis命令進行優化,以提升Redis的運行速度,慢查詢有兩個重要的配置項:

    slowlog-log-slower-than:用於設置慢查詢的評定時間,也就是說執行時間超過改時間的命令,將會被當成慢查詢記錄在日志中,它的執行時間是微妙

    slowlog-max-len:用來配置慢查詢日志的最大記錄數

slowlog-log-slower-than 10000

# There is no limit to this length. Just be aware that it will consume memory.
# You can reclaim memory used by the slow log with SLOWLOG RESET.
slowlog-max-len 128

  我們可以根據實際的業務情況進行響應的配置,其中慢日志是按照插入順序倒敘存入慢查詢日志中的。

  我們可以使用 slowlog get n 來獲取相關的慢查詢日志,再找到這些慢查詢對應的業務進行相關優化。

127.0.0.1:6388> slowlog get 10
(empty list or set)

七、避免大量數據同時失效

  Redis過期Key的刪除使用的是貪心策略,其會在一秒內進行10次掃描(改配置可以在redis.conf文件中配置,默認配置是 hz 10),redis會隨機抽取20個key,刪除這20個key中過期的key,如果過期的key比重超過25%,則重復執行該流程,直到比重低於25%為止。

  那么如果在同一時期如果存在大量的key一起過期,就會導致Redis多次循環持續掃描刪除過期key,直到被刪除的數據足夠多(redis的過期數據不再很密集,隨機抽取低於25%),才會停止,在此過程中,由於循環和刪除的原因,會導致Redis的獨寫出現明顯的卡頓,卡頓的另一種原因是內存管理器需要頻繁回收內存,因此也會消耗一定的CPU。

  為了避免因為這種情況導致的卡頓現象,我們要預防大量的key在同一時刻一起過期,簡單的解決方案就是在過期時間的基礎上加一個指定范圍的隨機數。

八、檢查數據持久化策略

  Redis的持久化策略有RDB、AOF和混合持久化方式(4.0之后新增),由於RDB和AOF各有利弊,RDB可能會造成一定的數據丟失,AOF由於文件較大,會影響Redis的啟動速度,因此如果是Redis4.0及以上版本,可以直接使用混合持久化方式。

  同樣,如果在業務中,Redis不需要持久化數據庫時,可以關閉持久化,這樣可以有效地提升Redis的運行速度,不會出現間歇性卡頓的問題。

九、使用 Pipeline 批量操作數據

  Pipeline(管道技術)是客戶端提供的一種批處理技術,用於一次處理多個Redis命令,從而提高整個交互的性能。

十、客戶端優化

  在客戶端的使用上,我們盡量使用Pipeline技術外,還需要盡量使用Redis連接池,而不是頻繁的創建、銷毀Redis連接,這樣就可以減少網絡傳輸次數和減少非必要的調用命令。

十一、使用分布式架構來增加讀寫速度

  Redis的分布式架構有主從同步、哨兵模式、Redis Cluster集群,主從同步可以將寫操作放在master上處理,可以將讀操作放在slave上處理,因此極大的提高了redis的處理性能;而Sentinel是對主從同步的一個升級,解決了單點故障以及故障遷移的問題;而RedisCluster集群,在Redis3.0推出,解決了redis的擴展問題,並且將獨寫操作使用hash分布到了不同的redis服務器上,更大的提高了redis命令的操作性能。

  從選擇上,首選Redis Clluster集群的實現方案。

十二、使用物理機而非虛擬機

  在虛擬機中運行Redis服務器,因為和物理機共享一個物理端口,並且一台物理機可以有多個虛擬機在運行,因此在內存占用上和網絡延遲方面會有很糟糕的表現,我們可以通過 ./redis-cli -- intrinsic-latency 100 命令來超看延遲時間,如果對Redis性能要求很高的話,應盡可能在物理機上直接部署Redis服務器。

[root@liconglong-aliyun redis-5.0.4]# ./src/redis-cli -p 6388 --intrinsic-latency 100
Max latency so far: 1 microseconds.
Max latency so far: 11 microseconds.
Max latency so far: 13 microseconds.
Max latency so far: 21 microseconds.
Max latency so far: 57 microseconds.
Max latency so far: 136 microseconds.
Max latency so far: 416 microseconds.
Max latency so far: 498 microseconds.
Max latency so far: 536 microseconds.
Max latency so far: 577 microseconds.
Max latency so far: 692 microseconds.

十三、禁用 THP 特性

  Linux kernel在2.6.38內核增加了Transparent Huge Pages(THP)特性,支持大內存頁2M分配,默認開啟。

  當開啟了THP時,fork的速度會變慢,fork之后每個內存頁由原來的4KB變為2MB,會大幅增加重寫期間父進程的內存消耗,同時每次寫命令引起的復制內存頁放大了512倍,會拖慢寫操作的執行時間,導致大量寫操作慢查詢,例如簡單的incr命令也會出現在慢查詢中,因此Redis建議將此特性禁用,禁用方法如下:

echo never > /sys/kernel/mm/transparent_hugepage/enabled

  


免責聲明!

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



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