Springboot与Feign的Decoder
导读
工作中有些时候依赖方提供的Http Api可能是异构语言开发的,如php,我们经常遇到这样一件尴尬的事情它返回的内容是json的形式但Content-Type
是text/html;charset=utf-8
。如果我们不做任何的处理,我们在上层只能拿到响应的String再做json转对象的操作(Java中需要识别Content-Type
是application/json
才能自动做json转对象操作),使用体验很不优化,而且也不够面向对象,特别是在Feign接口的定义时,返回值处的可读性极差。为了优雅的处理类似的情况,我们就需要借助Feign提供的Decoder组件。但Decoder定义不规范时会对高并发场景下的接口访问造成极大的影响。今天我们详细分析下这种情况出现的场景及背后的原因。
案例
Feign接口定义
@FeignClient(
name = "TestFeignWithDecoder",
url = "http://localhost:9000",
configuration = TestFeignWithDecoder.ClientConfig.class)
public interface TestFeignWithDecoder {
@GetMapping(value = "/open/api/user")
String test();
class ClientConfig {
//见decoder定义
}
class TextHtmlMessageConverter extends MappingJackson2HttpMessageConverter {
public TextHtmlMessageConverter() {
List<MediaType> mediaTypes = new ArrayList<>(1);
mediaTypes.add(MediaType.valueOf("text/html;charset=UTF-8"));
setSupportedMediaTypes(mediaTypes);
}
}
}
没有类似场景接口,可以自己使用moco在本地或者服务器上进行mock【moco使用简单教程】
Decoder定义
正确定义
@Bean
Decoder feignDecoder() {
TextHtmlMessageConverter textHtmlMessageConverter = new TextHtmlMessageConverter();
HttpMessageConverters httpMessageConverters = new HttpMessageConverters(textHtmlMessageConverter);
return new SpringDecoder(() -> httpMessageConverters);
}
错误定义
@Bean
Decoder feignDecoder() {
TextHtmlMessageConverter textHtmlMessageConverter = new TextHtmlMessageConverter();
return new SpringDecoder(() -> new HttpMessageConverters(textHtmlMessageConverter));
}
压测处理
负载工具:wrk 【wrk简明教程】
执行命令:wrk -t4 -c100 -d60 --latency http://localhost/api
命令解释:
- -t 使用线程数
- -c 使用并发连接数
- -d 压测持续时间
- --latency 压测结束后打印延迟统计时间
目标机器:4c4g
压测参数 | 不用decoder | 错误定义decoder | 正确定义decoder |
---|---|---|---|
4线程100并发连接30s | 100 | 58 | 227 |
4线程200并发连接60s | 910 | 143 | 876 |
结果分析
根据业务需要可以使用自定义decoder,只要使用正确,不会对性能有较大影响,使用错误会有巨大影响,不过如果是低并发场景,影响较小。如果是正确定义,对性能还有很大影响,需要考虑此处使用是否合理。
原因分析
代码return new SpringDecoder(() -> new HttpMessageConverters(textHtmlMessageConverter));
此处使用的是Java8中函数式编程,传入的是一个函数,每次请求调用decode方法时都会调用此方法创建一个HttpMessageConverters
对象。而在新建此对象的过程中会执行Jackson2ObjectMapperBuilder.registerWellKnownModulesIfAvailable
,会去类路径下查询相关的类,有就添加相关的解析器,比如我们引入了om.google.gson.Gson包,就会添加GsonHttpMessageConverter转换器。常见的查看类路径下是否存在某个类,只需要使用类加载器加载,能加载就存在。最简单的方式就是调用Class.forName(xxx)
。这也是反射的起手式。这里也的确进行了很多反射操作,大量占用cpu的操作所以严重影响性能。
总结
Feign是声明式的Http客户端,对外部调用进行了良好的面向对象封装。本文中出现的问题可以通过一些手段规避
- 公司有统一的开发规范,遵循同样的规约开发,能减少很多类似的坑
- 如果有类似问题,是否有公共的已经经过良好验证的工具包或者公司二方包解决这种大家都可能遇到的问题
- 遇到类似问题,没有通用解决方案,需要自己开发,必须对引入或者不引入该技术做性能对比,有明确的数据支撑性能开销可接受
对技术心怀敬畏,知其然也要知其所以然是用好技术的关键。会用是Api工程师,用好才是开发工程师。
转载自:https://juejin.cn/post/7187433472267386941