閱讀455 返回首頁    go 技術社區[雲棲]


Redis開發運維實踐高可用和集群架構與實踐(五)

11.1.5 其他問題

11.1.5.1 隻讀性

主從複製架構下,默認Slave是隻讀的,如果寫入則會報錯:

127.0.0.1:6379> set foo bar
(error) READONLY You can't write against a read only slave.

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

127.0.0.1:6379> CONFIG SET slave-read-only no
OK
127.0.0.1:6379> set foo bar
OK

11.1.5.1 事件通知

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


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


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

附sendmail模塊代碼:


最佳實踐:采用ELK(Elastic+Logstash+Kibana)進行日誌收集告警(ElastAlert用起來不錯),不啟用這個事件通知功能。如果你的環境中沒有ELK,或者啟動一個Tcp Server進程,notify腳本將事件通過tcp方式吐給這個server,該Server收集一批事件後再做諸如發郵件的處理。

11.1.5.2 虛擬IP切換

在sentinel進行切換時還會自動調用一個腳本(如果設置的話),做一些自動化操作,比如如果我們需要一個虛擬IP永遠飄在Master上(這個VIP可不是被應用用來連接redis 的,用過的人都知道連接redis sentinel並不依賴於VIP的),那麼可以在sentinel配置文件中配置:


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


因此,我們可以在failover.sh中進行判斷,如果該腳本所運行的主機IP等於新的Master IP,那麼將VIP加上,如果不等於,則該機器為Slave,就去掉VIP。通過這種方式進行VIP的切換:


最早這樣的用法是一個日本人寫的blog,請參見:https://blog.youyo.info/blog/2014/05/24/redis-cluster/

11.1.5.3 持久化動態修改

其實相對於VIP的切換,動態修改持久化則是比較常見的一個需求,一般在一主多從多Sentinel的HA環境中,為了性能常常在Master上關閉持久化,而在Slave上開啟持久化,但是如果發生切換就必須有人工幹預才能實現這個功能。可以利用client-reconfig-script自動化該進程,無需人工守護,我們就以RDB的動態控製為例: Sentinel配置文件如下:


rdbctl.sh源代碼:


原理和VIP切換一節基本一致,不再贅述。

11.1.5.4 Sentinel最大連接數

1. 問題描述

某準生產係統,測試運行一段時間後程序和命令行工具連接sentinel均報錯,報錯信息為:


此時應用創建redis新連接由於sentinel已經無法響應而無法找到master的IP與端口,因此無法連接redis,並且此時如果發生redis宕機亦無法進行生產切換。

2. 問題初步排查過程

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

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

3. 問題查證過程

此問題分為兩個子問題: 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,同時在防火牆策略上並沒有開長連接,使用的即為此默認設置。

因此在防火牆每半個小時將連接置為無效時,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)。


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

4. 問題解決過程

此係統由於訪問關係與網段規劃間的安全問題,必須跨越防火牆,因此試圖從配置角度解決此問題。

首先,聯係網絡相關同事,進行網絡變更,開啟從應用服務器到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參數)。

該參數的官方解釋為:


我們設置為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進行連接斷開。

5. 問題解決結果

通過開發、網絡和數據庫團隊的協同努力,配置上述參數和修改防火牆策略後,手動增加sentinel進程,超過原默認最大連接數10000後sentinel可以正常訪問操作,並且通過tcpdump進行抓包,在指定時間內(1分鍾),就有KeepAlive包對每個sentinel TCP連接進行探活,經過觀察sentinel連接穩定,再未出現短時間內暴漲的情況。

6. 問題後續

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

參考文檔: https://www.tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/

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


7. 問題再後續

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

最後更新:2017-05-08 12:06:02

  上一篇:go 思維導圖之分布式ID生成生成
  下一篇:go Redis開發運維實踐高可用和集群架構與實踐(四)