ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringCloud(1-5) OpenFeign

2021-11-29 15:02:08  阅读:165  来源: 互联网

标签:HTTP OpenFeign service SpringCloud 重试 Copied public


OpenFeign:声明式 RESTful 客户端

类似于 RestTemplate ,OpenFeign 是对 JDK 的 HttpURLConnection(以及第三方库 HttpClient 和 OkHttp)的包装和简化,并且还自动整合了 Ribbon 。

#1. 什么是 OpenFeign

Feign 早先由 Netflix 公司提供并开源,在它的 8.18.0 之后,Nefflix 将其捐赠给 Spring Cloud 社区,并更名为 OpenFeign 。OpenFeign 的第一个版本就是 9.0.0 。

OpenFeign 会完全代理 HTTP 的请求,在使用过程中我们只需要依赖注入 Bean,然后调用对应的方法传递参数即可。这对程序员而言屏蔽了 HTTP 的请求响应过程,让代码更趋近于『调用』的形式。

#2. Feign 的入门案例

#2.1 启动 Nacos 注册中心

启动你本地(或服务器)上的 Nacos Server ,确保其正在运行。

#2.2 创建被调用服务

注意

在调用和被调关系中,被调方是不需要 OpenFeign 的,主调方才需要。

创建一个 Spring Boot Maven 项目作为被调方,命名为 b-service(或其他),确保:

  1. 对外暴露出一个 URL ,即 ,对外提供一个功能。未来,我们的 a-service 会向这个 URL 发出 HTTP 请求,触发 b-service 的这个功能的执行,并从 b-service 这里获得 HTTP 响应。

  2. b-service 能启动、运行,并能连上 Nacos Server ,即,在 Nacos Server 上能看到 b-service 。

#2.3 创建主调服务

创建一个 Spring Boot Maven 项目作为主调方(调用发起方、HTTP 请求发起方),命名为 a-service(或其他)

  1. 在 Spring Initializer 中引入依赖:在 Initializer 的搜索框内搜索并选择 Spring Web 、 Nacos Service Discovery 和 OpenFeign 。

注意

这里自动引入的 OpenFeign 的 maven 依赖为:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Copied!
  1. 为项目添加配置 application.yaml :

    server:
      port: 8080
    spring:
      application:
        name: a-service
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
            username: nacos
            password: nacos
            namespace: public
    #       group: hemiao
    
    Copied!
  2. 最后创建一个启动类 AserviceApplication:

    @SpringBootApplication
    @EnableDiscoveryClient 
    @EnableFeignClients(basePackages = "...") // 看这里,看这里,看这里
    public class AserviceApplication {
        public static void main(String[] args) {
            SpringApplication.run(AserviceApplication.class, args);
        }
    }
    
    Copied!

我们可以看到启动类增加了一个新的注解: @EnableFeignClients ,如果我们要使用 OpenFeign ,必须要在启动类加入这个注解,以开启 OpenFeign 。

这样,我们的 Feign 就已经集成完成了,那么如何通过 Feign 去调用之前我们写的 HTTP 接口呢?

和 MyBatis 类似:

首先创建一个接口 BServiceClient(名字任意),并且通过注解配置要调用的服务地址:

 
@FeignClient(value = "b-service")  // 这里要和 b-service 在 nacos-server 上登记的名字相呼应
public interface BServiceClient {
  @GetMapping("/")
  public String index();

  @PostMapping("/login1")
  public String login1(@RequestParam("username") String username, 
                       @RequestParam("uesrname") String password);

  @PostMapping("/login2")
  public String login2(@SpringQueryMap LoginToken token);

  @PostMapping("/login3")
  public String login3(@RequestBody LoginToken token);

}
Copied!

@FeignClient 注解的 name 属性的值是被调方(也就是服务的提供者)在 Nacos 注册中心上所注册的名字,通常也就是被调方(服务提供者)的 spring.application.name 。

注意

一个服务只能被一个类绑定,不能让多个类绑定同一个远程服务,否则,会在启动项目是出现 “已绑定” 异常。

然后在 OpenFeign 里面通过单元测试来查看效果。

@Test
public void test() {
  try {
    log.debug("{}", bService.index());
  } catch (Exception e) {
    e.printStackTrace();
  }
}
Copied!

说明

OpenFeign 的能力包括但不仅包括这个。

#3. FeignClient 抛出异常

当调用方 b-service 正常返回时,b-service(的 Spring MVC)的返回就是正常的 HTTP 200 响应,而在 a-service 这边,Openfeign 会帮我们做数据(从 HTTP 响应体中的)提取、转换操作,并从 FeignClient 中返回。

当被调方 b-service 返回的是非 200 的响应(比如,500、429 等)时,在 a-service 这边,Openfeign 则会在 FeignClient 方法中抛出一个异常(一个 RuntimeException 的子类)

#4. OpenFeign 的配置

#4.1 超时和超时重试

OpenFeign 本身也具备重试能力,在早期的 Spring Cloud 中,OpenFeign 默认使用的是 feign.Retryer.Default#Default ,重试 5 次。但 OpenFeign 整合了 Ribbon ,而 Ribbon 也有重试的能力,此时,就可能会导致行为的混乱。(总重试次数 = OpenFeign 重试次数 x Ribbon 的重试次数,这是一个笛卡尔积。)

后来 Spring Cloud 意识到了此问题,因此做了改进issues 467 (opens new window),将 OpenFeign 的默认重试改为 feign.Retryer#NEVER_RETRY ,即,默认关闭 。

简单来说,OpenFeign 对外表现出的超时和重试的行为,实际上是它所用到的 Ribbon 的超时和超时重试行为。我们在项目中进行的配置,也都是配置 Ribbon 的超时和超时重试。

# 全局配置
ribbon:
  readTimeout: 1000     # 请求处理的超时时间
  MaxAutoRetries: 5     # 最大重试次数
  MaxAutoRetriesNextServer: 1   # 切换实例的重试次数
  # 是否开启对所有请求进行超时重试。一般不会开启这个功能。默认值是 false ,表示仅对 get 请求进行超时重试
  # okToRetryOnAllOperations: true
Copied!

整个 OpenFeign(实际上是 Ribbon)的最大重试次数为:

(1 + MaxAutoRetries) x (1 + MaxAutoRetriesNextServer)
Copied!

这里需要注意的是『重试』次数是不包含『本身那一次』的。

故意加大被调服务的返回响应时长,你会看到主调服务中打印类似如下消息:

feign.RetryableException: Read timed out executing GET http://SERVICE-PRODUCER/demo?username=tom&password=123

	at feign.FeignException.errorExecuting(FeignException.java:249)
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:129)
	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89)
  ...
Copied!

另外,在被调服务方,你会发现上述配置会导致被调服务收到 12 次请求:

请求次数 = (1 + 5) x (1 + 1)
Copied!

你也可以指定对某个特定服务的超时和超时重试:

# 针对自己向 b-service 发出请求超时的设置
b-service:
  ribbon:
    readTimeout: 3000
    MaxAutoRetries: 2
    MaxAutoRetriesNextServer: 0
Copied!

#4.2 替换底层 HTTP 实现(了解)

类似 RestTemplate,本质上是 OpenFeign 的底层会用到 JDK 的HttpURLConnection 发出 HTTP 请求。另外,如果有需要,你也可以换成第三方库 HttpClient 或 OkHttp 。

替换成 HTTPClient

将 OpenFeign 的底层 HTTP 客户端替换成 HTTPClient 需要 2 步:

  1. 引入依赖:

    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-httpclient</artifactId>
    </dependency>
    
    Copied!
  2. 在配置文件中启用它:

    feign:
      httpclient:
        enabled: true # 激活 httpclient 的使用
替换成 OkHttp

将 OpenFeign 的底层 HTTP 客户端替换成 OkHttp 需要 2 步:

  1. 引入依赖:

    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-okhttp</artifactId>
    </dependency>
    
    Copied!
  2. 在配置文件中启用它:

    feign:
      okhttp:
        enabled: true   # 激活 okhttp 的使用
    
     

#4.3 日志配置(了解)

SpringCloudFeign 为每一个 FeignClient 都提供了一个 feign.Logger 实例。可以根据 logging.level.<FeignClient> 参数配置格式来开启 Feign 客户端的 DEBUG 日志,其中 <FeignClient> 部分为 Feign 客户端定义接口的完整路径。如:

logging:
  level:
    com.woniu.outlet.client: DEBUG
Copied!

然后再在配置类(比如主程序入口类)中加入 Looger.Level 的 Bean:

@Bean
public Logger.Level feignLoggerLevel() {
    return  Logger.Level.FULL;
}
Copied!
级别说明
NONE 不输出任何日志
BASIC 只输出 Http 方法名称、请求 URL、返回状态码和执行时间
HEADERS 输出 Http 方法名称、请求 URL、返回状态码和执行时间 和 Header 信息
FULL 记录 Request 和 Response 的 Header,Body 和一些请求元数据

#5. OpenFeign 的底层原理概述

虽然在使用 OpenFeign 时,我们( 程序员 )定义的是接口,但是 OpenFeign 框架会通过 JDK 动态代理生成 @FeignClient 接口的代理对象。逻辑相当于:

@Autowired
XxxServiceClient client = Proxy.newProxyInstance(invocationHandler);
Copied!

在这里,出现了一个 InvocationHandler 对象,结合 JDK 动态代理的知识,我们知道,当你调用 client 的某个方法时,实际上触发的就是这个 InvocationHandler 对象的 invoke 方法。InvocationHandler 对象逻辑相当于:

public class SimpleInvocationHandler implements InvocationHandler {

    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();

    public SimpleInvocationHandler(Map<Method, MethodHandler> methodToHandler) {
      this.methodToHandler = methodToHandler;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MethodHandler handler = methodToHandler.get(method);                              
        return handler.invoke();
    }
}
Copied!

在 InvocationHandler 中最核心的在于它有一个 Map ,这个 map 以 InvocationHandler 所代理的那个 FeignClient 中所声明的方法的 Method 对象为 key ,值是一个一个的 MethodHandler 对象。

假设有一个 @FeignClient 为如下形式:

@FeignClient("a-service")
public interface AService {
  @RequestMapping("/hello")
  public String hello();

  @RequestMapping("/world")
  public String world();
}
Copied!

那么,AService 有一个代理对象 InvocationHandler ,它里面的 Map 逻辑上形如:

keyvalue
helloMethod helloMethodHandler
worldMethod worldMethodHandler

那么,当你调用 bService.hello(); 方法时,实际上是 InvocationHandler 对象的 invoke 方法被执行,而 InvocationHandler 对象会从它的 Map 中以 hello 方法的 Method 对象为 key 找到对应的一个 MethodHandler 对象,然后调用 MethodHandler 对象的 invoke 方法。

调用关系和流程形如:

bService.hello()
└──> invocationHandler.invoke()
     └──> methodHandler.invoke()
          ├──> 第一件事 ...
          └──> 第二件事 ...
Copied!

MethodHandler 的 invoke() 方法核心就是干了 2 件事情:

  1. 传给 Ribbon 目标服务的服务名,找它 “要” 一个该服务的实例的具体的地址;

  2. 根据 Ribbon 返回的具体地址,发出 HTTP 请求,并等待、解析响应。

标签:HTTP,OpenFeign,service,SpringCloud,重试,Copied,public
来源: https://www.cnblogs.com/Peace123/p/15619420.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有