Redis常見性能問題及解決方案
常見性能問題和解決方案?
- Master最好不要做任何持久化工作,包括內存快照和AOF日志文件,特別是不要啟用內存快照做持久化。
- 如果數(shù)據(jù)比較關鍵,某個Slave開啟AOF備份數(shù)據(jù),策略為每秒同步一次。
- 為了主從復制的速度和連接的穩(wěn)定性,Slave和Master最好在同一個局域網(wǎng)內。
- 盡量避免在壓力較大的主庫上增加從庫
- Master調用BGREWRITEAOF重寫AOF文件,AOF在重寫的時候會占大量的CPU和內存資源,導致服務load過高,出現(xiàn)短暫服務暫停現(xiàn)象。
- 為了Master的穩(wěn)定性,主從復制不要用圖狀結構,用單向鏈表結構更穩(wěn)定,即主從關系為:Master<–Slave1<–Slave2<–Slave3…,這樣的結構也方便解決單點故障問題,實現(xiàn)Slave對Master的替換,也即,如果Master掛了,可以立馬啟用Slave1做Master,其他不變。
使用批量操作減少網(wǎng)絡傳輸
一個 Redis 命令的執(zhí)行可以簡化為以下 4 步:
- 發(fā)送命令
- 命令排隊
- 命令執(zhí)行
- 返回結果
其中,第 1 步和第 4 步耗費時間之和稱為 Round Trip Time (RTT,往返時間) ,也就是數(shù)據(jù)在網(wǎng)絡上傳輸?shù)臅r間。
使用批量操作可以減少網(wǎng)絡傳輸次數(shù),進而有效減小網(wǎng)絡開銷,大幅減少 RTT。
另外,除了能減少 RTT 之外,發(fā)送一次命令的 socket I/O 成本也比較高(涉及上下文切換,存在read()和write()系統(tǒng)調用),批量操作還可以減少 socket I/O 成本。這個在官方對 pipeline 的介紹中有提到:https://redis.io/docs/manual/pipelining/ 。
Redis 中有一些原生支持批量操作的命令,比如:
MGET(獲取一個或多個指定 key 的值)、MSET(設置一個或多個指定 key 的值)、HMGET(獲取指定哈希表中一個或者多個指定字段的值)、HMSET(同時將一個或多個 field-value 對設置到指定哈希表中)、SADD(向指定集合添加一個或多個元素)- ……
不過,在 Redis 官方提供的分片集群解決方案 Redis Cluster 下,使用這些原生批量操作命令可能會存在一些小問題需要解決。就比如說 MGET 無法保證所有的 key 都在同一個 hash slot(哈希槽)上,MGET可能還是需要多次網(wǎng)絡傳輸,原子操作也無法保證了。不過,相較于非批量操作,還是可以節(jié)省不少網(wǎng)絡傳輸次數(shù)。
整個步驟的簡化版如下(通常由 Redis 客戶端實現(xiàn),無需我們自己再手動實現(xiàn)):
- 找到 key 對應的所有 hash slot;
- 分別向對應的 Redis 節(jié)點發(fā)起
MGET請求獲取數(shù)據(jù); - 等待所有請求執(zhí)行結束,重新組裝結果數(shù)據(jù),保持跟入?yún)?key 的順序一致,然后返回結果。
如果想要解決這個多次網(wǎng)絡傳輸?shù)膯栴},比較常用的辦法是自己維護 key 與 slot 的關系。不過這樣不太靈活,雖然帶來了性能提升,但同樣讓系統(tǒng)復雜性提升。
pipeline的作用?與原生批命令(mset和mget)的區(qū)別?
redis客戶端執(zhí)行一條命令分4個過程: 發(fā)送命令、命令排隊、命令執(zhí)行、返回結果。使用pipeline可以批量請求,批量返回結果,執(zhí)行速度比逐條執(zhí)行要快。
使用pipeline組裝的命令個數(shù)不能太多,不然數(shù)據(jù)量過大,增加客戶端的等待時間,還可能造成網(wǎng)絡阻塞,可以將大量命令的拆分多個小的pipeline命令完成。
原生批命令(mset和mget)與pipeline對比:
- 原生批命令是原子性,
pipeline是非原子性。pipeline命令中途異常退出,之前執(zhí)行成功的命令不會回滾。 - 原生批命令只有一個命令,但
pipeline支持多命令。
pipeline 和 Redis 事務的對比?
- 事務是原子操作,pipeline 是非原子操作。兩個不同的事務不會同時運行,而 pipeline 可以同時以交錯方式執(zhí)行。
- Redis 事務中每個命令都需要發(fā)送到服務端,而 Pipeline 只需要發(fā)送一次,請求次數(shù)更少。
事務可以看作是一個原子操作,但其實并不滿足原子性。當我們提到 Redis 中的原子操作時,主要指的是這個操作(比如事務、Lua 腳本)不會被其他操作(比如其他事務、Lua 腳本)打擾,并不能完全保證這個操作中的所有寫命令要么都執(zhí)行要么都不執(zhí)行。這主要也是因為 Redis 是不支持回滾操作。
說說為什么Redis過期了為什么內存沒釋放?
第一種情況,可能是覆蓋之前的key,導致key過期時間發(fā)生了改變。
當一個key在Redis中已經(jīng)存在了,但是由于一些誤操作使得key過期時間發(fā)生了改變,從而導致這個key在應該過期的時間內并沒有過期,從而造成內存的占用。
第二種情況是,Redis過期key的處理策略導致內存沒釋放。
一般Redis對過期key的處理策略有兩種:惰性刪除和定時刪除。
先說惰性刪除的情況
當一個key已經(jīng)確定設置了xx秒過期同時中間也沒有修改它,xx秒之后它確實已經(jīng)過期了,但是惰性刪除的策略它并不會馬上刪除這個key,而是當再次讀寫這個key時它才會去檢查是否過期,如果過期了就會刪除這個key。也就是說,惰性刪除策略下,就算key過期了,也不會立刻釋放內容,要等到下一次讀寫這個key才會刪除key。
而定時刪除會在一定時間內主動淘汰一部分已經(jīng)過期的數(shù)據(jù),默認的時間是每100ms過期一次。因為定時刪除策略每次只會淘汰一部分過期key,而不是所有的過期key,如果redis中數(shù)據(jù)比較多的話要是一次性全量刪除對服務器的壓力比較大,每一次只挑一批進行刪除,所以很可能出現(xiàn)部分已經(jīng)過期的key并沒有及時的被清理掉,從而導致內存沒有即時被釋放。
Redis突然變慢,有哪些原因?
- 存在bigkey。如果Redis實例中存儲了 bigkey,那么在淘汰刪除 bigkey 釋放內存時,也會耗時比較久。應該避免存儲 bigkey,降低釋放內存的耗時。
- 如果Redis 實例設置了內存上限 maxmemory,有可能導致 Redis 變慢。當 Redis 內存達到 maxmemory 后,每次寫入新的數(shù)據(jù)之前,Redis 必須先從實例中踢出一部分數(shù)據(jù),讓整個實例的內存維持在 maxmemory 之下,然后才能把新數(shù)據(jù)寫進來。
- 開啟了內存大頁。當 Redis 在執(zhí)行后臺 RDB 和 AOF rewrite 時,采用 fork 子進程的方式來處理。但主進程 fork 子進程后,此時的主進程依舊是可以接收寫請求的,而進來的寫請求,會采用 Copy On Write(寫時復制)的方式操作內存數(shù)據(jù)。
- 什么是寫時復制?
- 這樣做的好處是,父進程有任何寫操作,并不會影響子進程的數(shù)據(jù)持久化。
- 不過,主進程在拷貝內存數(shù)據(jù)時,會涉及到新內存的申請,如果此時操作系統(tǒng)開啟了內存大頁,那么在此期間,客戶端即便只修改 10B 的數(shù)據(jù),Redis 在申請內存時也會以 2MB 為單位向操作系統(tǒng)申請,申請內存的耗時變長,進而導致每個寫請求的延遲增加,影響到 Redis 性能。
- 解決方案就是關閉內存大頁機制。
- 使用了Swap。操作系統(tǒng)為了緩解內存不足對應用程序的影響,允許把一部分內存中的數(shù)據(jù)換到磁盤上,以達到應用程序對內存使用的緩沖,這些內存數(shù)據(jù)被換到磁盤上的區(qū)域,就是 Swap。當內存中的數(shù)據(jù)被換到磁盤上后,Redis 再訪問這些數(shù)據(jù)時,就需要從磁盤上讀取,訪問磁盤的速度要比訪問內存慢幾百倍。尤其是針對 Redis 這種對性能要求極高、性能極其敏感的數(shù)據(jù)庫來說,這個操作延時是無法接受的。解決方案就是增加機器的內存,讓 Redis 有足夠的內存可以使用?;蛘哒韮却婵臻g,釋放出足夠的內存供 Redis 使用
- 網(wǎng)絡帶寬過載。網(wǎng)絡帶寬過載的情況下,服務器在 TCP 層和網(wǎng)絡層就會出現(xiàn)數(shù)據(jù)包發(fā)送延遲、丟包等情況。Redis 的高性能,除了操作內存之外,就在于網(wǎng)絡 IO 了,如果網(wǎng)絡 IO 存在瓶頸,那么也會嚴重影響 Redis 的性能。解決方案:1、及時確認占滿網(wǎng)絡帶寬 Redis 實例,如果屬于正常的業(yè)務訪問,那就需要及時擴容或遷移實例了,避免因為這個實例流量過大,影響這個機器的其他實例。2、運維層面,需要對 Redis 機器的各項指標增加監(jiān)控,包括網(wǎng)絡流量,在網(wǎng)絡流量達到一定閾值時提前報警,及時確認和擴容。
- 頻繁短連接。頻繁的短連接會導致 Redis 大量時間耗費在連接的建立和釋放上,TCP 的三次握手和四次揮手同樣也會增加訪問延遲。應用應該使用長連接操作 Redis,避免頻繁的短連接。
什么是大key?
通常我們會將含有較大數(shù)據(jù)或含有大量成員、列表數(shù)的Key稱之為大Key。
以下是對各個數(shù)據(jù)類型大key的描述:
- value是STRING類型,它的值超過5MB
- value是ZSET、Hash、List、Set等集合類型時,它的成員數(shù)量超過1w個
上述的定義并不絕對,主要是根據(jù)value的成員數(shù)量和大小來確定,根據(jù)業(yè)務場景確定標準。
大Key造成的問題?
- 客戶端超時阻塞:由于 Redis 執(zhí)行命令是單線程處理,然后在操作大 key 時會比較耗時,那么就會阻塞 Redis,從客戶端這一視角看,就是很久很久都沒有響應。
- 網(wǎng)絡阻塞:每次獲取大 key 產(chǎn)生的網(wǎng)絡流量較大,如果一個 key 的大小是 1 MB,每秒訪問量為 1000,那么每秒會產(chǎn)生 1000MB 的流量,這對于普通千兆網(wǎng)卡的服務器來說是災難性的。
- 工作線程阻塞:如果使用 del 刪除大 key 時,會阻塞工作線程,這樣就沒辦法處理后續(xù)的命令。
- 持久化阻塞(磁盤IO):對AOF 日志的影響
- 使用Always 策略的時候,
主線程在執(zhí)行完命令后,會把數(shù)據(jù)寫入到 AOF 日志文件,然后會調用 fsync() 函數(shù),將內核緩沖區(qū)的數(shù)據(jù)直接寫入到硬盤,等到硬盤寫操作完成后,該函數(shù)才會返回。因此當使用 Always 策略的時候,如果寫入是一個大 Key,主線程在執(zhí)行 fsync() 函數(shù)的時候,阻塞的時間會比較久,因為當寫入的數(shù)據(jù)量很大的時候,數(shù)據(jù)同步到硬盤這個過程是很耗時的。 - 另外兩種策略都不影響主線程
- 使用Always 策略的時候,
大 key 造成的阻塞問題還會進一步影響到主從同步和集群擴容。
大key怎么處理?
- 壓縮數(shù)據(jù):當vaule是string時,可以使用序列化、壓縮算法將key的大小控制在合理范圍內,但是序列化和反序列化都會帶來更多時間上的消耗?;蛘邔ey進行拆分,一個大key分為不同的部分,記錄每個部分的key,使用multiget等操作實現(xiàn)事務讀取。
- 分拆大Key :當value是list/set等集合類型時,根據(jù)預估的數(shù)據(jù)規(guī)模來進行分片,不同的元素計算后分到不同的片。
什么是 hotkey?
如果一個 key 的訪問次數(shù)比較多且明顯多于其他 key 的話,那這個 key 就可以看作是 hotkey(熱 Key)。例如在 Redis 實例的每秒處理請求達到 5000 次,而其中某個 key 的每秒訪問量就高達 2000 次,那這個 key 就可以看作是 hotkey。
hotkey 出現(xiàn)的原因主要是某個熱點數(shù)據(jù)訪問量暴增,如重大的熱搜事件、參與秒殺的商品。
hotkey 有什么危害?
處理 hotkey 會占用大量的 CPU 和帶寬,可能會影響 Redis 實例對其他請求的正常處理。此外,如果突然訪問 hotkey 的請求超出了 Redis 的處理能力,Redis 就會直接宕機。這種情況下,大量請求將落到后面的數(shù)據(jù)庫上,可能會導致數(shù)據(jù)庫崩潰。
因此,hotkey 很可能成為系統(tǒng)性能的瓶頸點,需要單獨對其進行優(yōu)化,以確保系統(tǒng)的高可用性和穩(wěn)定性。
如何解決 hotkey?
hotkey 的常見處理以及優(yōu)化辦法如下(這些方法可以配合起來使用):
- 讀寫分離:主節(jié)點處理寫請求,從節(jié)點處理讀請求。
- 使用 Redis Cluster:將熱點數(shù)據(jù)分散存儲在多個 Redis 節(jié)點上。
- 熱點key拆分:將熱點數(shù)據(jù)分散到多個 Key 中,例如通過引入隨機前綴,使不同用戶請求分散到多個 Key,多個 key 分布在多實例中,避免集中訪問單一 Key。
- 多級緩存:在redis前增加其他緩存層(本地緩存、CDN)來緩解Redis的壓力,從而減少對hotkey的直接訪問。
- 限流和降級:在熱點Key 訪問過高時,應用限流策略,減少對 Redis 的請求,或者在必要時返回降級的數(shù)據(jù)或空值。
慢查詢命令
經(jīng)常使用O(n)以上復雜度的命令,由于Redis是單線程執(zhí)行命令,因此這種情況Redis處理數(shù)據(jù)時就會很耗時。例如
- sort:對列表(list)、集合(set)、有序集合(sorted set)中的元素進行排序。在最簡單的情況下(沒有權重、沒有模式、沒有
LIMIT),SORT命令的時間復雜度近似于O(n*log(n)) - sunion:用于計算兩個或多個集合的并集。時間復雜度可以描述為
O(N),其中N是所有參與運算集合的元素總數(shù)。如果有多個集合,每個集合有不同數(shù)量的元素參與運算,那么復雜度會是所有這些集合元素數(shù)量的總和。 - zunionstore:用于計算一個或多個有序集合的并集,并將結果存儲到一個新的有序集合中。在最簡單的情況下,
ZUNIONSTORE命令的時間復雜度是O(N*log(N)),其中N是所有參與計算的集合中元素的總數(shù)。 - keys * :獲取所有的 key 操作;復雜度
O(n),數(shù)據(jù)量越大執(zhí)行速度越慢;可以使用scan命令替代 - Hgetall:返回哈希表中所有的字段和;
- smembers:返回集合中的所有成員;
解決方案就是,不使用這些復雜度較高的命令,并且一次不要獲取太多的數(shù)據(jù),每次盡量操作少量的數(shù)據(jù),讓Redis可以及時處理返回
keys命令存在的問題?如何高效安全的遍歷所有key?
redis的單線程的。keys指令會導致線程阻塞一段時間,直到執(zhí)行完畢,服務才能恢復。scan采用漸進式遍歷的方式來解決keys命令可能帶來的阻塞問題,每次scan命令的時間復雜度是O(1),但是要真正實現(xiàn)keys的功能,需要執(zhí)行多次scan。
scan的缺點:在scan的過程中如果有鍵的變化(增加、刪除、修改),遍歷過程可能會有以下問題:新增的鍵可能沒有遍歷到,遍歷出了重復的鍵等情況,也就是說scan并不能保證完整的遍歷出來所有的鍵。
什么是內存碎片?為什么會有 Redis 內存碎片?
可以將內存碎片簡單地理解為那些不可用的空閑內存。
當數(shù)據(jù)刪除后,Redis 釋放的內存空間會由內存分配器管理,并不會立即返回給操作系統(tǒng)。所以,操作系統(tǒng)仍然會記錄著給 Redis 分配了大量內存。而Redis 釋放的內存空間可能并不是連續(xù)的,那么,這些不連續(xù)的內存空間很有可能處于一種閑置的狀態(tài)。也就產(chǎn)生了內存碎片
舉個例子:Redis 默認使用 jemalloc 作為內存分配器,它是按照固定大小來分配內存的,比如實際需要 8kb 的內存,分配器給了 12kb。那么多余的 4kb 其實就無法被利用上了,它就叫內存碎片。
Redis 內存碎片雖然不會影響 Redis 性能,但是會增加內存消耗。
如何清理 Redis 內存碎片?
- 最簡單的方法就是重啟節(jié)點了,如果你采用的是高可用架構的 Redis 集群的話,你可以將碎片率過高的主節(jié)點轉換為從節(jié)點,以便進行安全重啟。
- Redis4.0-RC3 版本以后自帶了內存整理,可以避免內存碎片率過大的問題。
直接通過 config set 命令將 activedefrag 配置項設置為 yes 即可。
config set activedefrag yes
具體什么時候清理需要通過下面兩個參數(shù)控制:
# 內存碎片占用空間達到 500mb 的時候開始清理 config set active-defrag-ignore-bytes 500mb # 內存碎片率大于 1.5 的時候開始清理 config set active-defrag-threshold-lower 50
通過 Redis 自動內存碎片清理機制可能會對 Redis 的性能產(chǎn)生影響,我們可以通過下面兩個參數(shù)來減少對 Redis 性能的影響:
# 內存碎片清理所占用 CPU 時間的比例不低于 20% config set active-defrag-cycle-min 20 # 內存碎片清理所占用 CPU 時間的比例不高于 50% config set active-defrag-cycle-max 50
Redis的Key和Value的設計原則有哪些?
Key 設計原則
- 短小精煉: 避免過長:Key 應該盡量短小,以節(jié)省內存和提高操作速度,通常不超過 256 字節(jié)。
- 使用命名空間: 分隔符:使用冒號(:)作為分隔符來組織命名空間,有助于實現(xiàn) Key 的層級結構管理。 例如 user:1001:profile,可以很好地反映數(shù)據(jù)的邏輯分層關系。
- 避免熱 Key: 負載均衡:確保 Key 的分布均勻,避免某單一 Key 承擔過多的訪問壓力,可能需對數(shù)據(jù)進行分片處理。
- 選擇唯一和通用的標識方式: 全局唯一性:確保 Key 的唯一性,避免不同數(shù)據(jù)使用相同的 Key。 結合業(yè)務邏輯,如使用用戶ID、產(chǎn)品ID等。
Value 設計原則
- 選擇合適的數(shù)據(jù)結構:對應使用:根據(jù)不同的需求選擇適當?shù)臄?shù)據(jù)類型,如 String、List、Set、Hash、Sorted Set 等。 避免存儲過大對象:如需存儲大對象,建議先進行拆分或壓縮。
- 限制單個 Value 的大小: 分片存儲:對于需要存儲大量數(shù)據(jù)的 Value,可以考慮拆分成多部分存儲,以降低單個操作的復雜度。
- 合理設置Blob:如果需要存儲Blob數(shù)據(jù),考慮放在外部存儲引擎中,只將引用或索引保存在 Redis。
- 利用壓縮: 節(jié)省空間:對數(shù)據(jù)進行壓縮,以減少內存占用和網(wǎng)絡傳輸時間。
- TTL設置: 數(shù)據(jù)過期:合理使用 TTL 來控制數(shù)據(jù)的生命周期,避免無用數(shù)據(jù)長期占用內存。
Redis 性能瓶頸時如何處理?
如果 Redis 無法承受當前的負載的話,可以考慮從以下幾個解決方法去解決:
- 首先想到的是擴容,比如增加 Redis 的配置,容納更多的內存等。
- 如果超過單機配置了,那么可以上 redis 主從,通過從服務分擔讀取數(shù)據(jù)的壓力,利用哨兵自動進行故障轉移
- 還可以利用 redis 集群進行數(shù)據(jù)分片,比如 Redis Cluster。
- 也可以增加本地內存,通過多級緩存分擔 redis 的壓力。
到此這篇關于Redis常見性能問題及解決方案的文章就介紹到這了,更多相關Redis性能內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

