記一次Redis資料庫配置導致的連線數洩露的問題

2021-10-09 01:27:04 字數 3639 閱讀 5872

去年聖誕節當天,突然收到乙個我經手過的專案的告警郵件,錯誤訊息顯示「redis::commanderror: err max number of clients reached」

什麼情況?難道這個專案翻車了?第一反應是這台伺服器執行著自建的 redis 資料庫,但是客戶端只有同個內網的乙個 ruby on rails 的應用,怎麼會有連線數爆掉的可能?

理論連線數計算

老衲掐指一算:

sidekiq 客戶端所需連線數: 對面 rails 應用有 10 個 unicorn 工作程序,每個unicorn程序初始化乙個 sidekiq 客戶端,乙個 sidekiq 客戶端預設連線池大小是 5,而且是懶惰策略,按需連線的,最大值是 10 x 5 = 50;

顯式 redis 連線: 程式**裡有乙個 $redis 全域性變數,初始化了乙個 redis 連線,10個工作程序,也就是 10 個連線;

sidekiq 服務端所需連線數: sidekiq server 端 concurrency 配置是 10,那麼按照官方文件,另有加上 2 個連線,也就是12個連線;

rails cache 所需連線數: 按照redis-storegem 原始碼,預設連線池大小應該是 5,10個 unicorn 工作程序,按需連線,最大值是 10 x 5 = 50。

在不考慮其他可能還用到 redis 連線的情況下,目前已知的最大 redis 連線數需求是 122,這個數遠小於 redis 理論最大連線數啊,而且當時顯示連線數到達上萬!而且這個專案已經很少訪問,壓力極其小,不大可能會達到理論所需連線數啊!

一定是有某種神秘力量在主導這一切!!!

以上理論最大連線數分析只是定性分析,只能大概說明有一些詭異的東西存在,而想真正確認問題根源,還得做定量分析,只有資料才能說明一切!

初步觀察:redis 資料庫伺服器端監控

事不宜遲,要採集資料,第一步就是加監控,所以當時就緊急寫了乙個定時採集 redis 客戶端數量(使用 redis 內建client list命令)的指令碼,結合 crontab 定時執行,將結果寫入檔案,作為後續分析的基礎。

通過監控指令碼,發現幾個有意思的現象:

從 redis 資料庫服務端採集的資料看,一直只有來自一台內網機器,也就是我前面說的 rails 程式所在的伺服器的連線,說明這個 redis 資料庫不存在共享給其他應用的可能性;

經過3天左右的監控,即從12.25到12.28,連續3天,redis 連線數一直穩步上公升,平均每日增加 70-80。在典型的系統資源洩露類(比如記憶體洩露)問題的場景中,這樣的線條看起來特別熟悉,所以,真的是連線數洩露了?

進一步分析:redis 資料庫伺服器端與客戶端連線數對比分析

在有了上一步的發現之後,我繼續用系統命令sudo netstat -apnt檢查6379埠連線數發現,客戶端機器也才只有 42 個左右的連線到 redis 伺服器端,結合最開始的理論連線數分析,這個數量是比較合理的。

但是!但是!反過來去服務端機器用同樣的命令檢查,在服務端視角,卻有多達300+個客戶端建立的連線,而且都是在 established 狀態!這個數量和上面另一種監控方式得到的數量一致!

到底是什麼情況?還能有這種操作?

至此,redis 連線數洩露是板上釘釘的事情了,可是又是為什麼呢?為此,我在網上搜尋了很多問答跟文章,後來總算找到了答案,果不其然,還是預設配置的問題。

redis 預設配置

redis 為了避免客戶端連線數過多,有乙個timeout配置,意思是如果連線的空閒時間超過了timeout的值,則關閉連線。預設配置是0,意思是沒有超時限制,永遠不關閉連線。生產上顯然不會配置0……

omg!趕緊開啟我們的 redis 的配置檔案驗證是否如此,果不其然,redis一直保持著預設配置!

至此,很好解釋為什麼連線數會洩露了,因為有很多空閒或者實際上客戶端已經斷開的連線,在伺服器端一側仍然保持著。那什麼情況會導致這樣的情況發生呢?

我猜測:

網路通訊差: 按照 tcp 協議,客戶端斷開連線時,向伺服器端傳送 fin 訊號,但是服務端未接收到,客戶端超時後放棄等待,直接斷開,服務端由於通訊故障,保持了 established 狀態,不過由於兩端機器在同個內網,網路質量沒有理由不行;

客戶端異常: 客戶端連線之後,由於**執行過程中產生異常,導致未正常釋放或者關閉連線,sidekiq 的worker很可能就有這類問題。這個的可能性非常大,畢竟我日常寫 bug (/ω╲)。

找到問題根源之後,修復起來就簡直太簡單了。事實上,開發領域就是如此,絕大部分時間都花在了找 bug 上,而改掉bug,可能只需要一分鐘不到。

首先,修改了下 redis 資料庫配置:

成功重啟 redis 之後,重新執行前面的監控指令碼,以便觀察修復後情況,初步可以確認這下伺服器端和客戶端的連線數一致了:

再又經過幾天的指令碼自動採集資料後分析,系統又恢復平穩執行了,連線數一直穩定在理論最大連線數之下。

這個問題的根源其實很小,但是排查過程還是花了挺多時間,主要是需要等待採集到足夠的資料後用於分析。其他心得體會:

保護「案發現場」很重要,要想挖掘問題根源,必須保持環境可重現,這次出現問題的時候雖然第一時間重啟了 redis 使服務恢復,但是由於沒有修改任何配置,所以使得後來的監控能夠發現問題根源;

使用開源軟體,必須對預設配置保持警惕,相信應該有人以前聽說過 redis 預設監聽0.0.0.0**請求的安全漏洞;

這個專案由於開始較早,當時並沒有考慮使用 redis 雲資料庫,自建資料庫有風險,需要慎重對待,盡可能的情況下,專業的事情,交給專業的人去做。

redis配置優化 記一次線上redis問題排查

在通過redis快取進行了一系列的介面效能優化後,大部分介面返回在1ms 200ms間,這都是redis的功勞,但隨著介面redis快取越來越多,新的問題產生了,從redis取資料竟然用了5s 通過觀察日誌,並不是每次取資料都是5s,大部分情況從redis取資料還是很快的不會超過5ms.1 在檢視 ...

記一次資料庫的實戰

話不多說 直接開始 開始我們的敲 的工程吧 首先匯入標頭檔案 import tkinter import tkinter.messagebox import pandas as pd import numpy as np import matplotlib.pyplot as plt from sk...

記一次MySQL資料庫crash事件

mysql8.0資料庫最近一次不知道怎麼回事,突然啟動不了,如下提示 mysql daemon failed to start 日誌如下 網上也找了很多資料,但都處理不了 因為本人安裝資料庫習慣將安裝好的資料庫移到移到其他目錄,所以做了乙個操作,用原來的覆蓋現有的檔案 左邊是原始資料庫檔案,右邊是移...