OpenFeign 全称 Spring Cloud OpenFeign,它是 Spring 官方推出的一种声明式服务调用与负载均衡组件,它的出现就是为了替代进入停更维护状态的Netflix Feign。 Spring Cloud openfeign对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Nacos,从而使得Feign的使用更加方便。 Feign使用http远程调用方法就好像调用本地的方法,感觉不到是远程方法。他的使用就和直接写控制类那样,暴露接口提供调用,我们只需要编写调用接口+@FeignClient注解,在使用这个api的时候,只需要定义好方法,到时候调用这个方法就可以了。这种服务之间的调用使用起来是非常的方便,体验也比较好。
在平时开发的SpringBoot项目中,像这种rest服务是如何被调用的呢?通常下是使用Httpclient、Okhttp、HttpURLConnection、RestTemplate,其中RestTemplate是最常见的。之前在 Nacos配置中心,使用的是RestTemplate。
使用OpenFeign组件需要引入客户端依赖:
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-openfeignartifactId>
- dependency>
通过OpenFeign远程调用服务的时候,比RestTemplate更加方便,跟编写Controller接口是差不多的。 需要写上@FeignClient注解,里面配置微服务名字和rest的@RequestMapping("/api/store")。
- @FeignClient(name = "service-store", path = "/api/store")
- public interface StoreFeignService {
-
- // 声明要调用的Rest
- @GetMapping("/{id}")
- Map
getStoreNum(@PathVariable String id); - }
注意: 需要在启动类中写上注解 @EnableFeignClients
- @Autowired
- private StoreFeignService storeFeignService;
-
- // 在业务中直接调用
- storeFeignService.getStoreNum(uid);
Feign 提供了很多的扩展机制,让用户可以更加灵活的使用。
可以通过配置Feign的日志级别来显示需要的日志。
定义一个Feign的配置文件,并交给Spring管理。 Feign的日志级别一开始默认是NONE,不显示任何的日志,可以通过定义一个bean,返回日志的级别:
- import feign.Logger;
- import feign.Retryer;
- import org.springframework.context.annotation.Bean;
- import java.util.concurrent.TimeUnit;
-
-
- @Configuration
- public class FeignConfig {
- @Bean
- public Logger.Level feignLoggerLevel() {
- return Logger.Level.BASIC;
- }
- }
日志级别有四种:
SpringBoot默认的级别是info,级别比较高,需要在配置文件中配置,如果只在loggin.level下配置级别,就是全局配置,所以我们可以指定包,指定哪个包下面的日志级别。(项目中定义Feign的Package)
- logging:
- level:
- com.ms.sailfish.feign: debug
全局配置: 在Feign配置类加上@Configuration注解,直接丢给Spring来管理,达成全局配置。 局部配置:
① 局部配置可以通过在feign客户端中指定配置文件,只需要在注解后面加上指定配置类。
@FeignClient(name = "service-store", path = "/api/store", configuration = FeignConfig.class)
② 局部配置还可以直接通过yml配置文件来指定。
- feign:
- client:
- config:
- service-goods: FULL # 指定哪个服务,并且赋上类型。
通过yml直接配置超时时间:
- feign:
- client:
- config:
- default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
- connectTimeout: 2000
- readTimeout: 2000
在store服务中加个Thread.sleep(5000),就能看到报超时异常SocketTimeoutException。
通过加入bean来实现 创建重试器 (重试周期(50毫秒),最大重试周期(2000毫秒),最多尝试次数 3次 ),feign没有采用线性的重试机制而是采用的是一种指数级(乘法)的重试机制 每次重试时间: 当前重试时间 *= 1.5
- @Bean
- public Retryer retryer() {
- return new Retryer.Default(50, TimeUnit.SECONDS.toMillis(2), 3);
- }
在来看看default的构造器,就能更清楚参数含义。
- public Default(long period, long maxPeriod, int maxAttempts) {
- this.period = period;
- this.maxPeriod = maxPeriod;
- this.maxAttempts = maxAttempts;
- this.attempt = 1;
- }
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
- <dependency>
- <groupId>io.github.openfeigngroupId>
- <artifactId>feign-okhttpartifactId>
- dependency>
- import okhttp3.ConnectionPool;
- import okhttp3.OkHttpClient;
- import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
- import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import java.util.concurrent.TimeUnit;
-
-
- @Configuration
- @ConditionalOnClass({OkHttpClient.class})
- @ConditionalOnProperty({"feign.okhttp.enabled"})
- public class FeignOkhttpConfig {
- @Bean
- public okhttp3.OkHttpClient okHttpClient(OkhttpProperties okhttpProperties) {
- return new okhttp3.OkHttpClient.Builder()
- //设置连接超时
- .connectTimeout(okhttpProperties.getConnectTimeout(), TimeUnit.MILLISECONDS)
- //设置读超时
- .readTimeout(okhttpProperties.getReadTimeout(), TimeUnit.MILLISECONDS)
- //是否自动重连
- .retryOnConnectionFailure(true)
- .connectionPool(new ConnectionPool())
- .addInterceptor(new OkHttpLogInterceptor())
- //构建OkHttpClient对象
- .build();
- }
- }
- feign:
- client:
- config:
- default:
- connectTimeout: 2000
- readTimeout: 2000
- httpclient:
- enabled: false
- okhttp:
- enabled: true
- connectTimeout: 4000
- readTimeout: 3000
通过类获取超时时间
- import lombok.Data;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.stereotype.Component;
-
-
- @Data
- @Component
- @ConfigurationProperties(prefix = "feign.okhttp")
- public class OkhttpProperties {
- private Long connectTimeout;
- private Long readTimeout;
- }
可以在拦截器中配置业务需求的代码。
- import lombok.extern.slf4j.Slf4j;
- import okhttp3.Interceptor;
- import okhttp3.Request;
- import okhttp3.Response;
- import okhttp3.ResponseBody;
- import java.io.IOException;
-
-
- @Slf4j
- public class OkHttpLogInterceptor implements Interceptor {
- @Override
- public Response intercept(Interceptor.Chain chain) throws IOException {
- //这个chain里面包含了request和response,所以你要什么都可以从这里拿
- Request request = chain.request();
- long t1 = System.nanoTime();//请求发起的时间
- log.info(String.format("发送请求 %s on %s%n%s",
- request.url(), chain.connection(), request.headers()));
- Response response = chain.proceed(request);
- long t2 = System.nanoTime();//收到响应的时间
- //注意这里不能直接使用response.body().string()的方式输出日志
- //因为response.body().string()之后,response中的流会被关闭,程序会报错,我们需要创建出一个新的response给应用层处理
- ResponseBody responseBody = response.peekBody(1024 * 1024);
- log.info(String.format("接收响应: [%s] %n返回json:【%s】 %.1fms%n%s",
- response.request().url(),
- responseBody.string(),
- (t2 - t1) / 1e6d,
- response.headers()));
- return response;
- }
- }
@FeignClient(name = "service-store", path = "/api/store", configuration = FeignOkhttpConfig.class)