写点什么

TCP close_wait 引发的血案

作者:云舒编程
  • 2024-01-25
    广东
  • 本文字数:1229 字

    阅读完需:约 4 分钟

TCP close_wait 引发的血案

大家好,我是「云舒编程」,今天我们来聊聊最近遇到的线上出现大量 close_wait 导致服务不可用的问题。


文章首发于微信公众号:云舒编程

关注公众号获取:1、大厂项目分享 2、各种技术原理分享 3、部门内推

一、问题

     服务 A 调用服务 B,在服务 A 的机器上出现了大量的 close_wait 状态的 TCP 连接。


二、closed_wait

     根据 TCP 四次挥手,理论上 close_wait 是一个非常短暂的状态,对应到下图:当服务端接收到客户端的 FIN 并且回复 ACK 后服务端就会进入 close_wait。然后该服务端继续发送 FIN 包后就会继续进入后续的流程,最终会正常关闭 TCP 连接。


     如果服务端出现了大量的 close_wait 那就证明没有进行正常的 TCP 关闭,也就是服务端最终没有调用 close 或者 shutdown,导致最后一个 FIN 没有发出去。


IP 异常

        通过排查发现服务 A 处于 closed_wait 状态的对应的服务 B 的 IP 都已经不在对方的服务列表中了。


        同时同事反馈前天进行了一次压测,触发了下游的自动扩缩容。拿着这些 IP 跟运维确定,发现的确是前天扩容后又缩容了的 IP。

三、分析

出现大量 closed_wait 的条件:

  1. 大量的短 TCP 链接

  2. 未正确关闭 TCP(close 或者 shutdown)


前天压测满足了条件一,那就只剩下条件二了。

由于服务使用了连接池,猜测是不是这里导致的问题。连接池大致逻辑如下:

type ConnPool struct{    poolName string    connsMap map[string][]*net.Conn //key是对端ip+port,value是连接池列表}
func (cli *TcpClient) doSend(ctx context.Context,sendByte []byte)([]byte,error){ pool := getPool(TCP_POOL_NAME)
//根据IP,PORT 分配conn ip port从服务注册中心获取 conn := pool.Alloc(cli.ip,cli.port)
//放回连接池 defer pool.Put(conn)
conn.Write()
conn.Read()
return }
复制代码


        发现该连接池的管理比较坑,使用被调用方的 ip+port 作为 key 进行存储。如果对方的服务下线了,那么从服务注册中心就再也无法获取该 ip 了,其对应的 TCP 连接就再也无法释放,并且未对连接做探活处理,从而导致 TCP 状态会永远停留在 closed_wait 状态。

以前为什么没有出现

     按照上述的连接池实现,只要下游的 IP 出现了变化,那么理论上我们的服务就会出现无法释放的 closed_wait 状态的连接才对。那这个问题应该早就暴露了才对?


     通过排查就发现了极其狗血的事情:下游服务的发布窗口在每周四的下午,我们的服务发布是在每周五的下午。通过狗血的发布窗口就把这个事情给自然解决了。

问题解决

  1. TCP 连接设置 keepalive

  2. 单独使用一个协程定时去检测连接是否可用

  3. 读取到了 io.EOF,这种就说明对端(服务端)关闭了这个连接,该连接可以释放了。

  4. 拿 ip、port 询问注册中心是否可用,不可用则关闭。

推荐阅读

1、原来阿里字节员工简历长这样

2、一条SQL差点引发离职

3、MySQL并发插入导致死锁


如果你也觉得我的分享有价值,记得点赞或者收藏哦!你的鼓励与支持,会让我更有动力写出更好的文章哦!

更多精彩内容,请关注公众号「云舒编程」


发布于: 刚刚阅读数: 4
用户头像

云舒编程

关注

公众号 云舒编程,大白话分享技术原理 2020-09-23 加入

字节、阿里资深工程师。 做过营销、支付、百万级Feed流优化、权限系统、网关。 专注于技术原理分享,用最简单的话分享最复杂的技术原理

评论

发布
暂无评论
TCP close_wait 引发的血案_TCP_云舒编程_InfoQ写作社区