• SpringCloud OpenFeign


    一、介绍

    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。

    三、SpringCloud整合OpenFeign

    1、引入依赖

    使用OpenFeign组件需要引入客户端依赖:

    1. <dependency>
    2. <groupId>org.springframework.cloudgroupId>
    3. <artifactId>spring-cloud-starter-openfeignartifactId>
    4. dependency>

    2、编写调用接口

    通过OpenFeign远程调用服务的时候,比RestTemplate更加方便,跟编写Controller接口是差不多的。 需要写上@FeignClient注解,里面配置微服务名字和rest的@RequestMapping("/api/store")。

    1. @FeignClient(name = "service-store", path = "/api/store")
    2. public interface StoreFeignService {
    3. // 声明要调用的Rest
    4. @GetMapping("/{id}")
    5. Map getStoreNum(@PathVariable String id);
    6. }

    注意: 需要在启动类中写上注解 @EnableFeignClients

    1. @Autowired
    2. private StoreFeignService storeFeignService;
    3. // 在业务中直接调用
    4. storeFeignService.getStoreNum(uid);

     

    四、OpenFeign自定义配置

    Feign 提供了很多的扩展机制,让用户可以更加灵活的使用。

    1. feign.Logger.Level:修改日志级别,包含四种不同的级别:NONE、BASIC、HEADERS、FULL。
    2. feign.codec.Decoder:响应结果的解析器,http远程调用的结果做解析,例如解析json字符串为java对象。
    3. feign.codec.Encoder:请求参数编码,将请求参数编码,便于通过http请求发。
    4. feign.Contract:支持的注解格式,默认是SpringMVC的注解。
    5. feign. Retryer:失败重试机制,请求失败的重试机制,默认是没有,不过会使用Ribbon的重试。

    1、日志配置

    可以通过配置Feign的日志级别来显示需要的日志。

    a、定义配置类

    定义一个Feign的配置文件,并交给Spring管理。 Feign的日志级别一开始默认是NONE,不显示任何的日志,可以通过定义一个bean,返回日志的级别:

    1. import feign.Logger;
    2. import feign.Retryer;
    3. import org.springframework.context.annotation.Bean;
    4. import java.util.concurrent.TimeUnit;
    5. @Configuration
    6. public class FeignConfig {
    7. @Bean
    8. public Logger.Level feignLoggerLevel() {
    9. return Logger.Level.BASIC;
    10. }
    11. }

     日志级别有四种:

    • NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
    • BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
    • HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
    • FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据。

    b、配置文件设置级别

    SpringBoot默认的级别是info,级别比较高,需要在配置文件中配置,如果只在loggin.level下配置级别,就是全局配置,所以我们可以指定包,指定哪个包下面的日志级别。(项目中定义Feign的Package)

    1. logging:
    2. level:
    3. com.ms.sailfish.feign: debug

     

    c、配置域

    全局配置: 在Feign配置类加上@Configuration注解,直接丢给Spring来管理,达成全局配置。 局部配置:

    ① 局部配置可以通过在feign客户端中指定配置文件,只需要在注解后面加上指定配置类。

    @FeignClient(name = "service-store", path = "/api/store", configuration = FeignConfig.class)

    ② 局部配置还可以直接通过yml配置文件来指定。

    1. feign:
    2. client:
    3. config:
    4. service-goods: FULL # 指定哪个服务,并且赋上类型。

     

    2、配置超时时间

    通过yml直接配置超时时间:

    1. feign:
    2. client:
    3. config:
    4. default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
    5. connectTimeout: 2000
    6. readTimeout: 2000

    在store服务中加个Thread.sleep(5000),就能看到报超时异常SocketTimeoutException。

    3、重试机制配置

    通过加入bean来实现 创建重试器 (重试周期(50毫秒),最大重试周期(2000毫秒),最多尝试次数 3次 ),feign没有采用线性的重试机制而是采用的是一种指数级(乘法)的重试机制 每次重试时间: 当前重试时间 *= 1.5

    1. @Bean
    2. public Retryer retryer() {
    3. return new Retryer.Default(50, TimeUnit.SECONDS.toMillis(2), 3);
    4. }

     在来看看default的构造器,就能更清楚参数含义。

    1. public Default(long period, long maxPeriod, int maxAttempts) {
    2. this.period = period;
    3. this.maxPeriod = maxPeriod;
    4. this.maxAttempts = maxAttempts;
    5. this.attempt = 1;
    6. }

    五、Feign使用优化

    Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:

    • URLConnection:默认实现,不支持连接池
    • Apache HttpClient :支持连接池
    • OKHttp:支持连接池

    1、导入依赖

    1. <dependency>
    2. <groupId>io.github.openfeigngroupId>
    3. <artifactId>feign-okhttpartifactId>
    4. dependency>

    2、设置配置类

    1. import okhttp3.ConnectionPool;
    2. import okhttp3.OkHttpClient;
    3. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    4. import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    5. import org.springframework.context.annotation.Bean;
    6. import org.springframework.context.annotation.Configuration;
    7. import java.util.concurrent.TimeUnit;
    8. @Configuration
    9. @ConditionalOnClass({OkHttpClient.class})
    10. @ConditionalOnProperty({"feign.okhttp.enabled"})
    11. public class FeignOkhttpConfig {
    12. @Bean
    13. public okhttp3.OkHttpClient okHttpClient(OkhttpProperties okhttpProperties) {
    14. return new okhttp3.OkHttpClient.Builder()
    15. //设置连接超时
    16. .connectTimeout(okhttpProperties.getConnectTimeout(), TimeUnit.MILLISECONDS)
    17. //设置读超时
    18. .readTimeout(okhttpProperties.getReadTimeout(), TimeUnit.MILLISECONDS)
    19. //是否自动重连
    20. .retryOnConnectionFailure(true)
    21. .connectionPool(new ConnectionPool())
    22. .addInterceptor(new OkHttpLogInterceptor())
    23. //构建OkHttpClient对象
    24. .build();
    25. }
    26. }

    3、yml配置

    1. feign:
    2. client:
    3. config:
    4. default:
    5. connectTimeout: 2000
    6. readTimeout: 2000
    7. httpclient:
    8. enabled: false
    9. okhttp:
    10. enabled: true
    11. connectTimeout: 4000
    12. readTimeout: 3000

    通过类获取超时时间

    1. import lombok.Data;
    2. import org.springframework.boot.context.properties.ConfigurationProperties;
    3. import org.springframework.stereotype.Component;
    4. @Data
    5. @Component
    6. @ConfigurationProperties(prefix = "feign.okhttp")
    7. public class OkhttpProperties {
    8. private Long connectTimeout;
    9. private Long readTimeout;
    10. }

    4、拦截器

    可以在拦截器中配置业务需求的代码。

    1. import lombok.extern.slf4j.Slf4j;
    2. import okhttp3.Interceptor;
    3. import okhttp3.Request;
    4. import okhttp3.Response;
    5. import okhttp3.ResponseBody;
    6. import java.io.IOException;
    7. @Slf4j
    8. public class OkHttpLogInterceptor implements Interceptor {
    9. @Override
    10. public Response intercept(Interceptor.Chain chain) throws IOException {
    11. //这个chain里面包含了request和response,所以你要什么都可以从这里拿
    12. Request request = chain.request();
    13. long t1 = System.nanoTime();//请求发起的时间
    14. log.info(String.format("发送请求 %s on %s%n%s",
    15. request.url(), chain.connection(), request.headers()));
    16. Response response = chain.proceed(request);
    17. long t2 = System.nanoTime();//收到响应的时间
    18. //注意这里不能直接使用response.body().string()的方式输出日志
    19. //因为response.body().string()之后,response中的流会被关闭,程序会报错,我们需要创建出一个新的response给应用层处理
    20. ResponseBody responseBody = response.peekBody(1024 * 1024);
    21. log.info(String.format("接收响应: [%s] %n返回json:【%s】 %.1fms%n%s",
    22. response.request().url(),
    23. responseBody.string(),
    24. (t2 - t1) / 1e6d,
    25. response.headers()));
    26. return response;
    27. }
    28. }

    5、引入配置

    @FeignClient(name = "service-store", path = "/api/store", configuration = FeignOkhttpConfig.class)

  • 相关阅读:
    硬件寿命警告!Windows11在特定情况下对【固态硬盘】执行与【机械硬盘】相同的磁盘碎片整理。
    (十九)STM32——输入捕获
    程序员的精力管理
    《Deep Residual Learning for Image Recognition》阅读笔记
    面试官:你是如何保障MySQL数据库与Redis缓存的数据一致性?
    【Vue】Setup 函数的使用
    2023年亚太杯APMCM数学建模大赛数据分析题MySQL的使用
    解决hadoop使用put上传报错问题
    电压源的电路分析知识分享
    Instagram Shop如何开通?如何销售?最全面攻略
  • 原文地址:https://blog.csdn.net/summer_fish/article/details/127740155