天天看點

Redis開發運維實踐高可用和叢集架構與實踐(五)11.1.5 其他問題11.1.5.1 隻讀性11.1.5.1 事件通知11.1.5.2 虛拟IP切換11.1.5.3 持久化動态修改11.1.5.4 Sentinel最大連接配接數1. 問題描述2. 問題初步排查過程3. 問題查證過程4. 問題解決過程5. 問題解決結果6. 問題後續7. 問題再後續

主從複制架構下,預設slave是隻讀的,如果寫入則會報錯:

注意這個行為是可以修改的,雖然這樣的修改沒有意義:

在sentinel中,如果出現warning以上級别的事件發生, 是可以通過如下配置進行腳本調用的(對于該腳本redis啟動使用者需要有執行權限):

比如說,我們希望在發生這些事件的時候進行郵件通知,那麼,notify.py就是一個觸發郵件調用的東東,傳入第一個參數為事件類型,第二個參數為事件資訊:

有兩個注意事項: 1) 這個時候如果叢集發生了切換會産生很多事件,此腳本是在每一個事件發生時調用一次,那麼你将短時間收到很多封郵件,加上很多的郵件網關是不允許在一個短時間内發送太多的郵件的,是以這個僅僅是一個示例,并不具備實際上的作用。 2) 一般我們會采用多個sentinel,隻需在一個sentinel上配置即可,否則将同一個消息會被多個sentinel多次處理。

附sendmail子產品代碼:

最佳實踐:采用elk(elastic+logstash+kibana)進行日志收集告警(elastalert用起來不錯),不啟用這個事件通知功能。如果你的環境中沒有elk,或者啟動一個tcp server程序,notify腳本将事件通過tcp方式吐給這個server,該server收集一批事件後再做諸如發郵件的處理。

在sentinel進行切換時還會自動調用一個腳本(如果設定的話),做一些自動化操作,比如如果我們需要一個虛拟ip永遠飄在master上(這個vip可不是被應用用來連接配接redis 的,用過的人都知道連接配接redis sentinel并不依賴于vip的),那麼可以在sentinel配置檔案中配置:

在發生主從切換,master發生變化時,該腳本會被sentinel進行調用,調用的參數如其配置檔案所描述的:

是以,我們可以在failover.sh中進行判斷,如果該腳本所運作的主機ip等于新的master ip,那麼将vip加上,如果不等于,則該機器為slave,就去掉vip。通過這種方式進行vip的切換:

其實相對于vip的切換,動态修改持久化則是比較常見的一個需求,一般在一主多從多sentinel的ha環境中,為了性能常常在master上關閉持久化,而在slave上開啟持久化,但是如果發生切換就必須有人工幹預才能實作這個功能。可以利用client-reconfig-script自動化該程序,無需人工守護,我們就以rdb的動态控制為例: sentinel配置檔案如下:

rdbctl.sh源代碼:

原理和vip切換一節基本一緻,不再贅述。

某準生産系統,測試運作一段時間後程式和指令行工具連接配接sentinel均報錯,報錯資訊為:

此時應用建立redis新連接配接由于sentinel已經無法響應而無法找到master的ip與端口,是以無法連接配接redis,并且此時如果發生redis當機亦無法進行生産切換。

首先,通過netstat對sentinel所監聽端口26379進行連接配接數統計,此時連接配接則報錯。如下圖:

Redis開發運維實踐高可用和叢集架構與實踐(五)11.1.5 其他問題11.1.5.1 隻讀性11.1.5.1 事件通知11.1.5.2 虛拟IP切換11.1.5.3 持久化動态修改11.1.5.4 Sentinel最大連接配接數1. 問題描述2. 問題初步排查過程3. 問題查證過程4. 問題解決過程5. 問題解決結果6. 問題後續7. 問題再後續

通過sentinel伺服器端統計發現,redis sentinel 的連接配接中大部分都是來自于兩台非同網段(中間有防火牆)的應用伺服器連接配接(均為established狀态),并且來自其的連接配接也大約半個小時後穩步增加一次,而同網段的應用伺服器連接配接sentinel的連接配接數基本保持一緻。排除了應用的特殊性(采用的jedis版本和封裝的工具類都是一樣的)後,初步判斷此問題與網絡有關,更詳細的說是連接配接數增加與防火牆切斷連接配接後的重連有關。

此問題分為兩個子問題: 1) 防火牆将tcp連接配接設定為無效時sentinel伺服器為何沒有斷開連接配接,保持established狀态? 2) 為何連接配接數還會不斷增加?

對于問題1) ,tcp在三次握手建立連接配接時os會啟動一個timer來進行倒計時,經過一個設定的時間(這個時間建立socket的程式可以設定,如果沒有設定則采用os的參數tcp_keepalive_time,這個參數預設為7200s,即2小時)後這個連接配接還是沒有資料傳輸,它就會以一定間隔(程式可以設定,如果沒有設定則采用os的參數tcp_keepalive_intvl,預設為75s)發出n(程式可以設定,如果沒有設定則采用os的參數tcp_keepalive_probes,預設為9次)次keep alive包。tcp連接配接就是通過上述的過程,在沒有流量時是通過發送tcp keep-alive資料包,然後對方回應tcp keep-alive ack來确定信道是否還在真實連接配接。通過檢視sentinel源代碼,其預設是不開啟keepalive的(而jedis預設是開啟的),并且預設對于不活動的連接配接也不會主動關閉的(timeout預設為0)。

對于防火牆,通過翻閱防火牆技術資料(詳見下列描述,摘自:《junos enterprise switching: a practical guide to junos switches and certification》),我司采用的juniper防火牆對于沒有流量的tcp連接配接預設是30分鐘,30分鐘内沒有流量就會斷掉鍊路,而不會發送tcp reset,同時在防火牆政策上并沒有開長連接配接,使用的即為此預設設定。

Redis開發運維實踐高可用和叢集架構與實踐(五)11.1.5 其他問題11.1.5.1 隻讀性11.1.5.1 事件通知11.1.5.2 虛拟IP切換11.1.5.3 持久化動态修改11.1.5.4 Sentinel最大連接配接數1. 問題描述2. 問題初步排查過程3. 問題查證過程4. 問題解決過程5. 問題解決結果6. 問題後續7. 問題再後續

是以在防火牆每半個小時将連接配接置為無效時,sentinel同時又禁止了keepalive(因為預設設定keepalive為0,即disable發送keepalive包)。應用伺服器的jedis雖然開啟了keepalive,但是它發送的keepalive包由于防火牆已經将此鍊路标記為無效,而無法發送到sentinel端,而且jedis由于采用了os預設參數,是以需要等待tcp_keepalive_time(2小時)後才啟動發送keep alive包進行探活的,在tcp_keepalive_time+tcp_keepalive_intvl*tcp_keepalive_probes=7895s=131.58分鐘後,jedis端才會認定這個連接配接斷掉而清理掉這個連接配接。簡單的說就是jedis會在很長一段時間後才會發keepalive包,并且這個包也是發不到sentinel上的,而sentinel本身也不會發送keepalive包,是以從sentinel這端看連接配接一直存在,而從jedis那端看7895s之後就會清理一次連接配接。這也解釋了為什麼防火牆将tcp連接配接斷開後,sentinel端的連接配接并沒有釋放。

對于問題2) ,翻閱jedis源代碼,jedis通過連接配接sentinel并pubsub來監聽叢集事件,以确定是否發生了切換,并且拿到新的master 位址和端口。如果斷開則會5秒後嘗試重連(jedissentinelpool.java)。

Redis開發運維實踐高可用和叢集架構與實踐(五)11.1.5 其他問題11.1.5.1 隻讀性11.1.5.1 事件通知11.1.5.2 虛拟IP切換11.1.5.3 持久化動态修改11.1.5.4 Sentinel最大連接配接數1. 問題描述2. 問題初步排查過程3. 問題查證過程4. 問題解決過程5. 問題解決結果6. 問題後續7. 問題再後續

是以,這是導緻連接配接數不斷上升的原因。 綜上,防火牆相對頻繁的斷開和伺服器不斷重連導緻在一個相對較短的時間内連接配接驟增,造成到達sentinel最大連接配接數,sentinel 的最大連接配接數在redis.h中定義,為10000:

Redis開發運維實踐高可用和叢集架構與實踐(五)11.1.5 其他問題11.1.5.1 隻讀性11.1.5.1 事件通知11.1.5.2 虛拟IP切換11.1.5.3 持久化動态修改11.1.5.4 Sentinel最大連接配接數1. 問題描述2. 問題初步排查過程3. 問題查證過程4. 問題解決過程5. 問題解決結果6. 問題後續7. 問題再後續

此系統由于通路關系與網段規劃間的安全問題,必須跨越防火牆,是以試圖從配置角度解決此問題。

首先,聯系網絡相關同僚,進行網絡變更,開啟從應用伺服器到sentinel的鍊路相對的長連接配接,即無流量逾時而斷開的時間設定為8小時。以此手段降低斷開頻率,以便緩解短時間内不斷重試連接配接造成的sentinel連接配接增長。

然後,通過閱讀redis源代碼(net.c),發現,sentinel也采用了redis 所有參數設定(通過config.c的函數void loadserverconfigfromstring(char *config))。是以,通過設定redis 的下列兩個參數可以解決這個問題,第一個參數是tcp keepalive參數,此參數預設為0,也就是不發送keepalive。也就是改變os預設的tcp_keepalive_time參數(在unix c的socket程式設計中tcp_keepidle參數對應os 的tcp_keepalive_time參數)。

Redis開發運維實踐高可用和叢集架構與實踐(五)11.1.5 其他問題11.1.5.1 隻讀性11.1.5.1 事件通知11.1.5.2 虛拟IP切換11.1.5.3 持久化動态修改11.1.5.4 Sentinel最大連接配接數1. 問題描述2. 問題初步排查過程3. 問題查證過程4. 問題解決過程5. 問題解決結果6. 問題後續7. 問題再後續

該參數的官方解釋為:

我們設定為tcp-keepalive 60,加快回收連接配接速度,從網絡斷開到連接配接清理時間縮短為60+75*9=12.25分鐘。

同時,通過設定maxclients為65536,增大sentinel最大連接配接數,使得在上述12.25分鐘即使有某種異常導緻sentinel連接配接數增加也不至于到達最大限制。此參數的官方解釋為:

對于redis 的timeout參數,由于啟用這個參數有程式微小開銷(會調用redis.c中的int clientscronhandletimeout(redisclient *c, mstime_t now_ms)),決定保持預設為0,而通過上述參數使用os進行連接配接斷開。

通過開發、網絡和資料庫團隊的協同努力,配置上述參數和修改防火牆政策後,手動增加sentinel程序,超過原預設最大連接配接數10000後sentinel可以正常通路操作,并且通過tcpdump進行抓包,在指定時間内(1分鐘),就有keepalive包對每個sentinel tcp連接配接進行探活,經過觀察sentinel連接配接穩定,再未出現短時間内暴漲的情況。

在redis中預設不開啟keepalive就是為了盡可能減小網絡負載,榨幹網絡性能,盡可能達到redis的。在後續的程式運作中,如果發現網絡是瓶頸時(在相當長的一段時間内不會),可以加大sentinel的keepalive參數,減小keepalive資料包的傳輸,這個修改是不影響redis對外服務的。

附錄:如何用tcpdump進行keep alive抓包

我們後來在這個應用上發現一旦網絡有抖動,sentinel的連接配接增加就回大幅度增加,後來通過jmap檢視sentinelpool的執行個體竟然多達200多個,也就是說這個就是程式的問題,在sentinelpool上不應該多次執行個體化,而是采用已有連接配接進行重連。