緣起:
redis.clients.jedis.exceptions.JedisConnectionException:Could not get a resource from the pool
生產環境的業務服務器報了大量上面的錯誤。Jedis無法從連接池中獲取一個可用的連接,所有客戶端與Redis服務端保持通信的連接都在工作中,沒有閑置的連接可以使用。
? ? ? ?目前生產環境每天Redis的QPS在5000左右,連接池配置20個最大連接數貌似是真的很小,是不是增大連接池的配置就解決問題了?出現這個問題的根本原因是:連接池中的Jedis對象是有限的,如果Jedis一直被占用,沒有歸還,如果這時需要操作redis,就需要等待可用的Jedis,當等待時間超過maxWaitMillis,就會拋出could not get a resource from pool。以下幾種場景會出現這個問題:
1.并發實在太高了,連接池中的連接數確實太小了,大量的請求等待空閑的連接。
2.由于Redis是單線程,某個查詢太慢,阻塞了其他操作命令的執行。
3.Redis內部問題導致處理客戶端的命令慢了,比如RDB持久化時,fork進程做內存快照;AOF持久化時,AOF文件重寫時會占用大量的CPU資源;
4.大量key同時過期。
以下數據來自于CAT對緩存的監控數據:藍線表示出現Could not get a resource from the pool的次數,綠線表示QPS,從圖中可以看出隨著QPS的升高,出現異常的次數也在增高,難道真的是因為QPS高,連接池數小的原因?
CAT上按照小時為維度獲取緩存出現異常的數據如下:
從以下數據可以發現緩存出現異常的時間段都比較集中,而且間隔的時間段貌似存在著某種規律。出現問題的時間段也并不是每天QPS最高的時候,QPS最高的幾個時間段反而沒有出現任何異常。取了一個出現異常的時間段的緩存情況如下
發現這個時間段有幾個比較耗時的操作命令,但是這幾個命令在其他時間段最大耗時就10多毫秒。業務上也不存在不合理使用Redis數據結構的問題。是該看看緩存的監控情況了(這一部分圖片沒截)。
? ? ? ?找運維看了Redis的情況,發現Redis的某個時間段CPU飆到100%了,這個時間段和出現異常的時間段吻合。問題基本已經確認,這個時間段Redis內部一定發生了點什么,導致處理客戶端的請求變慢了,導致大量的請求被阻塞,超過maxWaitMillis時,集中出現了大量的Could not get a resource from the pool異常。
? ? ? 生產環境Redis的持久化策略是AOF,AOF會將所有的寫命令按照一定頻率寫入到日志文件中,隨著AOF文件越來越大,里面會有大部分是重復命令或者可以合并的命令(比如100次incr = set key 100),重寫可以減少AOF日志尺寸,減少內存占用,加快數據庫恢復時間。AOF重寫的過程會fork一個子進程,導致CPU飆到100%了。在這種情況下即使增大接池連接數也沒什么卵用。這個問題的解決思路是減少AOF重寫的頻率,兩種方式:
1、讓Redis決定是否做AOF重寫操作,根據auto-aof-rewrite-percentage和auto-aof-rewrite-min-size兩個參數,auto-aof-rewrite-percentage:當前寫入日志文件的大小超過上一次rewrite之后的文件大小的百分之多少時重寫;auto-aof-rewrite-min-size:當前aof文件大于多少字節后才觸發
2、用crontab定時重寫,命令是:BGREWRITEAOF
上面提到慢查詢會阻塞Redis,那么業務開發同學在使用時如何避免呢?
1、避免讓Redis執行耗時長的命令,絕大多數讀寫命令的時間復雜度都在O(1)到O(N)之間,O(1)的命令是安全的,O(N)命令在使用時需要注意,如果N的數量級不可預知,應避免使用,如對一個field數未知的Hash數據執行HGETALL/HKEYS/HVALS命令,通常來說這些命令執行的很快,但如果這個Hash中的field數量極多,耗時就會成倍增長
2、避免在使用這些O(N)命令時發生問題主要有幾個辦法:不要把List當做列表使用,僅當做隊列來使用,嚴格控制Hash、Set、Sorted Set的大小,將排序、并集、交集等操作放在客戶端執行,禁止使用KEYS命令
3、避免一次性遍歷集合類型的所有成員,而應使用SCAN類的命令進行分批的,游標式的遍歷SSCAN/HSCAN/ZSCAN等命令,分別用于對Set/Hash/Sorted Set中的元素進行游標式遍歷
4、盡可能使用長連接或連接池,避免頻繁創建銷毀連接,使用pipelining將連續執行的命令組合執行