Redis Sentinel最大連接數(shù)

2018-08-03 11:53 更新

1. 問(wèn)題描述

某準(zhǔn)生產(chǎn)系統(tǒng),測(cè)試運(yùn)行一段時(shí)間后程序和命令行工具連接sentinel均報(bào)錯(cuò),報(bào)錯(cuò)信息為:

jedis.exceptions.JedisDataException: ERR max number of clients reached

此時(shí)應(yīng)用創(chuàng)建redis新連接由于sentinel已經(jīng)無(wú)法響應(yīng)而無(wú)法找到master的IP與端口,因此無(wú)法連接redis,并且此時(shí)如果發(fā)生redis宕機(jī)亦無(wú)法進(jìn)行生產(chǎn)切換。

2. 問(wèn)題初步排查過(guò)程

首先,通過(guò)netstat對(duì)sentinel所監(jiān)聽(tīng)端口26379進(jìn)行連接數(shù)統(tǒng)計(jì),此時(shí)連接則報(bào)錯(cuò)。如下圖:

通過(guò)sentinel服務(wù)器端統(tǒng)計(jì)發(fā)現(xiàn),redis sentinel 的連接中大部分都是來(lái)自于兩臺(tái)非同網(wǎng)段(中間有防火墻)的應(yīng)用服務(wù)器連接(均為Established狀態(tài)),并且來(lái)自其的連接也大約半個(gè)小時(shí)后穩(wěn)步增加一次,而同網(wǎng)段的應(yīng)用服務(wù)器連接sentinel的連接數(shù)基本保持一致。排除了應(yīng)用的特殊性(采用的jedis版本和封裝的工具類都是一樣的)后,初步判斷此問(wèn)題與網(wǎng)絡(luò)有關(guān),更詳細(xì)的說(shuō)是連接數(shù)增加與防火墻切斷連接后的重連有關(guān)。

3. 問(wèn)題查證過(guò)程

此問(wèn)題分為兩個(gè)子問(wèn)題: 1) 防火墻將TCP連接設(shè)置為無(wú)效時(shí)sentinel服務(wù)器為何沒(méi)有斷開(kāi)連接,保持Established狀態(tài)? 2) 為何連接數(shù)還會(huì)不斷增加?

對(duì)于問(wèn)題1) ,TCP在三次握手建立連接時(shí)OS會(huì)啟動(dòng)一個(gè)Timer來(lái)進(jìn)行倒計(jì)時(shí),經(jīng)過(guò)一個(gè)設(shè)定的時(shí)間(這個(gè)時(shí)間建立socket的程序可以設(shè)置,如果沒(méi)有設(shè)置則采用OS的參數(shù)tcp_keepalive_time,這個(gè)參數(shù)默認(rèn)為7200s,即2小時(shí))后這個(gè)連接還是沒(méi)有數(shù)據(jù)傳輸,它就會(huì)以一定間隔(程序可以設(shè)定,如果沒(méi)有設(shè)置則采用OS的參數(shù)tcp_keepalive_intvl,默認(rèn)為75s)發(fā)出N(程序可以設(shè)定,如果沒(méi)有設(shè)置則采用OS的參數(shù)tcp_keepalive_probes,默認(rèn)為9次)次Keep Alive包。TCP連接就是通過(guò)上述的過(guò)程,在沒(méi)有流量時(shí)是通過(guò)發(fā)送TCP Keep-Alive數(shù)據(jù)包,然后對(duì)方回應(yīng)TCP Keep-Alive ACK來(lái)確定信道是否還在真實(shí)連接。通過(guò)查看Sentinel源代碼,其默認(rèn)是不開(kāi)啟Keepalive的(而jedis默認(rèn)是開(kāi)啟的),并且默認(rèn)對(duì)于不活動(dòng)的連接也不會(huì)主動(dòng)關(guān)閉的(timeout默認(rèn)為0)。

對(duì)于防火墻,通過(guò)翻閱防火墻技術(shù)資料(詳見(jiàn)下列描述,摘自:《Junos Enterprise Switching: A Practical Guide to Junos Switches and Certification》),我司采用的Juniper防火墻對(duì)于沒(méi)有流量的TCP連接默認(rèn)是30分鐘,30分鐘內(nèi)沒(méi)有流量就會(huì)斷掉鏈路,而不會(huì)發(fā)送TCP Reset,同時(shí)在防火墻策略上并沒(méi)有開(kāi)長(zhǎng)連接,使用的即為此默認(rèn)設(shè)置。

因此在防火墻每半個(gè)小時(shí)將連接置為無(wú)效時(shí),sentinel同時(shí)又禁止了Keepalive(因?yàn)槟J(rèn)設(shè)置Keepalive為0,即disable發(fā)送Keepalive包)。應(yīng)用服務(wù)器的jedis雖然開(kāi)啟了keepalive,但是它發(fā)送的keepalive包由于防火墻已經(jīng)將此鏈路標(biāo)記為無(wú)效,而無(wú)法發(fā)送到sentinel端,而且jedis由于采用了OS默認(rèn)參數(shù),因此需要等待tcp_keepalive_time(2小時(shí))后才啟動(dòng)發(fā)送Keep Alive包進(jìn)行探活的,在tcp_keepalive_time+tcp_keepalive_intvl*tcp_keepalive_probes=7895s=131.58分鐘后,jedis端才會(huì)認(rèn)定這個(gè)連接斷掉而清理掉這個(gè)連接。簡(jiǎn)單的說(shuō)就是jedis會(huì)在很長(zhǎng)一段時(shí)間后才會(huì)發(fā)keepalive包,并且這個(gè)包也是發(fā)不到sentinel上的,而sentinel本身也不會(huì)發(fā)送keepalive包,所以從sentinel這端看連接一直存在,而從jedis那端看7895s之后就會(huì)清理一次連接。這也解釋了為什么防火墻將TCP連接斷開(kāi)后,sentinel端的連接并沒(méi)有釋放。

對(duì)于問(wèn)題2) ,翻閱jedis源代碼,jedis通過(guò)連接sentinel并pubsub來(lái)監(jiān)聽(tīng)集群事件,以確定是否發(fā)生了切換,并且拿到新的master 地址和端口。如果斷開(kāi)則會(huì)5秒后嘗試重連(JedisSentinelPool.java)。

因此,這是導(dǎo)致連接數(shù)不斷上升的原因。 綜上,防火墻相對(duì)頻繁的斷開(kāi)和服務(wù)器不斷重連導(dǎo)致在一個(gè)相對(duì)較短的時(shí)間內(nèi)連接驟增,造成到達(dá)sentinel最大連接數(shù),sentinel 的最大連接數(shù)在redis.h中定義,為10000:

4. 問(wèn)題解決過(guò)程

此系統(tǒng)由于訪問(wèn)關(guān)系與網(wǎng)段規(guī)劃間的安全問(wèn)題,必須跨越防火墻,因此試圖從配置角度解決此問(wèn)題。

首先,聯(lián)系網(wǎng)絡(luò)相關(guān)同事,進(jìn)行網(wǎng)絡(luò)變更,開(kāi)啟從應(yīng)用服務(wù)器到sentinel的鏈路相對(duì)的長(zhǎng)連接,即無(wú)流量超時(shí)而斷開(kāi)的時(shí)間設(shè)置為8小時(shí)。以此手段降低斷開(kāi)頻率,以便緩解短時(shí)間內(nèi)不斷重試連接造成的sentinel連接增長(zhǎng)。

然后,通過(guò)閱讀redis源代碼(net.c),發(fā)現(xiàn),sentinel也采用了redis 所有參數(shù)設(shè)置(通過(guò)config.c的函數(shù)void loadServerConfigFromString(char *config))。因此,通過(guò)設(shè)置redis 的下列兩個(gè)參數(shù)可以解決這個(gè)問(wèn)題,第一個(gè)參數(shù)是TCP Keepalive參數(shù),此參數(shù)默認(rèn)為0,也就是不發(fā)送keepalive。也就是改變OS默認(rèn)的tcp_keepalive_time參數(shù)(在Unix C的socket編程中TCP_KEEPIDLE參數(shù)對(duì)應(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.

我們?cè)O(shè)置為tcp-keepalive 60,加快回收連接速度,從網(wǎng)絡(luò)斷開(kāi)到連接清理時(shí)間縮短為60+75*9=12.25分鐘。

同時(shí),通過(guò)設(shè)置maxclients為65536,增大sentinel最大連接數(shù),使得在上述12.25分鐘即使有某種異常導(dǎo)致sentinel連接數(shù)增加也不至于到達(dá)最大限制。此參數(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

對(duì)于redis 的timeout參數(shù),由于啟用這個(gè)參數(shù)有程序微小開(kāi)銷(會(huì)調(diào)用redis.c中的int clientsCronHandleTimeout(redisClient *c, mstime_t now_ms)),決定保持默認(rèn)為0,而通過(guò)上述參數(shù)使用OS進(jìn)行連接斷開(kāi)。

5. 問(wèn)題解決結(jié)果

通過(guò)開(kāi)發(fā)、網(wǎng)絡(luò)和數(shù)據(jù)庫(kù)團(tuán)隊(duì)的協(xié)同努力,配置上述參數(shù)和修改防火墻策略后,手動(dòng)增加sentinel進(jìn)程,超過(guò)原默認(rèn)最大連接數(shù)10000后sentinel可以正常訪問(wèn)操作,并且通過(guò)tcpdump進(jìn)行抓包,在指定時(shí)間內(nèi)(1分鐘),就有KeepAlive包對(duì)每個(gè)sentinel TCP連接進(jìn)行探活,經(jīng)過(guò)觀察sentinel連接穩(wěn)定,再未出現(xiàn)短時(shí)間內(nèi)暴漲的情況。

6. 問(wèn)題后續(xù)

在redis中默認(rèn)不開(kāi)啟keepalive就是為了盡可能減小網(wǎng)絡(luò)負(fù)載,榨干網(wǎng)絡(luò)性能,盡可能達(dá)到redis的。在后續(xù)的程序運(yùn)行中,如果發(fā)現(xiàn)網(wǎng)絡(luò)是瓶頸時(shí)(在相當(dāng)長(zhǎng)的一段時(shí)間內(nèi)不會(huì)),可以加大sentinel的keepalive參數(shù),減小keepalive數(shù)據(jù)包的傳輸,這個(gè)修改是不影響redis對(duì)外服務(wù)的。

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

附錄:如何用TCPDUMP進(jìn)行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 ) "

7. 問(wèn)題再后續(xù)

我們后來(lái)在這個(gè)應(yīng)用上發(fā)現(xiàn)一旦網(wǎng)絡(luò)有抖動(dòng),sentinel的連接增加就回大幅度增加,后來(lái)通過(guò)jmap查看sentinelpool的實(shí)例竟然多達(dá)200多個(gè),也就是說(shuō)這個(gè)就是程序的問(wèn)題,在sentinelpool上不應(yīng)該多次實(shí)例化,而是采用已有連接進(jìn)行重連。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)