持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
背景
最近在做微服务的集成,为了解决各服务间的rpc调用问题,使用到了openFeign,虽然是简单地在springboot项目中集成openFeign,但是里面其实还是有很多需要注意的点,下面就依次列举出来。
依赖
首先我们的springboot版本是2.7.3,spring cloud版本是2021.0.4,下面我们引入openFeign的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.4</version>
</dependency>
<!-- openFeign负载均衡由ribbon改为loadbalancer-->
<!-- 不加这个依赖会报错:No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer? -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.1.4</version>
</dependency>
使用openFeign
-
依赖引入后,我们需要使用openFeign的功能,首先需要在启动类上添加
@EnableFeignClients注解; -
其次,我们需要开始编写FeignClient:
// api接口,提供给客户端和服务端共用 public interface WalletApi { /** * 扣钱 * * @param amount * @return */ @PostMapping("/dedutMoney") Boolean deductMoney(@RequestParam("amount") long amount); } // account代表目标服务名称,需要从注册中心找 // contextId作为注入到ioc容器中bean的名称,要保持唯一性 // path对应服务端的@RequestMapping中的内容 @FeignClient(name = "account", contextId = "walletApi", path = "/wallet") public interface WalletApiClient extends WalletApi { } // 服务端代码 @Slf4j @RestController @RequestMapping("/wallet") public class WalletController implements WalletApi { @Override public Boolean deductMoney(long amount) { } }上面的代码把客户端和服务端抽象出了公共的一个接口,这样的话,我们就可以避免两边都要写requestMapping这一套,也无形之中让生产者和消费者直接达成了共识,避免很多无效沟通。
至此,openFeign的集成就算是告一段落了,项目中就可以正常使用了。下面我们继续openFeign的优化:
链接池
默认的情况下,openFeign使用的上是HttpURLConnection发起请求,具体代码可查看feign.Client.Default类实现,也就是说,openFeign每次需要创建一个新的请求,而不是使用的链接池,所以我们的需要替换掉这个默认的实现,改用一个有链接池的实现。
-
添加依赖
我们打算把
HttpURLConnection实现替换成okhttp的实现<!-- 替换默认的HttpURLConnection,改为okhttp,并添加链接池--> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>11.9.1</version> </dependency> -
通过javaConfig的方式把okhttp的实现引入进来
import feign.Feign; import feign.Logger; import feign.okhttp.OkHttpClient; import lombok.Getter; import lombok.Setter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; import org.springframework.cloud.openfeign.FeignAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.PreDestroy; import java.util.concurrent.TimeUnit; /** * @author zouwei * @className FeignConfig * @date: 2022/9/18 19:12 * @description: */ @Configuration @ConditionalOnClass(Feign.class) @AutoConfigureBefore(FeignAutoConfiguration.class) public class FeignConfig { @Getter @Setter @Configuration @ConfigurationProperties(prefix = "feign.okhttp") @ConditionalOnProperty(name = "feign.okhttp.enabled", havingValue = "true") protected static class OkHttpProperties { boolean followRedirects = true; // 链接超时时间,单位毫秒 int connectTimeout = 5000; boolean disableSslValidation = false; // 读超时,单位毫秒 int readTimeout = 5000; // 写超时,单位毫秒 int writeTimeout = 5000; // 是否自动重连 boolean retryOnConnectionFailure = true; // 最大空闲链接 int maxIdleConnections = 10; // 默认保持5分钟 long keepAliveDuration = 1000 * 60 * 5L; } /** * 配置okhttp以及对应的链接池 */ @Configuration( proxyBeanMethods = false ) @ConditionalOnClass({OkHttpClient.class}) @ConditionalOnMissingBean({okhttp3.OkHttpClient.class}) @ConditionalOnProperty({"feign.okhttp.enabled"}) protected static class OkHttpFeignConfiguration { private okhttp3.OkHttpClient okHttpClient; protected OkHttpFeignConfiguration() { } @Bean public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, OkHttpProperties properties, OkHttpClientConnectionPoolFactory connectionPoolFactory) { this.okHttpClient = httpClientFactory.createBuilder(properties.isDisableSslValidation()) // 链接超时时间 .connectTimeout(properties.getConnectTimeout(), TimeUnit.MILLISECONDS) // 是否禁用重定向 .followRedirects(properties.isFollowRedirects()) //设置读超时 .readTimeout(properties.getReadTimeout(), TimeUnit.MILLISECONDS) //设置写超时 .writeTimeout(properties.getWriteTimeout(), TimeUnit.MILLISECONDS) // 链接失败是否重试 .retryOnConnectionFailure(properties.isRetryOnConnectionFailure()) //链接池 .connectionPool(connectionPoolFactory.create(properties.getMaxIdleConnections(), properties.getKeepAliveDuration(), TimeUnit.MILLISECONDS)) .build(); return this.okHttpClient; } @PreDestroy public void destroy() { if (this.okHttpClient != null) { this.okHttpClient.dispatcher().executorService().shutdown(); this.okHttpClient.connectionPool().evictAll(); } } } }
-
更改配置
feign: # 不使用httpclient,改用okhttp httpclient: enabled: false okhttp: enabled: true # 是否禁用重定向 follow-redirects: true connect-timeout: 5000 # 链接失败是否重试 retry-on-connection-failure: true read-timeout: 5000 write-timeout: 5000 # 最大空闲数量 max-idle-connections: 5 # 生存时间 keep-alive-duration: 15000
这样我们就把openFeign的请求发送改造成链接池了,避免了每次请求都创建HttpURLConnection对象;
开启请求压缩功能
为了更好地减少请求发送的时间,我们可以针对请求数据进行压缩处理,openFeign也内置了压缩功能,不过需要我们自己开启:
feign:
# 开启压缩功能
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
配置超时时间
我们还可以给指定的FeignClient指定对应的超时时间,因为并不是所有的服务超时时间都是统一的,有些特殊的业务场景需要针对性地设置超时时间:
feign:
client:
config:
# 设置超时,囊括了okhttp的超时,okhttp属于真正执行的超时,openFeign属于服务间的超时
# 设置全局超时时间
default:
connectTimeout: 2000
readTimeout: 5000
# 针对特定contextId设置超时时间
walletApi:
connectTimeout: 1000
readTimeout: 2000
添加LoadBalancerCacheManager
在项目启动过程中,会出现警告:LoadBalancerCacheManager not available, returning delegate without caching.,说明LoadBalancerCacheManager没有开启,我们需要加入以下依赖并开启缓存:
<!-- 解决项目启动警告:LoadBalancerCacheManager not available, returning delegate without caching.-->
<!-- 如果注册中心有自己的缓存,那么就可以禁用loadbalancer的缓存-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.1</version>
</dependency>
<!-- 解决项目启动警告:Spring Cloud LoadBalancer is currently working with the default cache. You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
另外,我们还需要通过配置开启缓存:
spring:
cloud:
loadbalancer:
cache:
# 开启缓存,如果注册中心有自己的缓存,那么就可以禁用loadbalancer的缓存
enabled: true
# 过期时间10s
ttl: 10
# 容量256M
capacity: 256
caffeine:
# initialCapacity=[integer]: sets Caffeine.initialCapacity.
# maximumSize=[long]: sets Caffeine.maximumSize.
# maximumWeight=[long]: sets Caffeine.maximumWeight.
# expireAfterAccess=[duration]: sets Caffeine.expireAfterAccess(long, java.util.concurrent.TimeUnit).
# expireAfterWrite=[duration]: sets Caffeine.expireAfterWrite(long, java.util.concurrent.TimeUnit).
# refreshAfterWrite=[duration]: sets Caffeine.refreshAfterWrite(long, java.util.concurrent.TimeUnit).
# weakKeys: sets Caffeine.weakKeys().
# weakValues: sets Caffeine.weakValues().
# softValues: sets Caffeine.softValues().
# recordStats: sets Caffeine.recordStats().
# initialCapacity初始化键值对的数量
spec: initialCapacity=500,expireAfterWrite=5s
至此,我们基本上已经把OpenFeign的几个优化点列举完毕,感兴趣的小伙伴也可以自己尝试一下。