W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
某準生產(chǎn)系統(tǒng),測試運行一段時間后程序和命令行工具連接sentinel均報錯,報錯信息為:
jedis.exceptions.JedisDataException: ERR max number of clients reached
此時應(yīng)用創(chuàng)建redis新連接由于sentinel已經(jīng)無法響應(yīng)而無法找到master的IP與端口,因此無法連接redis,并且此時如果發(fā)生redis宕機亦無法進行生產(chǎn)切換。
首先,通過netstat對sentinel所監(jiān)聽端口26379進行連接數(shù)統(tǒng)計,此時連接則報錯。如下圖:
通過sentinel服務(wù)器端統(tǒng)計發(fā)現(xiàn),redis sentinel 的連接中大部分都是來自于兩臺非同網(wǎng)段(中間有防火墻)的應(yīng)用服務(wù)器連接(均為Established狀態(tài)),并且來自其的連接也大約半個小時后穩(wěn)步增加一次,而同網(wǎng)段的應(yīng)用服務(wù)器連接sentinel的連接數(shù)基本保持一致。排除了應(yīng)用的特殊性(采用的jedis版本和封裝的工具類都是一樣的)后,初步判斷此問題與網(wǎng)絡(luò)有關(guān),更詳細的說是連接數(shù)增加與防火墻切斷連接后的重連有關(guān)。
此問題分為兩個子問題: 1) 防火墻將TCP連接設(shè)置為無效時sentinel服務(wù)器為何沒有斷開連接,保持Established狀態(tài)? 2) 為何連接數(shù)還會不斷增加?
對于問題1) ,TCP在三次握手建立連接時OS會啟動一個Timer來進行倒計時,經(jīng)過一個設(shè)定的時間(這個時間建立socket的程序可以設(shè)置,如果沒有設(shè)置則采用OS的參數(shù)tcp_keepalive_time,這個參數(shù)默認為7200s,即2小時)后這個連接還是沒有數(shù)據(jù)傳輸,它就會以一定間隔(程序可以設(shè)定,如果沒有設(shè)置則采用OS的參數(shù)tcp_keepalive_intvl,默認為75s)發(fā)出N(程序可以設(shè)定,如果沒有設(shè)置則采用OS的參數(shù)tcp_keepalive_probes,默認為9次)次Keep Alive包。TCP連接就是通過上述的過程,在沒有流量時是通過發(fā)送TCP Keep-Alive數(shù)據(jù)包,然后對方回應(yīng)TCP Keep-Alive ACK來確定信道是否還在真實連接。通過查看Sentinel源代碼,其默認是不開啟Keepalive的(而jedis默認是開啟的),并且默認對于不活動的連接也不會主動關(guān)閉的(timeout默認為0)。
對于防火墻,通過翻閱防火墻技術(shù)資料(詳見下列描述,摘自:《Junos Enterprise Switching: A Practical Guide to Junos Switches and Certification》),我司采用的Juniper防火墻對于沒有流量的TCP連接默認是30分鐘,30分鐘內(nèi)沒有流量就會斷掉鏈路,而不會發(fā)送TCP Reset,同時在防火墻策略上并沒有開長連接,使用的即為此默認設(shè)置。
因此在防火墻每半個小時將連接置為無效時,sentinel同時又禁止了Keepalive(因為默認設(shè)置Keepalive為0,即disable發(fā)送Keepalive包)。應(yīng)用服務(wù)器的jedis雖然開啟了keepalive,但是它發(fā)送的keepalive包由于防火墻已經(jīng)將此鏈路標記為無效,而無法發(fā)送到sentinel端,而且jedis由于采用了OS默認參數(shù),因此需要等待tcp_keepalive_time(2小時)后才啟動發(fā)送Keep Alive包進行探活的,在tcp_keepalive_time+tcp_keepalive_intvl*tcp_keepalive_probes=7895s=131.58分鐘后,jedis端才會認定這個連接斷掉而清理掉這個連接。簡單的說就是jedis會在很長一段時間后才會發(fā)keepalive包,并且這個包也是發(fā)不到sentinel上的,而sentinel本身也不會發(fā)送keepalive包,所以從sentinel這端看連接一直存在,而從jedis那端看7895s之后就會清理一次連接。這也解釋了為什么防火墻將TCP連接斷開后,sentinel端的連接并沒有釋放。
對于問題2) ,翻閱jedis源代碼,jedis通過連接sentinel并pubsub來監(jiān)聽集群事件,以確定是否發(fā)生了切換,并且拿到新的master 地址和端口。如果斷開則會5秒后嘗試重連(JedisSentinelPool.java)。
因此,這是導(dǎo)致連接數(shù)不斷上升的原因。 綜上,防火墻相對頻繁的斷開和服務(wù)器不斷重連導(dǎo)致在一個相對較短的時間內(nèi)連接驟增,造成到達sentinel最大連接數(shù),sentinel 的最大連接數(shù)在redis.h中定義,為10000:
此系統(tǒng)由于訪問關(guān)系與網(wǎng)段規(guī)劃間的安全問題,必須跨越防火墻,因此試圖從配置角度解決此問題。
首先,聯(lián)系網(wǎng)絡(luò)相關(guān)同事,進行網(wǎng)絡(luò)變更,開啟從應(yīng)用服務(wù)器到sentinel的鏈路相對的長連接,即無流量超時而斷開的時間設(shè)置為8小時。以此手段降低斷開頻率,以便緩解短時間內(nèi)不斷重試連接造成的sentinel連接增長。
然后,通過閱讀redis源代碼(net.c),發(fā)現(xiàn),sentinel也采用了redis 所有參數(shù)設(shè)置(通過config.c的函數(shù)void loadServerConfigFromString(char *config))。因此,通過設(shè)置redis 的下列兩個參數(shù)可以解決這個問題,第一個參數(shù)是TCP Keepalive參數(shù),此參數(shù)默認為0,也就是不發(fā)送keepalive。也就是改變OS默認的tcp_keepalive_time參數(shù)(在Unix C的socket編程中TCP_KEEPIDLE參數(shù)對應(yīng)OS 的tcp_keepalive_time參數(shù))。
該參數(shù)的官方解釋為:
# TCP keepalive.
#
# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
# of communication. This is useful for two reasons:
#
# 1) Detect dead peers.
# 2) Take the connection alive from the point of view of network
# equipment in the middle.
#
# On Linux, the specified value (in seconds) is the period used to send ACKs.
# Note that to close the connection the double of the time is needed.
# On other kernels the period depends on the kernel configuration.
#
# A reasonable value for this option is 60 seconds.
我們設(shè)置為tcp-keepalive 60,加快回收連接速度,從網(wǎng)絡(luò)斷開到連接清理時間縮短為60+75*9=12.25分鐘。
同時,通過設(shè)置maxclients為65536,增大sentinel最大連接數(shù),使得在上述12.25分鐘即使有某種異常導(dǎo)致sentinel連接數(shù)增加也不至于到達最大限制。此參數(shù)的官方解釋為:
################################### LIMITS ####################################
# Set the max number of connected clients at the same time. By default
# this limit is set to 10000 clients, however if the Redis server is not
# able to configure the process file limit to allow for the specified limit
# the max number of allowed clients is set to the current file limit
# minus 32 (as Redis reserves a few file descriptors for internal uses).
#
# Once the limit is reached Redis will close all the new connections sending
# an error 'max number of clients reached'.
#
maxclients 10000
對于redis 的timeout參數(shù),由于啟用這個參數(shù)有程序微小開銷(會調(diào)用redis.c中的int clientsCronHandleTimeout(redisClient *c, mstime_t now_ms)),決定保持默認為0,而通過上述參數(shù)使用OS進行連接斷開。
通過開發(fā)、網(wǎng)絡(luò)和數(shù)據(jù)庫團隊的協(xié)同努力,配置上述參數(shù)和修改防火墻策略后,手動增加sentinel進程,超過原默認最大連接數(shù)10000后sentinel可以正常訪問操作,并且通過tcpdump進行抓包,在指定時間內(nèi)(1分鐘),就有KeepAlive包對每個sentinel TCP連接進行探活,經(jīng)過觀察sentinel連接穩(wěn)定,再未出現(xiàn)短時間內(nèi)暴漲的情況。
在redis中默認不開啟keepalive就是為了盡可能減小網(wǎng)絡(luò)負載,榨干網(wǎng)絡(luò)性能,盡可能達到redis的。在后續(xù)的程序運行中,如果發(fā)現(xiàn)網(wǎng)絡(luò)是瓶頸時(在相當長的一段時間內(nèi)不會),可以加大sentinel的keepalive參數(shù),減小keepalive數(shù)據(jù)包的傳輸,這個修改是不影響redis對外服務(wù)的。
參考文檔: http://www.tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/
附錄:如何用TCPDUMP進行keep alive抓包
tcpdump -pni bond0 -v "src port 26379 and ( tcp[tcpflags] & tcp-ack != 0 and ( (ip[2:2] - ((ip[0]&0xf)<<2) ) - ((tcp[12]&0xf0)>>2) ) == 0 ) "
我們后來在這個應(yīng)用上發(fā)現(xiàn)一旦網(wǎng)絡(luò)有抖動,sentinel的連接增加就回大幅度增加,后來通過jmap查看sentinelpool的實例竟然多達200多個,也就是說這個就是程序的問題,在sentinelpool上不應(yīng)該多次實例化,而是采用已有連接進行重連。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: