一次抓包排查实战记录

问题的发现 周五,本是一个风清气爽,令人愉悦的日子。我本还在美滋滋地等待着下班,然而天有不测,有用户反应容器日志看不到了,根据经验我知道,日志采集&收集链路上很可能又发生了阻塞。 登录目标容器所在机器找到日志采集容器,并娴熟地敲下docker logs --tail 200 -f <container-id>命令,发现确实阻塞了,阻塞原因是上报日志的请求500了,从而不断重试导致日志采集阻塞。 随后,我找到收集端的容器,查看日志,发现确实有请求报500了,并且抛出了Unknown value type的错误,查看相关代码。 业务代码: if _, err := jsonparser.ArrayEach(body, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { ... }); err != nil { return err // 错误抛出点 } jsonparser包中代码: 显然问题出在了对body的解析上,究竟是什么样的body导致了解析错误呢?接下来,就是tcpdump和wireshark上场的时候了。 使用Tcpdump抓包 首先,我们通过tcpdump抓到相关的请求。由于日志采集端会不断重试,因此最简单的方法便是登录采集端所在机器,并敲下如下命令tcpdump -i tunl0 dst port 7777 -w td.out ,并等待10-20秒。 熟悉tcpdump的小伙伴,对这条命令显然已经心领神会了。尽管如此,这里我还是稍微解释下。 -i tunl0:-i 参数用来指定网卡,由于采集器并没有通过eth0。因此,实战中,有时发现命令正确缺抓不到包的情况时,不妨指定下别的网卡。网络错综复杂,不一定都会通过eth0网卡。 dst port 777: 指定了目标端口作为过滤参数,收集端程序的端口号是7777 -w td.out: 表明将抓包记录保存在td.out文件中,这是因为json body是用base64编码并使用gzip加密后传输的,因此我得使用wireshark来抽离出来。(主要还是wireshark太香了:),界面友好,操作简单,功能强大) 接着,我用scp命令将td.out文件拷到本地。并使用wireshar打开它 使用Wireshark分析 打开后,首先映入眼帘的则是上图内容,看起来很多?不要慌,由于我们排查的是http请求,在过滤栏里输入HTTP,过滤掉非HTTP协议的记录。 我们可以很清楚地发现,所有的HTTP都是发往一个IP的,且长度都是59,显然这些请求都是日志采集端程序不断重试的请求。接下来,我们只需要将某个请求里的body提取出来查看即可。 很幸运,wireshark提供了这种功能,如上图所示,我们成功提取出来body内容。为bnVsbA==,使用base64解码后为null。 解决问题 既然body的内容为null,那么调用jsonparser.ArrayEach报错也是意料之中的了,body内容必须得是一个JsonArray。 然而,采集端为何会发送body为null的请求呢,深入源码,发现了如下一段逻辑。 func (e *jsonEncoder) encode(obj []publisher....

八月 2, 2020 · 1 分钟 · erenming

Golang中的map实现

总所周知,大多数语言中,字典的底层是哈希表,而且其算法也是十分清晰的。无论采用链表法还是开放寻址法,我们都能实现一个简单的哈希表结构。对于Go来说,它是具体如何实现哈希表的呢?以及,采取了哪些优化策略呢? 内存模型 map在内存的总体结构如下图所示。 头部结构体hmap type hmap struct { count int // 键值对个数 flags uint8 B uint8 // 2^B = 桶数量 noverflow uint16 // 溢出桶的个数 hash0 uint32 // hash seed buckets unsafe.Pointer // 哈希桶 oldbuckets unsafe.Pointer // 原哈希桶,扩容时为非空 nevacuate uintptr // 扩容进度,地址小于它的桶已被迁移了 extra *mapextra // optional fields } hmap即为map编译后的内存表示,这里需要注意的有两点。 B的值是根据负载因子(LoadFactor)以及存储的键值对数量,在创建或扩容时动态改变 buckets是一个指针,它指向一个bmap结构 桶结构体bmap type bmap struct { // tophash数组可以看做键值对的索引 tophash [bucketCnt]uint8 // 实际上编译器会动态添加下述属性 // keys [8]keytype // values [8]valuetype // padding uinptr // overflow uinptr } 虽然bmap结构体中只有一个tophash数组,但实际上,其后跟着8个key的槽位、8个value的槽位、padding以及一个overflow指针。如下图所示...

二月 1, 2020 · 2 分钟 · erenming

用Golang实现并理解Web中间件

在编写web应用中,我们常常会遇到这样的需求,比如,我们需要上报每个API的运行时间到运维监控系统。这时候你可以像下述代码一样将统计的逻辑写到每个路由函数中。 ...

十二月 20, 2019 · 1 分钟 · erenming

Golang中的string实现

说到string类型,我们往往都能很熟练地对它进行各种处理,包括迭代、随机访问和匹配等等操作。然而在工作中,我发现迭代一个字符串产生的字符的类型与随机访问一个字符的类型却并不相同,为什么会这么奇怪呢?于是我决定一探究竟 ...

十二月 11, 2019 · 1 分钟 · erenming