我给微信找BUG系列:微信代理引起的负载问题排查过程

马谦马谦马谦 程序员1 1,573字数 2461阅读8分12秒阅读模式

本周处理了好几例负载高问题,原因竟然都是因为微信对代理场景的支持不好导致的。

回顾十分曲折的排查过程,记录下来!顺带吐槽一下微信。

一、问题描述

背景:我们的设备作为客户处的上网出口,代理内网用户上网。

问题:设备流量不高,但是负载特别高,经常性产生断网事件,流量突降为0。

二、排查过程

2.1 分析负载和CPU

先画出问题时间点的负载趋势图,可以看到负载确实是很高的:

我给微信找BUG系列:微信代理引起的负载问题排查过程-图片1

这个设备只是2核的,负载超过4就可以认为负载高了,而记录下来的负载基本都在10以上,远超出设备负载。

然后分析CPU采样,找到对应时间点的mpstat记录,采样中显示较高的是iowaitsoft

soft高一般都是流量过高或者连接数过大导致的,而iowait高大部分时候都是内存不足导致的,因为内存不足会导致内存cache回收,大量的磁盘脏数据刷到磁盘,导致IO高。因此下一步应该先分析内存。

2.2 分析内存占用

分析问题时间点前后的内存状态,发现了内存cache回收的情况:

设备只有2G内存,属于低端平台了,内存不足应该属于常态,所以经常回收cache,导致了io高。

然后统计slab占用,发现占用最高的是TCP和TCPv6,总共占用了接近三百兆:

我给微信找BUG系列:微信代理引起的负载问题排查过程-图片2

正常情况下,这两个东西不可能占用这么大比例。这只能说明,网络流量有异常!!

内核给TCPv6和TCP分配slab的场景

为了搞清楚内核在什么场景下会分配这两块内存好对症下药,所以在内核中定位了相关代码,最后发现是创建TCP套接字的时候分配的。

slab是内核中的的内存分配机制,所有的内存都通过kmem_cache_create创建,而每块被分配的内存块都是有名字的(如上面的TCPv6和TCP)。因此在内核代码中直接搜索:

不出意料,没有搜索到相关代码。因为根据linux代码的特性,这个字符串肯定是被宏定义代替或者结构体引用了。

于是先搜索"TCPv6",在net/ipv6/tcpv6_prot中找到了定义:

整个内核代码中只有这一处出现了关键字段,除此之外没有其他地方有这个字符串,因此肯定一定是哪里引用这个字段。继续通过正则查找kmem_cache_create\(.*->namne,找到:

proto_register是注册协议到内核的协议栈,产生对应协议请求的时候会调用这个方法来创建socket对象,TCPv6TCP内存块占用内存高,说明肯定是这里走得多了,这就间接说明设备肯定产生了大量连接。

2.3 分析网络流量

分析出流量有异常之后,第一个想到的是不是网络环境有问题,因为上周刚好也查了一个类似的问题:一次孤儿socket过多导致系统异常的问题排查过程。但是抓包分析后发现,流量并没有出现类似的特征,而且设备总共的孤儿socket并不多,这说明两个问题不是同一个原因。

于是只能继续分析了,因为流量特征(各种数据包的占比)都比较正常,所以排查的重点放在的包的内容身上。终于在经过了大量的分析后发现了数据异常:

我给微信找BUG系列:微信代理引起的负载问题排查过程-图片3

以上是内网发到我们设备的代理请求,设备地址是10.115.5.2,因为是代理场景,所以目的地址是它,这没有问题。但问题出在了数据包身上,也就是最后一列,可以看到他们都是:

这个HTTP代理请求是请求代理本机,这是明显有问题的,也是不符合RFC规定的,正常的代理数据包应该是被代理的地址如www.biadu.com,绝不会是10.115.5.2,如:

以下是一些正常的代理请求:

我给微信找BUG系列:微信代理引起的负载问题排查过程-图片4

如果代理的请求是本机,就会导致本机自己再去连接自己的80端口,造成的结果就是设备会多产生一个发往本机nginx的连接。nginx收到请求后因为不存在这个uri资源会返回404到代理进程,代理进程再返回给客户端。流程为:

我给微信找BUG系列:微信代理引起的负载问题排查过程-图片5

本来到这里也没有很大的问题,只是会导致本机多一个连接而已。但关键的问题是,返回错误给PC客户端后,客户端会频繁发起重连,产生大量的重试请求。频率有多高呢?从下面的这个抓包截图来看,不到0.5ms的时间内内网就产生了10+个请求:

我给微信找BUG系列:微信代理引起的负载问题排查过程-图片6

代理场景并不像路一样由,只要做NAT把地址转换一下发出去就可以了,路由对设备的资源占用并不大。代理场景除了要和内网PC执行三次握手以外,还要主动发起连接请求WAN端,要产生双倍的资源消耗。如此大规模的重连请求,占用了大量的连接资源,足以算得上dos攻击了。因此,可以认为本次问题就是内网的异常连接导致的。

这些异常的请求具体是什么呢,通过分析数据包内容和特征,很容易就能看出是微信的请求,是在请求下载小视频:

我给微信找BUG系列:微信代理引起的负载问题排查过程-图片7

三、和微信的交涉

为了确认微信的问题,找微信客服反馈。他们承认确实是有问题,但是并不想改:

我给微信找BUG系列:微信代理引起的负载问题排查过程-图片8

四、微信代理存在的问题

微信代理场景目前存在以下问题:

  1. 代理数据包构造不正确,也就是上面描述的场景。
  2. 微信视频不支持代理,使用代理后无法发起微信视频。
  3. 微信传输大文件时,不会经过代理,直接发到了服务端了。
  4. 不支持代理长连接。

第三个问题是出在另外一个客户身上,现象上是无法传输超出20M以上的文件。实际上通过抓包分析发现是微信直接绕过了代理,目的地址不是代理设备地址:

我给微信找BUG系列:微信代理引起的负载问题排查过程-图片9

而不支持长连接问题就更有意思了,问题现象是微信代理后无法正常收到图片等相关资源,微信给出的反馈竟然是不支持嵌套,嵌套这个词说出来就很有意思了,刚说出来我还琢磨是什么嵌套。最后抓包发现竟然指的是HTTP长连接。

我给微信找BUG系列:微信代理引起的负载问题排查过程-图片10

我给微信找BUG系列:微信代理引起的负载问题排查过程-图片11

五、吐槽

从技术角度而言,这几个问题实际上并不能算成是问题,调整起来的技术难度并不高。不知道为什么就是不调整,反馈了很多次了。

2020-4-12日更新:微信下周会发新版,解决这个问题!

 

 最后更新:2020-4-12
马谦马谦马谦
  • 本文由 马谦马谦马谦 发表于 2020年3月14日10:45:34
  • 转载请务必保留本文链接:https://www.dyxmq.cn/program/wechat-proxy-bug.html
system和popen返回ENOMEM问题 程序员

system和popen返回ENOMEM问题

一、我为什么不喜欢system和popen 要说到我为什么不喜欢system和popen这两个函数,这个说来就话长了。最开始,我还是很喜欢用这两个函数的,直到后来发现了太多因为滥用导致的程序异常后,它...
    • none
      none 0

      > 代理场景并不像路一样由

    匿名

    发表评论

    匿名网友
    :?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:
    确定

    取消

    拖动滑块以完成验证