tcp连接被抽象为一个socket,socket上添加了SO_KEEPALIVE后,该socket便可以启用keepalive。
keepalive的连接为长连接,这样client向server交互时不用每次都新建连接,用长连接进行持续的数据读取和写入即可。
keepalive的连接需要定期进行探测,当client不再活跃时,server端及时的释放该连接。
tcp keepalive的参数:
tcp_keepalive_time: 单位s,默认7200
client与server多久没有数据交互,就认为connection idle,然后开始发起探测。
tcp_keepalive_intvl: 单位s,默认75一次探测完毕后,等待多久进行下一次探测。 tcp_keepalive_probes:单位次数,默认9
最大探测次数,某个连接经过N次探测后仍然不活跃将被释放。
默认情况下:
2个小时(7200s)(tcp_keepalive_time)没有数据交互,就认为connection idle; 然后发起keep-alive消息,探测client是否存活; 每隔tcp_keepalive_intvl(75s)发起一次探测,探测tcp_keepalive_probes(9)次后,将彻底kill连接;
总结来说
1个tcp连接,要等:7200+75*9=2hour11min后,才被kill掉;
一般生产环境都会配置上面的3个参数,目录/proc/sys/net/ipv4/下:
//tcp_keepalive_time 参数
/proc/sys/net/ipv4 # cat tcp_keepalive_time
90
//tcp_keeplive_intv 参数
/proc/sys/net/ipv4# cat tcp_keepalive_intvl
15
//tcp_keepalive_probes参数
/proc/sys/net/ipv4# cat tcp_keepalive_probes
2
当程序中socket未配置keep-alive参数时,就使用系统上配置的参数。
Keepalive: TCP VS HTTP
Http的keepalive用于连接复用,在同一个连接上request-response。
Tcp的keepalive用于保活、心跳。
go-rpc的TCP Keepalive
go-rpc是golang自带的rpc框架,server端的代码:
func StartRpc() {
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
log.Fatalln("addr err:", err)
}
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
log.Fatalln("listen err:", err)
}
server := rpc.NewServer()
server.Register(new(Transfer))
for {
conn, err := listener.AcceptTCP()
if err != nil {
log.Println("accept err:", err)
continue
}
log.Println("accept tcp from:", conn.RemoteAddr())
go server.ServeCodec(jsonrpc.NewServerCodec(conn))
}
}
服务端接收1个connection,然后启动1个goroutine处理该连接上的request:
conn, err := listener.AcceptTCP()
go server.ServeCodec(jsonrpc.NewServerCodec(conn))
接收TCP连接时,先配置TCP为keepalive长连接,然后再配置keepalive参数:
func (ln *TCPListener) accept() (*TCPConn, error) {
fd, err := ln.fd.accept()
if err != nil {
return nil, err
}
tc := newTCPConn(fd)
if ln.lc.KeepAlive >= 0 {
setKeepAlive(fd, true) //启用tcp keepalive
ka := ln.lc.KeepAlive
if ln.lc.KeepAlive == 0 {
ka = defaultTCPKeepAlive //默认keepalive时间=15s
}
setKeepAlivePeriod(fd, ka)
}
return tc, nil
}
启用TCP keepalive:
//配置Keepalive标志
func setKeepAlive(fd *netFD, keepalive bool) error {
err := fd.pfd.SetsockoptInt(syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, boolint(keepalive))
runtime.KeepAlive(fd)
return wrapSyscallError("setsockopt", err)
}
配置TCP keepalive的时间参数:
syscall.TCP_KEEPIDLE: tcp_keepalive_time参数,配置为15s;
syscall.TCP_KEEPINTVL: tcp_keepalive_intvl参数,配置为15s;
tcp_keepalive_probes使用系统配置:2;
总结下来,server在连接15s没有数据后,发起探测,间隔15s发起一次探测,探测2次后不再活跃就kill连接,故一个空闲连接要等:15+15*2=45s后被kill。
func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
// The kernel expects seconds so round to next highest second.
secs := int(roundDurationUp(d, time.Second))
if err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs); err != nil {
return wrapSyscallError("setsockopt", err)
}
err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs)
runtime.KeepAlive(fd)
return wrapSyscallError("setsockopt", err)
}
syscall中的调用参数:
//其中:
TCP_KEEPIDLE --> /proc/sys/net/ipv4/tcp_keepalive_time
TCP_KEEPINTVL --> /proc/sys/net/ipv4/tcp_keepalive_intvl
TCP_KEEPCNT --> /proc/sys/net/ipv4/tcp_keepalive_probes
参考
1.https://zhuanlan.zhihu.com/p/69337371
2.https://segmentfault.com/a/1190000022022271
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)