當前位置:成語大全網 - 新華字典 - Redis有哪些慢操作?

Redis有哪些慢操作?

從業務服務器到Redis服務器這條調用鏈路中變慢的原因可能有2個

但是大多數情況下都是Redis服務的問題。但是應該如何衡量Redis變慢了呢?命令執行時間大於1s,大於2s?這其實並沒有壹個固定的標準。

例如在壹個配置較高的服務器中,0.5毫秒就認為Redis變慢了,在壹個配置較低的服務器中,3毫秒才認為Redis變慢了。所以我們要針對自己的機器做基準測試,看平常情況下Redis處理命令的時間是多長?

我們可以使用如下命令來監測和統計測試期間的最大延遲(以微秒為單位)

比如執行如下命令

參數中的60是測試執行的秒數,可以看到最大延遲為3725微秒(3毫秒左右),如果命令的執行遠超3毫秒,此時Redis就有可能很慢了!

那麽Redis有哪些慢操作呢?

Redis的各種命令是在壹個線程中依次執行的,如果壹個命令在Redis中執行的時間過長,就會影響整體的性能,因為後面的請求要等到前面的請求被處理完才能被處理,這些耗時的操作有如下幾個部分

Redis可以通過日誌記錄那些耗時長的命令,使用如下配置即可

執行如下命令,就可以查詢到最近記錄的慢日誌

之前的文章我們已經介紹了Redis的底層數據結構,它們的時間復雜度如下表所示

名稱 時間復雜度 dict(字典) O(1) ziplist (壓縮列表) O(n) zskiplist (跳表) O(logN) quicklist(快速列表) O(n) intset(整數集合) O(n)

「單元素操作」 :對集合中的元素進行增刪改查操作和底層數據結構相關,如對字典進行增刪改查時間復雜度為O(1),對跳表進行增刪查時間復雜為O(logN)

「範圍操作」 :對集合進行遍歷操作,比如Hash類型的HGETALL,Set類型的SMEMBERS,List類型的LRANGE,ZSet類型的ZRANGE,時間復雜度為O(n),避免使用,用SCAN系列命令代替。(hash用hscan,set用sscan,zset用zscan)

「聚合操作」 :這類操作的時間復雜度通常大於O(n),比如SORT、SUNION、ZUNIONSTORE

「統計操作」 :當想獲取集合中的元素個數時,如LLEN或者SCARD,時間復雜度為O(1),因為它們的底層數據結構如quicklist,dict,intset保存了元素的個數

「邊界操作」 :list底層是用quicklist實現的,quicklist保存了鏈表的頭尾節點,因此對鏈表的頭尾節點進行操作,時間復雜度為O(1),如LPOP、RPOP、LPUSH、RPUSH

「當想獲取Redis中的key時,避免使用keys *」 ,Redis中保存的鍵值對是保存在壹個字典中的(和Java中的HashMap類似,也是通過數組+鏈表的方式實現的),key的類型都是string,value的類型可以是string,set,list等

例如當我們執行如下命令後,redis的字典結構如下

我們可以用keys命令來查詢Redis中特定的key,如下所示

keys命令的復雜度是O(n),它會遍歷這個dict中的所有key,如果Redis中存的key非常多,所有讀寫Redis的指令都會被延遲等待,所以千萬不用在生產環境用這個命令(如果妳已經準備離職的話,祝妳玩的開心)。

「既然不讓妳用keys,肯定有替代品,那就是scan」

scan是通過遊標逐步遍歷的,因此不會長時間阻塞Redis

「用用zscan遍歷zset,hscan遍歷hash,sscan遍歷set的原理和scan命令類似,因為hash,set,zset的底層實現的數據結構中都有dict。」

「如果壹個key對應的value非常大,那麽這個key就被稱為bigkey。寫入bigkey在分配內存時需要消耗更長的時間。同樣,刪除bigkey釋放內存也需要消耗更長的時間」

如果在慢日誌中發現了SET/DEL這種復雜度不高的命令,此時妳就應該排查壹下是否是由於寫入bigkey導致的。

「如何定位bigkey?」

Redis提供了掃描bigkey的命令

可以看到命令的輸入有如下3個部分

這個命令的原理就是redis在內部執行了scan命令,遍歷實例中所有的key,然後正對key的類型,分別執行strlen,llen,hlen,scard,zcard命令,來獲取string類型的長度,容器類型(list,hash,set,zset)的元素個數

使用這個命令需要註意如下兩個問題

「如何解決bigkey帶來的性能問題?」

我們可以給Redis中的key設置過期時間,那麽當key過期了,它在什麽時候會被刪除呢?

「如果讓我們寫Redis過期策略,我們會想到如下三種方案」

定時刪除策略對CPU不友好,當過期鍵比較多的時候,Redis線程用來刪除過期鍵,會影響正常請求的響應

惰性刪除讀CPU是比較有好的,但是會浪費大量的內存。如果壹個key設置過期時間放到內存中,但是沒有被訪問到,那麽它會壹直存在內存中

定期刪除策略則對CPU和內存都比較友好

redis過期key的刪除策略選擇了如下兩種

「惰性刪除」 客戶端在訪問key的時候,對key的過期時間進行校驗,如果過期了就立即刪除

「定期刪除」 Redis會將設置了過期時間的key放在壹個獨立的字典中,定時遍歷這個字典來刪除過期的key,遍歷策略如下

「因為Redis中過期的key是由主線程刪除的,為了不阻塞用戶的請求,所以刪除過期key的時候是少量多次」 。源碼可以參考expire.c中的activeExpireCycle方法

為了避免主線程壹直在刪除key,我們可以采用如下兩種方案

Redis是壹個內存數據庫,當Redis使用的內存超過物理內存的限制後,內存數據會和磁盤產生頻繁的交換,交換會導致Redis性能急劇下降。所以在生產環境中我們通過配置參數maxmemoey來限制使用的內存大小。

當實際使用的內存超過maxmemoey後,Redis提供了如下幾種可選策略。

「Redis的淘汰策略也是在主線程中執行的。但內存超過Redis上限後,每次寫入都需要淘汰壹些key,導致請求時間變長」

可以通過如下幾個方式進行改善

Redis的持久化機制有RDB快照和AOF日誌,每次寫命令之後後,Redis提供了如下三種刷盤機制

「當aof的刷盤機制為always,redis每處理壹次寫命令,都會把寫命令刷到磁盤中才返回,整個過程是在Redis主線程中進行的,勢必會拖慢redis的性能」

當aof的刷盤機制為everysec,redis寫完內存後就返回,刷盤操作是放到後臺線程中去執行的,後臺線程每隔1秒把內存中的數據刷到磁盤中

當aof的刷盤機制為no,宕機後可能會造成部分數據丟失,壹般不采用。

「壹般情況下,aof刷盤機制配置為everysec即可」

在持久化壹節中,我們已經提到 「Redis生成rdb文件和aof日誌重寫,都是通過主線程fork子進程的方式,讓子進程來執行的,主線程的內存越大,阻塞時間越長。」

可以通過如下方式優化

當機器的內存不夠時,操作系統會將部分內存的數據置換到磁盤上,這塊磁盤區域就是Swap分區,當應用程序再次訪問這些數據的時候,就需要從磁盤上讀取,導致性能嚴重下降

「當Redis性能急劇下降時就有可能是數據被換到Swap分區,我們該如何排查Redis數據是否被換到Swap分區呢?」

每壹行Size表示Redis所用的壹塊內存大小,Size下面的Swap表示這塊大小的內存,有多少已經被換到磁盤上了,如果這2個值相等,說明這塊內存的數據都已經被換到磁盤上了

我們可以通過如下方式來解決

最後我們總結壹下Redis的慢操作