网关乱码问题排查纪实

乱码问题出现
下午时分,接到业务方报告,他们在调用网关进行接口测试时,发现前端传进来的中文参数经过网关转发后到后端时变成了乱码...



由于之前也出现业务方收到乱码参数但最后是业务方接口自身的问题,加上线上环境正常,所以也怀疑是业务方自身的原因,就让他们先确认直接调用接口是否有问题,再来确认网关是否有异常。
时间来到第二天早上,业务方确认直接调用接口可以正常收到中文参数,那么这次问题肯定出在网关转发上。

网关侧排查过程
由于前一天刚升级了一个框架版本,只部署了测试和预发,还没有部署到线上,所以怀疑乱码问题是这个引入,于是马上回滚了版本。可以回滚后进行验证,乱码问题依旧。进一步排查配置文件,发现没有明显的字符集相关的配置,这就让人很郁闷了,代码逻辑一致,配置也一样,为什么线上环境正常,而测试和预发环境出现乱码呢?
因为目前网关平台已经部署在 k8s 中,猜测是不是跟镜像不同有关,可是经过确认,各个环境都使用相同的镜像,这条思路也不通。没啥好办法,因为是必现问题,只能 debug 了。
可是更诡异的事情出现了,本地进行 debug,发下中文参数可以正确展示,而远程 debug 发现测试环境确实是乱码,如下图所示:


于是只能继续查看IOUtils.toString()
方法逻辑,发现如果不传字符集参数,则使用默认字符集,如下图所示:

其实定位到这里,基本可以确定乱码是 IOUtils.toString() 时使用了默认字符集导致,可是为什么本地和线上都是正常,而测试和预发环境却是乱码呢?
于是继续进入Charset.defaultCharset()
,发现它其实先尝试获取 file.encoding
系统属性指定的字符集,如果获取不到,则使用 UTF-8,如下图所示:

也就是说如果没有指定 file.encoding
系统属性,那么最后都是使用 utf-8 才对,那就不会出现中文乱码才对,可事实并不是这样,仍然无法解释。看了 debug 时候 file.encoding
系统属性,发现确实是不一样:


可是启动参数并没有显示设置file.encoding
系统属性,那么这个属性是从哪里初始化的呢?这里可以使用下面这行命令查看 java 的默认设置(没有显示指定的情况下从系统识别出):
那么本地和测试环境的file.encoding
属性分别如下:


那么到目前为止,本地环境和测试环境关于乱码的问题得到解释,那就是系统默认字符集不一样,本地为 utf-8,中文显示正常,测试环境为 ANSI_X3.4-1968(预发环境也一样),中文显示乱码。
可是还有一个问题,线上环境使用相同的镜像,那么默认系统属性也应该是 ANSI_X3.4-1968 ,那为什么线上环境展示正常呢?回想前面提及如果显示指定file.encoding
系统属性,那么就以指定的为准,所以赶紧到启动参数确认,顿时泪流满面,线上环境果然显示指定了file.encoding=UTF-8
:

至此,乱码问题真相大白,那就是线上环境显示指定了字符集,而其他环境没有指定,使用系统默认字符集,因此只有线上环境正常,而其他环境则显示乱码。
细心的读者可能会有疑问,对于网关来说,使用率比较高,难道这么久都没有发现乱码问题么?
其实这也是自己困惑的问题,这个问题的解释是,我们最近将网关部署到 k8s 平台(原来部署在云平台 ec2 实例上),部署到 k8s 使用的 java 基础镜像,默认字符集就是 ANSI_X3.4-1968 ,而部署在云平台 ec2 实例上使用的系统镜像默认字符集是 UTF-8,加上线上环境显示指定了file.encoding
系统属性,所以才导致迁移前使用正常,迁移后线上环境也使用正常,而测试/预发环境出现乱码的情况。
乱码问题解决
从前面的分析来看,有以下解决方案:
增加其他环境的 JVM 启动参数 -Dfile.encoding=utf-8
或者在读取字节流时显示指定字符集 IOUtils.toString(inputStream, "utf-8")
思考总结
其实乱码问题经常遇到,很多时候都是由于读取或者写入的时候没有指定字符集导致,所以大家以后在读取或者写入时候切记要显示指定想要的字符集,以免重蹈上面的覆辙。
另外一个教训就是,像时区,字符集这种非常重要的默认参数,最好在 jvm 启动参数中显示指定,否则也会出现使用系统默认属性不符合预期的情况。
大家有遇到类似乱码问题么,欢迎留言探讨~
版权声明: 本文为 InfoQ 作者【小江】的原创文章。
原文链接:【http://xie.infoq.cn/article/c133f16a7d2b041e56d9d6ff6】。文章转载请联系作者。
评论