开发过程中遇到的问题

  1. Kafka日志传输网络延迟导致的服务假死。

    原因:zap框架挂载了kafka日志传输,是同步模式。由于网络延迟,kafka日志不能及时完成传输,导致业务代码产生延迟。

    场景: 同事首先发现问题是在某个TCP写的位置,本地正常,初步怀疑是TCP写延迟,但是并未明确

    排查过程:

    1. TCP读顺利无延迟,写最终能被接收,初步排除网络连接方面问题

      1. pprof, 火焰图heap,cpufile均无明显异常,goroutine没有明显增高(实际有异常增多,但是当时用户量不高,没有发现,只是以为用户量引起的goroutine变化)
      2. 前后打日志(本地正常,所以无法对服务器做断点测试),标准输出或文本输出都正常,但是在write之后的日志出来的慢
      3. 疑惑…仍然怀疑TCP问题,但是稍加思考后还是否定了
      4. 只有日志模块(当时基本没有怀疑日志),没有查看ELK日志,因为一开始不知道使用了kafka日志传输
      5. 查看日志模块代码,有挂载kafka日志。查看配置,生产的连接写成了测试的连接地址,服务器在国外。

    解决方案: 修改连接,顺便将kafka的传输方式改成异步。

  2. Kafka日志传输造成的服务“停止”

    原因:kafka服务没有开启

    场景:客户端大量接收不到回包,日志稀少(但是阅读代码发现服务在大部分情况下确实不打日志)。但是过一段时间(半小时到1小时),几乎所有用户连接不上,telnet通过。

    排查过程:

    1. 查询逻辑,发现没有明显会造成阻塞的情况
    2. 在相关消息入口增加日志,重启服务后发现过一段时间后重现,日志不打印
    3. 怀疑日志问题,查看日志模块
    4. 发现kafka挂载,查看kafka异步代码,阻塞通道满
    

    总结: 这两次其实通过异常goroutine的排场是可以直接从pprof中定位到问题所在的,但是由于对他人项目的不熟,加上一眼看上去没有问题的心理,没有充分使用到pprof分析,导致后来依赖阅读代码的来排查。

  3. 连接指针未释放造成的最终内存持续累积

    原因: 连接未释放,早期read方法有点奇葩,好像是先分配一个指定大小的 byte 数组再去读数据的(具体不记得了)

    场景: 内存持续缓慢增长,heap数量只增不减

    排查过程:

    1. 通过pprof生成heap文件,图形化显示下查询到内存占用最多的地方累积在read方法。
    2. read的奇葩读法,但是read每次分配好像是4M,,依照当时的用户量以及15超时清理分钟,也不应该达到那么大累积量
    3. 继续查看pprof文件,发现有大量的Client连接,考虑到15分钟断开可能有所累积,但是结合web图的引导,确实是连接下的read积累了最多的内存
    4. 怀疑连接没有正确释放造成(这里是无法直接得出哪块代码有问题的)
    5. 查询代码,发现客户端和用户管理混乱,让其自己按照这个方向排查代码
    6. 得出问题,是在服务器异常断开后,没有EOF,新连接替代了旧连接,然后在用户管理下的超市清理处理不到这部分链接,被一直保存到connMap中,其read goroutine 也无法断开
    
  4. 客户端允许body过长造成的内存突增

    原因:socket分包的时候通过协议header的bodyLen分配内存,客户端由于某些错误传递了很大的bodyLen使得内存突增

    场景:监控服务报警,内存突增。

    排查过程:

    1. 由于有上述的经验,首先怀疑是read时候分配内存堆积造成的,实际上确实如此,但有所不同的是当前是徒增,而且没有出现指针不释放的问题
    2. 直接查看源码,发现bodyLen的判断是小于MaxInt32
    3. 直接依靠经验判断是bodyLen传入过大(而且基于最近出现了客户端连接异常的情况,发数据无响应等)。
    
  5. 客户端总是因心跳超时而被清理(早期设置心跳超时是30秒,没有设置readTimeout而是服务器根据心跳时间判断清理)

    原因: mars框架对相同数据发送的处理

    排查过程:

    1. debug模式下查看心跳日志,日志显示心跳在一段时间后(每五秒)会停止,客户端怀疑是服务端业务处理延迟。
    2. 通过tcpdum抓包,明确服务端业务处理没有问题
    3. 怀疑是tcp纳格算法造成的小数据包延迟
    4. 设置了TCPNODELAY依然会出现
    5. 敦促客户端自查源码,发现mars框架相同的数据传送多少次之后就不再传送
    

总结:

1.  服务端的代码框架是自己开发的,所以流程十分清晰,日志齐全,排查定位问题简单
2.  设计TCP的知识略有疑惑,但所幸通过baidu能及时解决
3.  当时和客户端商议提出一起写一份属于自己公司的通用框架模版,结果自己花费月余时间收集整理,客户端却直套了第三方,只是做业务处理上的框架,导致开发过程中不断要兼容客户端的处理方式,十分痛苦。但是也正是因为是自己开发的框架,在变更处理逻辑的时候十分顺畅。
4.  日志是很重要的,尽管有pprof可以排查问题,但是很多时候自己还是忽略了pprof的某些信息导致没有排查到问题,这样清晰的日志也可以很快帮忙定位问题。