我们项目是微服务架构,但项目前期并没有接入注册中心来管理服务间调用。现阶段使用Feign(httpclient)来进行服务间的调用。
测试环境已经进行了功能的验证(包括bug的修复),并未发现异常,准备同步到生产环境。
项目上线后,发现服务异常,查询日志如下:
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7dd6d974]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7dd6d974]
2020-12-31 18:12:05.416 INFO 20454 --- [nio-8081-exec-1] com.zhundian.operator.aop.WebLogAop : ### <<<------------------------- Log End ------------------------->>>
2020-12-31 18:12:05.426 ERROR 20454 --- [nio-8081-exec-1] c.z.o.exception.GlobalExceptionHandler : feign异常:
feign.codec.DecodeException: Error while extracting response for type [class com.zhundian.member.common.R] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens; nested exception is com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens
at [Source: (PushbackInputStream); line: 1, column: 2]
at feign.SynchronousMethodHandler.decode(SynchronousMethodHandler.java:182)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:142)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:80)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)
查看日志,可知是json解析异常。
此时,从项目背景可知,此问题应该属于测试与生产,在环境上有区别(题外话,这也就是为什么说docker是个好东西,可惜还没有在项目中进行使用)。
所以从环境因素上去下手:
所有数据库结构均来自测试环境同步,表结构完全相同,从报错也可以排除这个原因。
这里因为报错日志中有feign,所以,重点关注feign的配置。首先,查看与测试环境是否不同,有哪些差别等。
最后发现并无差别。
但一定是跟服务间调用有关系,所以,让部分同学去查询相关资料。
ng其实是我最早怀疑的对象,因为ng配置并不会经由我们的手,而且作为请求的出入口,应该重点关注。
对比下来,果然有区别:
# 开启gzip
gzip off;
# 开启gzip
gzip on;
我只贴出了明显的区别,测试环境没有开启Gzip,生产环境是开启的。而Feign默认是不开启的。
在Ng中,开启了Gzip,但Feign并没有开启。
Ng保持和测试环境一致,将Gzip关闭掉。
仔细了解了Gzip及Feign的相关配置,Ng可以开启Gzip并修改Feign的配置来解决这个问题。
# 开启gzip压缩服务
gzip on;
# gzip压缩是要申请临时内存空间的,假设前提是压缩后大小是小于等于压缩前的。
# 例如,如果原始文件大小为10K,那么它超过了8K,所以分配的内存是8 * 2 = 16K;再例如,
# 原始文件大小为18K,很明显16K也是不够的,那么按照 8 * 2 * 2 = 32K的大小申请内存。
# 如果没有设置,默认值是申请跟原始数据相同大小的内存空间去存储gzip压缩结果。
# 设置系统获取几个单位的缓存用于存储gzip的压缩结果数据流。
# 例如 4 4k 代表以4k为单位,按照原始数据大小以4k为单位的4倍申请内存。
# 4 8k 代表以8k为单位,按照原始数据大小以8k为单位的4倍申请内存。
# 如果没有设置,默认值是申请跟原始数据相同大小的内存空间去存储gzip压缩结果。
gzip_buffers 2 8k;
# nginx对于静态文件的处理模块。
# 该模块可以读取预先压缩的gz文件,这样可以减少每次请求进行gzip压缩的CPU资源消耗。
# 该模块启用后,nginx首先检查是否存在请求静态文件的gz结尾的文件,如果有则直接返回该gz文件内容。
# 为了要兼容不支持gzip的浏览器,启用gzip_static模块就必须同时保留原始静态文件和gz文件。
# 这样的话,在有大量静态文件的情况下,将会大大增加磁盘空间。我们可以利用nginx的反向代理功能实现只保留gz文件。
gzip_static on|off
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip压缩基于的http协议版本,默认就是HTTP 1.1
gzip_http_version 1.1;
# gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明
gzip_comp_level 2;
# 需要进行gzip压缩的Content-Type的Header的类型。建议js、text、css、xml、json都要进行压缩;
# 图片就没必要了,gif、jpge文件已经压缩得很好了,就算再压,效果也不好,而且还耗费cpu。
# javascript有多种形式。其中的值可以在 mime.types 文件中找到。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
# 默认值:off
# Nginx作为反向代理的时候启用,开启或者关闭后端服务器返回的结果,匹配的前提是后端服务器必须要返回包含"Via"的 header头。
# off - 关闭所有的代理结果数据的压缩
# expired - 启用压缩,如果header头中包含 "Expires" 头信息
# no-cache - 启用压缩,如果header头中包含 "Cache-Control:no-cache" 头信息
# no-store - 启用压缩,如果header头中包含 "Cache-Control:no-store" 头信息
# private - 启用压缩,如果header头中包含 "Cache-Control:private" 头信息
# no_last_modified - 启用压缩,如果header头中不包含 "Last-Modified" 头信息
# no_etag - 启用压缩 ,如果header头中不包含 "ETag" 头信息
# auth - 启用压缩 , 如果header头中包含 "Authorization" 头信息
# any - 无条件启用压缩
gzip_proxied [off|expired|no-cache|no-store|private|no_last_modified|no_etag|auth|any] ...
# 是否在http header中添加Vary: Accept-Encoding,建议开启
# 和http头有关系,加个vary头,给代理服务器用的,有的浏览器支持压缩,
# 有的不支持,所以避免浪费不支持的也压缩,所以根据客户端的HTTP头来判断,是否需要压缩
gzip_vary on;
# 禁用IE 6 gzip
gzip_disable "MSIE [1-6]\.";
以上内容来自(我支持原创):Nginx Gzip模块启用和配置指令详解
大家有兴趣可以看下这篇文章,讲得比较详细。
Gzip的Feign配置
feign:
compression:
request:
# 开启请求压缩
enabled: true
# 配置压缩的 MIME TYPE
mime-types: text/xml,application/xml,application/json
# 配置压缩数据大小的下限
min-request-size: 2048
response:
# 开启响应压缩
enabled: true
对于此次事故,给大家造成的困扰,深感抱歉。
以下为事故分析:
问题发生状况:接口间通过application/json,进行通讯时,会报json解析异常
问题分析:
1.有些接口的数据量比较大,需要进行json格式的数据压缩,需要在nginx上配置gzip压缩,gzip_types application/json
2.在nginx中,gzip压缩的配置,只能进行全局的配置
3.当nginx中,配置gzip压缩时,如果 Feign(接口间调用的工具)不配置gzip时,会解析不了json数据
解决方案:
1.关闭nginx中的gzip压缩配置gzip_types application/json(保证当前问题解决)
2.后续会在zuul网关,进行定制化gzip的压缩改造(满足计划排程的需求)
对给大家造成的不便感到非常抱歉,
后续对于全局性的配置修改,会提前2-3天通知各个应用方,当进行完充分测试后,再进行全局性的配置修改。