前言
在前文记一次Nacos容器升级调优,我们完成所有服务器容器化部署并且稳定上线,但是压测阶段我们发现服务间调用的性能略差。对此我们不得不对服务器间WebService
客户端openFeign
进行调优。
可能读者问到,为什么你们的服务通信要用openFeign
而不是RPC
呢?针对笔者开发的b端
系统来说,大多数请求都是基于http
暴露给前端使用的。而且因为业务原因,很多交互需要基于http进行通信,这就使得很多服务间通信的接口只能基于那些http
接口进行通信。
而且这种做法也能避免编写重复的代码,所以我们就索性使用openFeign
来实现服务间的通信。
如何提升Feign的执行效率
笔者查阅网上资料了解到,我们实际上可以通过自动装配修改feign
底层使用的客户端框架。这一点我们可以在源码中看到答案。
代码如下,可以看到FeignAutoConfiguration
这个自动装配类,会扫描当前包中是否存在ApacheHttpClient
或者OkHttpClient(或者配置中配置了feign.okhttp.enabled为true)
。即可实现底层客户端的修改。
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
public class FeignAutoConfiguration {
@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(CloseableHttpClient.class)
//该属性默认为true,所以只要导入ApacheHttpClient类即可完成自动装配
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration {
.......
}
@Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
protected static class OkHttpFeignConfiguration {
}
}
关于HTTP协议一些概念
在切换客户端进行压测调整工具之前,我们必须对HTTP
一些知识进行回顾补充:
Socket详解
socket即为网络通信套接字,一个socket
是由源ip+源端口号和目的ip+目的端口号
四元组构成。它是介于传输层和要用层之间的一个抽象层。所谓抽象层,我们可以理解为一组接口,socket
用到了一种类似于外观模式
的方式,将TCP、UDP
等协议簇工作细节对上层屏蔽,使得我们要用到这些协议只需按需将参数传给Socket
,让Socket
为我们完成数据组织传输即可。
而我们常说的socket
连接就是长连接,这就意味着除非客户端主动断开,否则这个连接就不会主动结束。
HTTP长连接
所谓HTTP长连接其实说的是TCP长连接,若HTTP使用的是TCP长连接,这就意味一次HTTP会话结束不会将当前HTTP所使用的TCP连接关闭,虽然有一点的性能开销,但下次需要建立连接时就无需再次进行TCP三次握手的建立连接过程,通信效率就相对高很多。
如下图所示,请求头中带有Connection: keep-alive
,就说明这个连接是长连接的。
了解openFeign底层可以使用的三大客户端
HttpURLConnection
这个是openFeign
默认使用的客户端,这个客户端仅仅是对HTTP
请求做了封装并没有做任何优化处理,它的通信过程很简单,通过outputStream
将数据写到流中,即使写完也不会立即发送,必须等客户端将流关闭之后才会从缓存区的内容生成HTTP
正文。
HttpURLConnection
还有一个特点就是它并没有什么超时机制,这可能会导致在网络故障的情况下出现请求僵死的情况。
HttpClient
HttpClient
相比于前者做到的池化,通过池化技术实现连接持久化,使用上也比前者更加方便,但它并没有对相同的请求进行缓存。
okHttp
okHttp
同样也做到池化,而且相较于HttpClient
它还做到的响应缓存,即相同的请求结果它会缓存起来。
okHttp不仅仅实现了池化,还做到了公用socket
,即相同的ip和端口会重用同一个socket
,这就意味着每次建立连接不仅无需进行TCP三次握手
,连建立请求socket
的步骤都省略了。
实践——基于压测了解三大客户端性能优劣
基于HttpURLConnection
接下来我们就基于jmeter
来压测不同客户端的性能,首先介绍一下笔者的机器:
1. 处理器:11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz 2.30 GHz
2. 16.0 GB (15.6 GB 可用)
以及笔者手动设置了feign
的超时时间
feign:
client:
config:
default:
connectTimeout: 3000
readTimeout: 3000
我们首先编写一个调用feign
的代码,可以看到笔者在order
服务中调用accout
服务的http
接口:
@GetMapping("testFeign")
public ResultData<String> testFeign() {
orderService.testFeign();
return ResultData.success("success");
}
testFeign
的具体内容如下:
@Override
public void testFeign() {
productFeign.getByCode("P001");
accountFeign.getByCode("zsy");
}
再看看笔者的压测参数,创建1000
个线程,分10
次发送
笔者基于这个参数,进行多组测试,找了一条平均的实验结果:
基于httpclient
从上文源码中我们知道要想修改为httpclient
客户端,包中必须得有ApacheHttpClient
,所以我们在模块引入ApacheHttpClient
相关的依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
启动项目时,可以看到自动装配过程有引入ApacheHttpClient
,说明配置生效了。
若读者对此还有疑问,可以可以在feign调用的代码段打个debug,不断深入调试,可以看到最终的执行会走到ApacheHttpClient
确定成功装配之后,我们就可以进行压测了,同样的压测过程,数据如下,可以看到尽管我们对客户端进行了池化,但是性能相对于默认的HttpURLConnection
还要差。
查阅底层池化配置确实没有问题,查阅网上说法是由于其api
众多,是我们很难再不破坏兼容性的情况下对其进行扩展。所以,Android
团队对提升和优化httpclient
积极性并不高,android5.0
被废弃,6.0
逐渐删除。所以在性能方便可能也没有做出很大的优化。
基于okhttp3
okhttp3配置步骤和前者差不多,首先引入依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
然后在feign中增加okhttp.enabled为true的配置
feign:
client:
config:
default:
connectTimeout: 3000
readTimeout: 3000
okhttp:
enabled: true
然后就可以进行压测了,最后压测结果如下,可以看出okhttp
性能最优,所以笔者就将feign
底层使用的客户端调整为okhttp3
小结
本次调优过程其实不是很困难,只需注意切换客户端,根据业务要求设置实际参数(例如笔者的项目超时时间统一设置为3s)
,通过debug
确认是否生效。
然后结合压测工具进行多次压测实验,避免偶然性,从中找出性能最好,准确率最高的样本进行比对。
参考文献
SpringCloud Alibaba微服务实战二十三 - Feign 性能调优
[JM_03]JMeter性能测试基础实战之QPS检测过程解析