TCP Keepalive与go-rpc的tcp连接

TCP Keepalive

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/...
2.https://tldp.org/HOWTO/TCP-Ke...

38 声望
21 粉丝
0 条评论
推荐阅读
cadvisor采集docker容器的DiskUsage指标--源码分析
一. DiskUsage指标cadvisor采集的docker容器的DiskUsage指标,包含:container_fs_inodes_freecontainer_fs_inodes_totalcontainer_fs_limit_bytescontainer_fs_usage_bytes {代码...} 二. 采集的过程docker容器...

a朋阅读 507

「刷起来」Go必看的进阶面试题详解
逃逸分析是Go语言中的一项重要优化技术,可以帮助程序减少内存分配和垃圾回收的开销,从而提高程序的性能。下面是一道涉及逃逸分析的面试题及其详解。

王中阳Go4阅读 1.9k评论 1

封面图
初学后端,如何做好表结构设计?
这篇文章介绍了设计数据库表结构应该考虑的4个方面,还有优雅设计的6个原则,举了一个例子分享了我的设计思路,为了提高性能我们也要从多方面考虑缓存问题。

王中阳Go4阅读 1.7k评论 2

封面图
滚蛋吧,正则表达式!
你是不是也有这样的操作,比如你需要使用「电子邮箱正则表达式」,首先想到的就是直接百度上搜索一个,然后采用 CV 大法神奇地接入到你的代码中?

良许4阅读 2.3k

又一款眼前一亮的Linux终端工具!
今天给大家介绍一款最近发现的功能十分强大,颜值非常高的一款终端工具。这个神器我是在其他公众号文章上看到的,但他们都没把它的强大之处介绍明白,所以我自己体验一波后,再向大家分享自己的体验。

良许5阅读 1.8k

一分钟搞明白!快速掌握 Go WebAssembly
最近因为各种奇怪的原因,更多的接触到了 WebAssembly。虽然之前很多博客也翻过写过各种文章,但总感觉欠些味道。于是今天梳理了一版,和大家一起展开学习。

煎鱼4阅读 2.1k

TCP 三次握手,给我长脸了噢
之前有个小伙伴在技术交流群里咨询过一个问题,我当时还给提供了点排查思路,是个典型的八股文转实战分析的案例,我觉得挺有意思,趁着中午休息简单整理出来和大家分享下,有不严谨的地方欢迎大家指出。

程序员小富4阅读 571

封面图
38 声望
21 粉丝
宣传栏