一、背景
java服务调用另一个java服务的接口,大多是通过服务注册中心,使用java feign client 去调用http接口。
写法如下:
其本质是一个http请求,入参和返回值会自动序列化。
本文分享一个因为序列化失败而报错的排查案例。
二、报错
{“traceId”:null,“code”:10500724010,“msg”:“获取资源详情异常:DataCenterServiceClient#findPersonResourceByUserIdV4(Long,String,Boolean,Boolean,String,Integer,Integer,Boolean,Integer,Integer,Integer,String) failed and no fallback available.”,“description”:null,“errors”:null}
第一反应是对方的服务挂了,所以feign调用出错。
但是,部分用户调用失败,注意是部分。
当我们拿到报错用户的账户进行登录,该错误在本地复现了;也就是说,这个错误是必现的。
所以,我们得出的结论是,脏数据导致查询报错。
但是,问题来了(凡事就怕但是),怎么个脏数据呢?
三、第一次调试过程
因为之前的程序写法,调用别的java服务接口前,并不会打印请求入参,特别是查询类的接口。
一般地,如果是操作类的接口,我们要求程序应该输出请求和返回日志。
所以,因为只有生产环境的数据能够复现,所以我们部署了一套灰度环境。
其次,在调用外部服务的接口前,增加了打印日志,输出请求参数的内容。
示例:
有了请求值,就可以手动调用三方服务的接口。
出乎外料之外的是,手动调用三方服务的接口,返回正常,并不报错。
所以,我们必须打印feign调用的返回值。
于是,我们继续排查。。。
四、打开feign调用的日志
1、FeignConfig
建议所有的spring boot程序框架都实例化该类。
// 配置 Feign 日志级别@ConfigurationpublicclassFeignConfig{@BeanLogger.LevelfeignLoggerLevel(){// 可选:NONE, BASIC, HEADERS, FULLreturnLogger.Level.FULL;}}2、配置
# application.yml# 日志级别设为 DEBUG 或 FULLlogging: level:# 你的 Feign Client 接口全限定名com.xxx.xxx.client.DataCenterServiceClient: debug如此,便能得到这个类下的所有方法的出入参信息。
原来feign调用报错了,是一个内部错误:
feign.codec.DecodeException
难怪被吞掉了,打印异常的时候,只能看到failed and no fallback available
五、解决问题
通过feign调用日志,我们得知是其中的字段fileLength,长度越界了。
对方是Long类型定义的一个字段,而我们接收方却定义为Integer。
1、三方服务的接口返回值定义
2、接收方的返回值定义
于是乎,出现了数据越界的错误,feign调用的时候,反序列化出错。
六、总结
本文通过打印feignClient调用的出入参信息,得以知晓反序列化出错导致接口调用报错。
也提醒我们,feign调用的日志输出,对于解决线上程序报错的重要性,程序应该默认实例化Logger.Level
其次,因为feign调用的日志输出量非常大,平常是关闭状态,等到要跟踪生产的问题将排得上用场。
最后,定义字段的时候,整型还是长整型,真的是个坑。测试也很难看出来,所以在生产进行测试的时候,不要只以自己的数据为测试结果。
所以,用户上线后,选取部分典型数据进行灰度测试,非常必要。