之前在这篇无人值守(一)[1]简单介绍了我们针对线上抖动问题定位的工具的设计思路,思路很简单,技术含量很低,是个人都可以想得到,但是它确实帮我们查到了很多很难定位的问题。

在本篇里,我们重点讲一讲这个工具[2]在生产环境帮我们发现了哪些问题。

OOM 类问题

RPC decode 未做防御性编程,导致 OOM

应用侧的编解码可能是非官方实现(如 node 之类的无官方 SDK 的项目),在一些私有协议 decode 工程中会读诸如 list len 之类的字段,如果外部编码实现有问题,发生了字节错位,就可能会读出一个很大的值。

非标准 app ----encode-------> 我们的应用 decode -----> Boom!

decoder 实现都是需要考虑这种情况的,类似这样[3]。如果对请求方的数据完全信任,碰到对方的 bug 或者恶意攻击,可能导致自己的服务 OOM。

在线上实际发现了一例内存瞬间飚升的 case,收到报警后,我们可以看到:

1: 1330208768 [1: 1330208768] @ 0x11b1df3 0x11b1bcb 0x119664e 0x11b1695 0x1196f77 0x11a956a 0x11a86c7 0x1196724 0x11b1695 0x11b1c29 0x119664e 0x11b1695 0x11b1c29 0x119664e 0x11b1695 0x11b1c29 0x119664e 0x11bb360 0x168f143 0x179c2fc 0x1799b70 0x179acd6 0x16d3306 0x16d1088 0xf59386 0xf59474 0xf54e5f 0xf54987 0xf539f1 0xf6043a 0xcd8c0d 0x49b481
....下面是表示栈内容的,这不重要

1: 1330208768 [1: 1330208768] 表示 inuse_objects : inuse_space [alloc_objects : alloc_space],这里可以看到一个对象就直接用掉了 1GB 内存,显然不是正常情况,查看代码后,发现有未进行大小判断而完全信任用户输入数据包的 decode 代码。

修复起来很简单,像前面提到的 async-h1 一样加个判断就可以了。

tls 开启后线上进程占用内存上涨,直至 OOM

线上需要做全链路加密,所以有开启 tls 的需求,但开启之后发现内存一路暴涨,直至 OOM,工具可以打印如下堆栈:

heap profile: 1460: 27614136 [45557: 1080481472] @ heap/1048576
727: 23822336 [730: 23920640] @ 0xc56b96 0xc591e8 0xc58e68 0xc59ed1 0xdd55ff 0xde15b8 0xde13ef 0xde09e9 0xde050c 0x13bfa13 0x13bf475 0x14c33d0 0x14c49f8 0x14cb398 0x14bffab 0x14cdf78 0xddcf90 0x45eda1
# 0xc56b95  *****mtls/crypto/tls.(*block).reserve+0x75   *****mtls/crypto/tls/conn.go:475

查阅老版本的 Go 代码,发现其 TLS 的 write buffer 会随着写出的数据包大小增加而逐渐扩容,其扩容逻辑比较简单:

func (b *block) reserve(n int) {if cap(b.data) >= n {return}m := cap(b.data)if m == 0 {m = 1024}for m < n {m *= 2}data := make([]byte, len(b.data), m)copy(data, b.data)b.data = data
}

初始为 1024,后续不够用每次扩容为两倍。但是阅读 tls 的代码后得知,这个写出的数据包大小最大实际上只有 16KB + 额外的一个小 header 大小左右,但老版本的实现会导致比较多的空间浪费,因为最终会扩容到 32KB。

这段比较浪费空间的逻辑在 Go1.12 之后已经进行了优化:

func sliceForAppend(in []byte, n int) (head, tail []byte) {if total := len(in) + n; cap(in) >= total {head = in[:total]} else {head = make([]byte, total)copy(head, in)}tail = head[len(in):]return
}

变成了需要多少,分配多少的朴实逻辑。所以会比老版本在这个问题上有不少缓解,不过在我们的场景下,新版本的代码依然没法满足需求,所以还需要进一步优化,这就是后话了。

goroutine 暴涨类问题

本地 app GC hang 死,导致 goroutine 卡 channel send

在我们的程序中有一段和本地进程通信的逻辑,write goroutine 会向一个 channel 中写数据,按常理来讲,同物理机的两个进程通过网络通信成本比较低,类似下面的代码按说不太可能出问题:


concurrently:
taskChan <- taskconsumer:
for task := range taskChan {// 憋一些 task 一起写localConnection.write(task 们)
}

看起来问题不大,但是线上经常都有和这个 channel send 相关的抖动,我们通过工具拿到的现场:

2020-11-03 08:00:05,950 [ERROR] [diag.goroutine] [diagnose] pprof goroutine, config_min : 3000, config_diff : 25, config_abs : 200000,  previous : [41402 44257 47247 50085 52795 55509 29762 32575 35451 38460], current : 55509, profile : goroutine profile: total 55513
40844 @ 0x46daaf 0x4433ab 0x443381 0x443165 0xf551f7 0x12fd2e7 0x12fc94f 0x13f41d5 0x13fc45f 0xf43ee4 0xcd8c0d 0x49b481
#    ****channel.Send 这是个假的栈,你理解意思就行了
#

当前憋了 5w 个 goroutine,有 4w 个卡在 channel send 上,这个 channel 的对面还是一条本地连接,令人难以接受。

但是要考虑到,线上的业务系统是 Java,Java 发生 FGÇ 的时候可不是闹着玩的。对往本地连接的 write buffer 写数据一定不会卡的假设是有问题的。

既然出问题了,说明在这里对我们的程序进行保护是必要的,修改起来也很简单,给 channel send 加一个超时就可以了。

应用逻辑死锁,导致连接不可用,大量 goroutine 阻塞在 lock 上

大多数网络程序里,我们需要在发送应用层心跳,以保证在一些异常情况(比如拔网线)下,能够把那些无效连接从连接池里剔除掉。

对于我们的场景来说,客户端向外创建的连接,如果一直没有请求,那么每隔一段时间会向外发送一个应用心跳请求,如果心跳连续失败(超时) N 次,那么将该连接进行关闭。

在这个场景下会涉及到两把锁:

  • 对连接进行操作的锁 conn lock

  • 记录心跳请求的 request map lock

心跳成功的流程:收到心跳响应包,获取 conn lock -> 获取 request map lock 心跳失败的流程:timer 超时,获取 request map lock -> 需要关闭连接 -> 获取 conn lock

可以看出来,心跳的成功和失败流程并发时,获取锁的流程符合死锁的一般定义:持有锁、非抢占、循环等待。

这个 bug 比较难触发,因为心跳失败要失败 N 次才会关闭连接,而正好在最后一次发生了心跳成功和失败并发才会触发上述的死锁,线上可以通过 goroutine 短时间的上涨发现这个问题,goroutine 的现场也是可以看得到的。简单分析就可以发现这个死锁问题(因为后续的流程都会卡在其中一把锁上)。

知道原因解决起来就不麻烦了,涉及到一些具体的业务逻辑,这里就不赘述了。

CPU 尖刺问题

应用逻辑导致死循环问题

国际化业务涉及到冬夏令时的切换,从夏令时切换到冬令时,会将时钟向前拔一个月,但天级日志轮转时,会根据轮转前的时间计算 24 小时后的时间,并按与 24:00 的差值来进行 time.Sleep,这时会发现整个应用的 CPU 飚高。自动采样结果发现一直在循环计算时间和重命名文件。

list 一下相关的函数,能很快地发现执行死循环的代码位置。这里就不截真实代码了,随便举个例子:

        .          .     23:func cpuex(wr http.ResponseWriter, req *http.Request) {.          .     24: go func() {17.73s     19.37s     25:  for {.          .     26:  }.          .     27: }().          .     28:}

参考资料

[1]

无人值守(上): https://xargin.com/autodumper-for-go/

[2]

工具: https://github.com/mosn/holmes

[3]

这样: https://github.com/http-rs/async-h1/blob/main/src/server/decode.rs#L41

无人值守的自动 dump(二)相关推荐

  1. 无人值守的自动 dump(一)

    Go 项目做的比较大(主要说代码多,参与人多)之后,可能会遇到类似下面这样的问题: 程序老是半夜崩,崩了以后就重启了,我也醒不来,现场早就丢了,不知道怎么定位 这压测开压之后,随机出问题,可能两小时, ...

  2. 大津二值化算法 ( Otsu's binarization ) 自动确定二值化图像时的阈值

    大津算法,也被称作最大类间方差法,是一种自动确定二值化阈值的算法. 在这里作者不介绍算法推导的过程,算法推导过程网络上有许多介绍,这里只给出算法最终推导出的结论: 使得左侧 的值最大,就可以得到最好的 ...

  3. 微信二维码来源统计自动生成二维码统计?

    微信二维码来源统计,我们先来了解下渠道二维码,我们的微信公众号使用渠道二维码可以实现记录粉丝是通过二维码扫描关注并进入活动功能的统计,渠道二维码还可以让粉丝扫码后直接关注成为粉丝并直接弹出微信活动的页 ...

  4. X64dbg脚本实现自动DUMP运行中解密出的PE文件

    X64dbg脚本实现自动DUMP运行中解密出的PE文件 // define a variable to hold allocated mem address var mem_addr // defin ...

  5. vue 项目中 自动生成 二维码

    vue 项目中 自动生成 二维码 ​ 最近在写一个vue项目,要求根据卡号可以自动生成一个二维码,并渲染在指定位置,因为第一次做类似业务,小编在网上找了找,发现了很多,具体起来主要用的就两种: QRc ...

  6. 【在web项目jsp页面自动生成二维码功能】

    在web项目jsp页面自动生成二维码功能 原文: http://www.cnblogs.com/gczmn/. https://www.jq22.com/jquery-info294/. 先将下面的文 ...

  7. 最强打包插件,支持fir,蒲公英上传, 360加固 ,自动生成二维码

    文章目录 序言 说明 效果 使用 下载demo 导入文件 文件内容说明 配置gradle 配置gradle.properties 文件位置 内容 项目中配置 补充说明 1.360加固配置 2.curl ...

  8. java 内存溢出时打印_如何在JVM内存溢出的时候自动dump内存快照

    解决OOM问题的一个初步思路 首先第一个问题,假设发生OOM了,必然说明系统中某个区域的对象太多了,塞满了那个区域,而且一定是无法回收掉那些对象,最终才会导致内存溢出的. 既然是这个思路,要解决OOM ...

  9. 西门子界面官方精美触摸屏+WINCC程序模板 西门子官方触摸屏程序模板,炫酷的扁平式动画效果,脚本动画,自动生成二维码,可仿真,堪比智能手机,有精简,精致,wincc,无线面板等包含了所有西门子人机界

    西门子界面官方精美触摸屏+WINCC程序模板 西门子官方触摸屏程序模板,炫酷的扁平式动画效果,脚本动画,自动生成二维码,可仿真,堪比智能手机,有精简,精致,wincc,无线面板等包含了所有西门子人机界 ...

最新文章

  1. Python数据挖掘1:创建一位数组和二维数组,取最大最小值,切片
  2. 环境变量、用户变量、系统变量
  3. Linux命令 crontab的理解和使用方法
  4. 消息中间件—简谈Kafka中的NIO网络通信模型
  5. ITK:遮盖一张图像给定标签图
  6. MVC之实体框架(数据持久化框架)EntityFrameWork(EF)
  7. 04751计算机网络安全讲解,【19份】04751计算机网络安全自考试卷_历年真题自考答案及解析_湖南080901计算机科学与技术(原B080702计算机及应用)专业-自考生资料网...
  8. 【UML】协作图Collaboration diagram(交互图)(转)
  9. 非受检异常(运行时异常)和受检异常的区别等
  10. 《GPU高性能编程CUDA实战》代码整理
  11. Qt总结之十三:QUDPSocket详解
  12. 今天给一份 2022 互联网就业指南。
  13. This relative module was not found: Error: Can‘t resolve ‘../assets/bg.jpg‘
  14. VBA实现dwg批量输出PDF
  15. 从实际案例聊聊JDK 17 的GC优化
  16. 双11之战:被激化的酒类电商出击,看1919新打法
  17. SP 2022论文泛读
  18. JDBC操作达梦数据库
  19. 2022-2028全球与中国语音控制设备市场现状及未来发展趋势
  20. 算法导论 — 比较排序算法对比实验

热门文章

  1. go mysql 查询语句_01 MySQL-初识MySQL-查询语句的执行流程-Go语言中文社区
  2. 我们是python_我们生活在“Python时代”
  3. 哈希表-map(对于python来说是字典)
  4. 综合学生信息管理系统(JSP+JDBC)
  5. 剑指offer 回溯法 面试题12 矩阵中的路径 面试题13 机器人的运动范围
  6. 稍稍乱入的CNN,本文依然是学习周莫烦视频的笔记。
  7. ActivityInfo taskAffinity
  8. 事件监听一直报错Cannot set property 'display' of undefined
  9. mac osx wine 1.7.5 源码编译方法及中文乱码的解决
  10. 2015第29周五AOP