Spring Cloud Gateway 500 问题排查

Sherwin.Wei Lv7

Spring Cloud Gateway 500 问题排查

提供一个线上 Spring Cloud Gateway 问题排查案例,涉及网关的 500 错误、源码还涉及到 linux node 知识点

线上就开始报警了,一看情况网关报 500 了。。

网关(用的是Spring Cloud Gateway)挂了,岂不是所有请求都进不来了?

到底怎么回事?

及时处理

话不多说,直接重启(及时回复)。

然后看起来好了,重启大法!yyds!

问题排查

感觉有点突然,最近网关并没有发版,上一个版本还是在 4 月份,挂的有点莫名其妙。

不过这两天在执行一个动态迁移服务,有大量的请求,预估两天的请求量有 300 w,我一开始以为是被这个打挂了。

但是有关的接口我都加了限流,且请求一直是匀速的,也已经正常处理一天一夜了,服务的各项指标也没任何异常,咋就突然这样了呢?

仔细看了看,网关的服务没挂,但是请求都返回了 500。

上去一查日志:

企业微信截图_5ae5c26d-ff69-427c-bfe0-d48232afece7.png

这是什么玩意?

看起来是要创建临时目录,然后失败了,因为空间不足,但是实际上我看磁盘空间还很多,这时候我已经有点感觉了。

可以看到是 Spring 的 SynchronossPartHttpMessageReader 类触发的这个报错,网关代码确实有用到 HttpMessageReader 相关的解析。

然后进行一波源码分析:

企业微信截图_35efaf20-1978-4045-af7f-4673e7e2bdb4.png

点进源码粗略了看了下,每个请求的解析都会创建一个临时目录,往里跟了几步看了看,这个临时目录的作用是到时候用来给解析 Multipart 存储临时文件用的。

企业微信截图_413eb438-9489-4ad9-b903-0745ed426fdc.png

但最近的请求也都跟 Multipart 没关系了,咱也不懂为啥要这样先预创建。

看到这反正问题已经定位到了:因为网关要用到 HttpMessageReader 相关的解析,而这个解析实现类,每次都会预创建一个临时目录,用来到时候给 Multipart 用(即使实际没这玩意),因此每个经过网关的请求,都会在网关本地服务器上创建一个临时目录

而由于这两天请求非常多,创建了很多目录,把操作系统的文件 inode 占满了,使得后续的所有请求在执行到创建临时目录的方法时就报错了,因此所有请求都返回了 500。

inode

这边先介绍下 inode。

在类Unix文件系统中,文件的元数据存储的地方叫 inode,也就是文件元数据和文件的数据是分开存储的。

所谓的元数据指的是:文件的大小、创建时间、修改时间、权限等等,它也会占用磁盘空间。

因此操作系统分配给 inode 存储空间也是有限的,超过了限制就申请不了

所以有时候磁盘空间够的,但是文件还是无法创建,一种可能就是 inode 满了。

对了,对 Linux 这类系统而言,目录也是文件,一样的。

tmp

tmp 目录其实是有讲究的,临时目录。

理论上这个目录默认操作系统会有个叫 tmpwatch 的玩意去清理长时间无用的文件,一般会定时去清理。

企业微信截图_ec7db585-accc-4a53-a9c9-f7a262c88a33.png

而且重启系统的话,这里面的文件也会被清空。

至此,产生问题的原因应该非常清晰了。那如何解决?

解决

  1. 利用 tmpwatch 勤快点去清理,比如近几个小时没用的就直接干了。
  2. 不用 HttpMessageReader
  3. 定时重启
  4. 修改源码
  5. 看看后续版本

第一点不太好,因为有几率误删有用的文件。
第二点理论上用了 Spring 体系,这玩意好像不太好避免,先暂定。
第三点,太骚了,还是算了。
第四点,改起来不难,但是后面升级版本啥的不太方便

先试试第五点。

然后我就去 spring-cloud github 搜一搜,果然有 issue。

企业微信截图_4af2c66e-95c1-41e1-a3c8-e6a97cee4708.png

点进去一看,关联到另一个 issue,一看巧了,一模一样的错:

企业微信截图_ab387016-7ded-41c4-a5e5-097193744d7c.png

然后他圈了好几个老哥,有个叫 poutsma 老哥回答了他的问题:

我简单翻译下:这样的实现是为了解决一个安全问题,为了不创建一个固定的临时目录,如果使用固定目录会带来严重的安全隐患,然后也不能在退出后删除,因为这样会删除所有上传的文件。

简单来说:不是bug

而且目录本身占用的磁盘空间可以忽略不计,只有当大量上传的文件存储在那里时,目录才会开始占用空间。

通常,操作系统会在一段时间后清理临时文件。

咳咳,老哥看起来说的没毛病,但是它这个实现确实没有考虑到大量的目录会撑爆 inode 的情况!

然后有个叫 RekaDowney 的老哥也在下面说到:

企业微信截图_fa78992b-2d16-4faa-af27-4e9bc348d577.png

不论请求的 content-type 是不是 multipart/form-data 都会创建临时目录(这跟我前面说的一样,我这几天的几百万请求压根不是 multipart 相关的)。

然后 poutsma 老哥觉得没啥毛病:

企业微信截图_2818ee19-5c73-4940-ab7b-15034baf0824.png

你不用 multipart 创建这个目录也没影响,这目录里面又不会有文件,一个目录才占几 bytes,洒洒水啦。

额,把我洒哭了(3分钟线上服务不可用这是P几事故?)

企业微信截图_057a7609-67ee-4d34-b402-d5c1d839b498.png

不过后面马上 poutsma 老哥意识到这样好像也不好,因此他回复到:

企业微信截图_58069c90-8847-41a5-bee4-e60096e15314.png

对,他稍微妥协了下:我改我改,目录只在解析 multipart 数据的才会创建。

一天后 RekaDowney 老哥已经等不及了,他说:我已经算不清到底创建了多少个目录了,至少有 200 多w,然后我修改了源码,就调整了几行,只有在解析 multipart 的时候才创建,并且老哥还说要不要他提个 pr 贡献一波代码!

企业微信截图_a8446e0c-eed8-4bb3-835b-c663a58c0160.png

这位 RekaDowney 老哥的执行力还是强啊,自己动手丰衣足食,毕竟鬼知道这修复啥时候能发版。

好了,吃瓜暂时吃到这,最终我跟踪看到这个玩意是在 5.2.16 版本修复:

企业微信截图_351eba45-04e4-46f9-9086-efd2d69a653f.png

具体的替换方式是不无脑创建了,就创建一个随机的目录,然后保存这个引用,这样即使有很多 multipart 的请求,也不会创建很多目录了,整挺好:

企业微信截图_9f332d5a-8fb5-43c2-a175-d62ac34f3c27.png 企业微信截图_302b6e2b-0ec4-4ada-b142-304d554d123c.png

然后我翻阅了一下 5.2.16 那个版本的 release,咳咳:

企业微信截图_53b09394-80bc-43ff-8d7a-b8e5e94ee1a0.png

上面写着这玩意是 feat,不是 bug。

你们觉得呢?

程序员的倔强之,这不是bug:)

最终我的解决方案是:升级了 gateway 的版本到 5.2.16。


这其实是一个比较有含金量的线上排查经验,网关的 500 错误,涉及到源码又涉及到 linux node 知识点,在面试的时候要提出当时立马重启(表现出你经验老道,及时止损),然后如何定位到问题,一共想到了几种解决方案,最终通过 github issue 找到错误,然后解决问题。

按这样的思路说下来,真实性毋庸置疑,面试官也会觉得你很老道,有线上排查问题的能力,收了!

Comments
On this page
Spring Cloud Gateway 500 问题排查