Spring Cloud Gateway 500 问题排查

Sherwin.Wei Lv7

提供一个线上 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 找到错误,然后解决问题。

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

目录
Spring Cloud Gateway 500 问题排查