緣起:
redis.clients.jedis.exceptions.JedisConnectionException:Could not get a resource from the pool
生產(chǎn)環(huán)境的業(yè)務(wù)服務(wù)器報了大量上面的錯誤。Jedis無法從連接池中獲取一個可用的連接,所有客戶端與Redis服務(wù)端保持通信的連接都在工作中没龙,沒有閑置的連接可以使用顽分。
? ? ? ?目前生產(chǎn)環(huán)境每天Redis的QPS在5000左右其徙,連接池配置20個最大連接數(shù)貌似是真的很小鳍侣,是不是增大連接池的配置就解決問題了?出現(xiàn)這個問題的根本原因是:連接池中的Jedis對象是有限的肥卡,如果Jedis一直被占用抵栈,沒有歸還告材,如果這時需要操作redis,就需要等待可用的Jedis古劲,當?shù)却龝r間超過maxWaitMillis斥赋,就會拋出could not get a resource from pool。以下幾種場景會出現(xiàn)這個問題:
1.并發(fā)實在太高了产艾,連接池中的連接數(shù)確實太小了疤剑,大量的請求等待空閑的連接。
2.由于Redis是單線程闷堡,某個查詢太慢隘膘,阻塞了其他操作命令的執(zhí)行。
3.Redis內(nèi)部問題導(dǎo)致處理客戶端的命令慢了杠览,比如RDB持久化時弯菊,fork進程做內(nèi)存快照;AOF持久化時踱阿,AOF文件重寫時會占用大量的CPU資源误续;
4.大量key同時過期吨悍。
以下數(shù)據(jù)來自于CAT對緩存的監(jiān)控數(shù)據(jù):藍線表示出現(xiàn)Could not get a resource from the pool的次數(shù),綠線表示QPS蹋嵌,從圖中可以看出隨著QPS的升高,出現(xiàn)異常的次數(shù)也在增高葫隙,難道真的是因為QPS高栽烂,連接池數(shù)小的原因?
CAT上按照小時為維度獲取緩存出現(xiàn)異常的數(shù)據(jù)如下:
從以下數(shù)據(jù)可以發(fā)現(xiàn)緩存出現(xiàn)異常的時間段都比較集中恋脚,而且間隔的時間段貌似存在著某種規(guī)律腺办。出現(xiàn)問題的時間段也并不是每天QPS最高的時候,QPS最高的幾個時間段反而沒有出現(xiàn)任何異常糟描。取了一個出現(xiàn)異常的時間段的緩存情況如下
發(fā)現(xiàn)這個時間段有幾個比較耗時的操作命令怀喉,但是這幾個命令在其他時間段最大耗時就10多毫秒。業(yè)務(wù)上也不存在不合理使用Redis數(shù)據(jù)結(jié)構(gòu)的問題船响。是該看看緩存的監(jiān)控情況了(這一部分圖片沒截)躬拢。
? ? ? ?找運維看了Redis的情況,發(fā)現(xiàn)Redis的某個時間段CPU飆到100%了见间,這個時間段和出現(xiàn)異常的時間段吻合聊闯。問題基本已經(jīng)確認,這個時間段Redis內(nèi)部一定發(fā)生了點什么米诉,導(dǎo)致處理客戶端的請求變慢了菱蔬,導(dǎo)致大量的請求被阻塞,超過maxWaitMillis時史侣,集中出現(xiàn)了大量的Could not get a resource from the pool異常拴泌。
? ? ? 生產(chǎn)環(huán)境Redis的持久化策略是AOF,AOF會將所有的寫命令按照一定頻率寫入到日志文件中惊橱,隨著AOF文件越來越大蚪腐,里面會有大部分是重復(fù)命令或者可以合并的命令(比如100次incr = set key 100),重寫可以減少AOF日志尺寸李皇,減少內(nèi)存占用削茁,加快數(shù)據(jù)庫恢復(fù)時間。AOF重寫的過程會fork一個子進程掉房,導(dǎo)致CPU飆到100%了茧跋。在這種情況下即使增大接池連接數(shù)也沒什么卵用。這個問題的解決思路是減少AOF重寫的頻率卓囚,兩種方式:
1瘾杭、讓Redis決定是否做AOF重寫操作,根據(jù)auto-aof-rewrite-percentage和auto-aof-rewrite-min-size兩個參數(shù)哪亿,auto-aof-rewrite-percentage:當前寫入日志文件的大小超過上一次rewrite之后的文件大小的百分之多少時重寫粥烁;auto-aof-rewrite-min-size:當前aof文件大于多少字節(jié)后才觸發(fā)
2贤笆、用crontab定時重寫,命令是:BGREWRITEAOF
上面提到慢查詢會阻塞Redis讨阻,那么業(yè)務(wù)開發(fā)同學(xué)在使用時如何避免呢芥永?
1、避免讓Redis執(zhí)行耗時長的命令钝吮,絕大多數(shù)讀寫命令的時間復(fù)雜度都在O(1)到O(N)之間埋涧,O(1)的命令是安全的,O(N)命令在使用時需要注意奇瘦,如果N的數(shù)量級不可預(yù)知棘催,應(yīng)避免使用,如對一個field數(shù)未知的Hash數(shù)據(jù)執(zhí)行HGETALL/HKEYS/HVALS命令耳标,通常來說這些命令執(zhí)行的很快醇坝,但如果這個Hash中的field數(shù)量極多,耗時就會成倍增長
2次坡、避免在使用這些O(N)命令時發(fā)生問題主要有幾個辦法:不要把List當做列表使用呼猪,僅當做隊列來使用,嚴格控制Hash贸毕、Set郑叠、Sorted Set的大小,將排序明棍、并集乡革、交集等操作放在客戶端執(zhí)行,禁止使用KEYS命令
3摊腋、避免一次性遍歷集合類型的所有成員沸版,而應(yīng)使用SCAN類的命令進行分批的,游標式的遍歷SSCAN/HSCAN/ZSCAN等命令兴蒸,分別用于對Set/Hash/Sorted Set中的元素進行游標式遍歷
4视粮、盡可能使用長連接或連接池,避免頻繁創(chuàng)建銷毀連接橙凳,使用pipelining將連續(xù)執(zhí)行的命令組合執(zhí)行