1 描述
如果我們直接使用SpringCloud Feign進行服務間調用的時候,http組件使用的是JDK的HttpURLConnection,每次請求都會新建一個連接,沒有使用線程池復用。具體的可以從源碼進行分析
2 源碼分析
我們在分析源碼很難找到入口,不知道從何開始入手,我們在分析SpringCloud feign的時候可用在配置文件下面我講一下個人的思路。
1 首先我點擊@EnableFeignClients 看一下這個注解在哪個資源路徑下
如下圖所示:
2 找到服務啟動加載的配置文件
3 因為feign底層的負載均衡是基于Ribbon的所以很快就找到了FeignRibbonClientAutoConfiguration.java 這個類
- @ConditionalOnClass({ ILoadBalancer.class, Feign.class })
- @Configuration
- @AutoConfigureBefore(FeignAutoConfiguration.class)
- @EnableConfigurationProperties({ FeignHttpClientProperties.class })
- //Order is important here, last should be the default, first should be optional
- // see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
- @Import({ HttpClientFeignLoadBalancedConfiguration.class,
- OkHttpFeignLoadBalancedConfiguration.class,
- DefaultFeignLoadBalancedConfiguration.class })
- public class FeignRibbonClientAutoConfiguration {
首先我們從這三個類進行分析,從名字上來看我為了驗證沒有特殊配置,feign底層走的是不是默認的DefaultFeignLoadBalancedConfiguration.class
OkHttpFeignLoadBalancedConfiguration.class
HttpClientFeignLoadBalancedConfiguration.class
DefaultFeignLoadBalancedConfiguration.class
DefaultFeignLoadBalancedConfiguration.class
- @Configuration
- class DefaultFeignLoadBalancedConfiguration {
- @Bean
- @ConditionalOnMissingBean
- public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
- SpringClientFactory clientFactory) {
- return new LoadBalancerFeignClient(new Client.Default(null, null),
- cachingFactory, clientFactory);
- }
- }
從上面代碼可知每次請求過來都會創建一個新的client,具體的源碼演示有興趣的可以深入研究,在這里不是我們所研究的重點。
OkHttpFeignLoadBalancedConfiguration.class
- @Configuration
- @ConditionalOnClass(OkHttpClient.class)
- @ConditionalOnProperty(value = "feign.okhttp.enabled")
- class OkHttpFeignLoadBalancedConfiguration {
- @Configuration
- @ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
- protected static class OkHttpFeignConfiguration {
- private okhttp3.OkHttpClient okHttpClient;
- @Bean
- @ConditionalOnMissingBean(ConnectionPool.class)
- public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
- OkHttpClientConnectionPoolFactory connectionPoolFactory) {
- Integer maxTotalConnections = httpClientProperties.getMaxConnections();
- Long timeToLive = httpClientProperties.getTimeToLive();
- TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
- return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
- }
- @Bean
- public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
- ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
- Boolean followRedirects = httpClientProperties.isFollowRedirects();
- Integer connectTimeout = httpClientProperties.getConnectionTimeout();
- this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()).
- connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
- followRedirects(followRedirects).
- connectionPool(connectionPool).build();
- return this.okHttpClient;
- }
- @PreDestroy
- public void destroy() {
- if(okHttpClient != null) {
- okHttpClient.dispatcher().executorService().shutdown();
- okHttpClient.connectionPool().evictAll();
- }
- }
- }
- @Bean
- @ConditionalOnMissingBean(Client.class)
- public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
- SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
- OkHttpClient delegate = new OkHttpClient(okHttpClient);
- return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
- }
- }
從源碼可以看出
1 該類是個配置類,當引入OkHttpClient.Class會加載
- client方法中可以看出會返回一個http連接池的client
- HttpClientFeignLoadBalancedConfiguration
- @Configuration
- @ConditionalOnClass(ApacheHttpClient.class)
- @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
- class HttpClientFeignLoadBalancedConfiguration {
這個類和OkHttpFeignLoadBalancedConfiguration原理類型
使用OKHttp替代默認的JDK的HttpURLConnection
使用appach httpclient使用教程類似
使用方法
1 pom
- <dependency>
- <groupId>io.github.openfeign</groupId>
- <artifactId>feign-okhttp</artifactId>
- </dependency>
2 Yml文件
- feign:
- okhttp:
- enabled: true
3 自定義連接池
可以通過代碼進行配置,也可以通過yml配置
- @Configuration
- @ConditionalOnClass(Feign.class)
- @AutoConfigureBefore(FeignAutoConfiguration.class)
- public class FeignOkHttpConfig {
- @Bean
- public okhttp3.OkHttpClient okHttpClient(){
- return new okhttp3.OkHttpClient.Builder()
- .readTimeout(60,TimeUnit.SECONDS)
- .connectTimeout(60,TimeUnit.SECONDS)
- .connectionPool(new ConnectionPool())
- .build();
- }
- }
驗證
默認的Feign處理會走到如下位置;
位置處于如下圖所示
- @Override
- public Response execute(Request request, Options options) throws IOException {
- HttpURLConnection connection = convertAndSend(request, options);
- return convertResponse(connection).toBuilder().request(request).build();
- }
走okhttp客戶端會走如下代碼
具體位置如下圖所示:
- @Override public Response execute() throws IOException {
- synchronized (this) {
- if (executed) throw new IllegalStateException("Already Executed");
- executed = true;
- }
- captureCallStackTrace();
- try {
- client.dispatcher().executed(this);
- Response result = getResponseWithInterceptorChain();
- if (result == null) throw new IOException("Canceled");
- return result;
- } finally {
- client.dispatcher().finished(this);
- }
- }
驗證結果
如下所示:
彩蛋
okhttp客戶端會走的代碼可以看出來okhttp有synchronized鎖線程安全的那默認的是否是線程安全的呢 有待去驗證。
追加
如果發現配置的超時時間無效,可以添加以下配置,因為讀取超時配置的時候沒有讀取上面的okhttp的配置參數,而是從Request中讀取。
具體配置如下所示:
- @Bean
- public Request.Options options(){
- return new Request.Options(60000,60000);
- }
補充:springCloud feign使用/優化總結
基于springCloud Dalston.SR3版本
1.當接口參數是多個的時候 需要指定@RequestParam 中的value來明確一下。
- /**
- * 用戶互掃
- * @param uid 被掃人ID
- * @param userId 當前用戶ID
- * @return
- */
- @PostMapping(REQ_URL_PRE + "/qrCodeReturnUser")
- UserQrCode qrCodeReturnUser(@RequestParam("uid") String uid,@RequestParam("userId") Integer userId);
2.接口參數為對象的時候 需要使用@RequestBody注解 并采用POST方式。
3.如果接口是簡單的數組/列表參數 這里需要使用Get請求才行
- @GetMapping(REQ_URL_PRE + "/getUserLevels")
- Map<Integer, UserLevel> getUserLevels(@RequestParam("userIds") List<Integer> userIds);
4.直接可以在@FeignClient中配置降級處理方式 對于一些不重要的業務 自定義處理很有幫助
- @FeignClient(value = "cloud-user", fallback = IUsers.UsersFallback.class)
5.feign默認只有HystrixBadRequestException異常不會走熔斷,其它任何異常都會進入熔斷,需要重新實現一下ErrorDecoder包裝業務異常
示例:https://github.com/peachyy/feign-support
6. feign HTTP請求方式選擇
feign默認使用的是基于JDK提供的URLConnection調用HTTP接口,不具備連接池。所以資源開銷上有點影響,經測試JDK的URLConnection比Apache HttpClient快很多倍。但是Apache HttpClient和okhttp都支持配置連接池功能。具體選擇需要權衡
7.默認不啟用hystrix 需要手動指定feign.hystrix.enabled=true 開啟熔斷
8.啟用壓縮也是一種有效的優化方式
- feign.compression.request.enabled=true
- feign.compression.response.enabled=true
- feign.compression.request.mime-types=text/xml,application/xml,application/json
9.參數相關調優
hystrix線程數設置
設置參數hystrix.threadpool.default.coreSize 來指定熔斷隔離的線程數 這個數需要調優,經測試 線程數我們設置為和提供方的容器線程差不多,吞吐量高許多。
第一次訪問服務出錯的問題
啟用Hystrix后,很多服務當第一次訪問的時候都會失敗 是因為初始化負載均衡一系列操作已經超出了超時時間了 默認的超時時間為1S,設置參數超時時間hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=30000 可解決這個問題。
負載均衡參數設置
設置了Hystrix的超時參數會 還需設置一下ribbon的相關參數 這些參數和Hystrix的超時參數有一定的邏輯關系
請求處理的超時時間 ribbon.ReadTimeout=120000
請求連接的超時時間 ribbon.ConnectTimeout=30000
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持我們。如有錯誤或未考慮完全的地方,望不吝賜教。
原文鏈接:https://blog.csdn.net/qq_33249725/article/details/88532160