首页 > 其他分享 >第四章-OpenFeign 远程调用

第四章-OpenFeign 远程调用

时间:2024-03-14 20:47:21浏览次数:27  
标签:调用 depart OpenFeign ResponseResult id provider public 第四章

第四章 Spring Cloud OpenFeign

在第二章中,我们通过RestTemplate实现了远程调用:

@Autowired  
private DiscoveryClient discoveryClient;  
  
private String getLoadBalancedServerAddress() {  
    List<ServiceInstance> instances = discoveryClient.getInstances("depart-provider");  
    Collections.shuffle(instances);  
    String URL = instances.get(0).getUri().toString() + "/provider/depart";  
    System.out.println(URL);  
    return URL;  
}  
  
  
@PostMapping  
public ResponseResult<Boolean> saveHandle(@RequestBody Depart depart) {  
  
    ResponseResult<Boolean> responseResult = 
								    restTemplate.postForObject(getLoadBalancedServerAddress(), depart, ResponseResult.class);  
    return responseResult;  
}  
  
  
@GetMapping("/{id}")  
public ResponseResult<Depart> getHandler(@PathVariable Long id) {  
    String url = getLoadBalancedServerAddress() + "/" + id;  
    ResponseResult<Depart> result = restTemplate.getForObject(url, ResponseResult.class, id);  
    return result;  
}  
  
@GetMapping("/list")  
public ResponseResult<List<Depart>> listHandler() {  
    String url = getLoadBalancedServerAddress() + "/list";  
    ResponseResult<List<Depart>> result = restTemplate.getForObject(url, ResponseResult.class);  
    return result;  
}

但是这种调用方式,与本地方法调用差异太大,编程的体验也不统一。我们必须改变这种远程调用的开发方式。

目的是:让远程调用像本地方法调用一样简单,此时就需要使用OpenFeign。

远程调用的关键点:服务名称、请求方式和路径、返回值类型、请求参数

image.png | 800

OpenFeign简介

OpenFeign 是一个声明式的http客户端,是Spring Cloud在Eureka公司开源的Feign基础上改造而来的,主要作用就是基于注解发送HTTP请求

Features : Declarative REST Client(声明式客户端): Feign creates a dynamic implementation of an interface decorated with JAX-RS or Spring MVC annotations

  • JAX-RS(Java Api eXtensions of Restful web Services):对于RESTful的web服务的Java扩展API,由JavaEE6引入。主要就是使用注解简化客户端的开发和部署。

  • Feign:假装、伪装。OpenFeign将服务提供者的Restful服务伪装为接口进行调用消费者只需要使用Feign接口 + 注解(MVC/JAX-RS)的方式即可为直接调用提供者提供的RESTful服务,无需再使用RestTemplate

  • OpenFeign仅仅是一个伪客户端,不会对请求和响应做额外处理。

OpenFeign与Ribbon

OpenFeign具有负载均衡功能。可以对指定的微服务采用负载均衡的方式进行消费、访问。老版本Spring Cloud所集成的OpenFeign默认采用了Ribbon负载均衡器。由于Netflix已不再维护Ribbon,所以Spring Cloud 2021.x开始集成的 OpenFeign 已经弃用 Ribbon ,而是采用Spring Cloud 自行研发的 Spring Cloud Loadbalancer 作为负载均衡器

使用OpenFeign

参照官网引入依赖:

To include Feign in your project use the starter with group org.springframework.cloud and artifact id spring-cloud-starter-openfeign. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.

  <!--openFeign-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <!--负载均衡器-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>

服务使用者

在服务调用的启动类上添加注解 @EnableFeignClients扫描用@FeignClient("service-name")声明的Feign客户端接口并生成代理对象

@FeignClient声明客户端

@FeignClient("depart-provider")  
public interface DepartClient {  
    @PostMapping("/provider/depart")  
    public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);  
  
    @DeleteMapping("/provider/depart/{id}")  
    public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);  
  
    @GetMapping("/provider/depart/{id}")  
    public ResponseResult<Depart> getHandler(@PathVariable Long id);  
  
    @GetMapping("/provider/depart/list")  
    public ResponseResult<List<Depart>> listHandler();  
  
    @GetMapping("/provider/depart/discovery")  
    public ResponseResult<List<String>> discoveryHandler();  
}
  • @FeignClient("depart-provider"):声明服务名称,自动获取服务提供者列表

  • @GetMapping("/provider/depart/list"):声明请求路径和方式,注意请求路径要带上提供者@RequestMapping的路径

  • ResponseResult<List<Depart>>:声明返回值类型

  • 方法入参:声明请求参数

OpenFeign将服务提供者的RESTful服务伪装为了接口,以上的信息是根据服务提供者的接口信息获取的。

我们只需要调用这个方法就可以完成远程调用,OpenFeign完成了服务拉取、负载均衡、发送请求获取响应的全部操作

服务调用者

@RestController  
@RequestMapping("/consumer/depart")  
public class DepartController {  
  
    @Autowired  
    private DepartClient departClient;  
  
    @PostMapping  
    public ResponseResult<Boolean> saveHandle(@RequestBody Depart depart) {  
        return departClient.postDepart(depart);  
    }  
  
    @GetMapping("/{id}")  
    public ResponseResult<Depart> getHandler(@PathVariable Long id) {  
        return departClient.getHandler(id);  
    }  
  
    @GetMapping("/list")  
    public ResponseResult<List<Depart>> listHandler() {  
        return departClient.listHandler();  
    }  
}

改进FeignClient

在当前的FeignClient中:

```java
@FeignClient("depart-provider")  
public interface DepartClient {  
    @PostMapping("/provider/depart")  
    public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);  
  
    @DeleteMapping("/provider/depart/{id}")  
    public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);  
  
    @GetMapping("/provider/depart/{id}")  
    public ResponseResult<Depart> getHandler(@PathVariable Long id);  
  
    @GetMapping("/provider/depart/list")  
    public ResponseResult<List<Depart>> listHandler();  
  
    @GetMapping("/provider/depart/discovery")  
    public ResponseResult<List<String>> discoveryHandler();  
}

可以使用@RequestMappring或者指定@FeignClient的path属性:

@FeignClient("depart-provider")  
@RequestMapping("/provider/depart")  
public interface DepartClient {  
    @PostMapping  
    public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);  
  
    @DeleteMapping("/{id}")  
    public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);  
  
    @GetMapping("/{id}")  
    public ResponseResult<Depart> getHandler(@PathVariable Long id);  
  
    @GetMapping("/list")  
    public ResponseResult<List<Depart>> listHandler();  
  
    @GetMapping("/discovery")  
    public ResponseResult<List<String>> discoveryHandler();  
}

但是新版Spring Cloud不能这样声明,需要使用第二种方式:

@FeignClient(value = "depart-provider",path = "/provider/depart")  
public interface DepartClient {  
    @PostMapping  
    public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);  
  
    @DeleteMapping("/{id}")  
    public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);  
  
    @GetMapping("/{id}")  
    public ResponseResult<Depart> getHandler(@PathVariable Long id);  
  
    @GetMapping("/list")  
    public ResponseResult<List<Depart>> listHandler();  
  
    @GetMapping("/discovery")  
    public ResponseResult<List<String>> discoveryHandler();  
}

OpenFeign常用配置

超时时间

spring-cloud-feign-overriding-defaults

spring:
	cloud:
		openfeign:
			client:
				config:
					default:
					    #连接超时
						connectTimeout: 5000
						#读超时
						readTimeout: 5000
						loggerLevel: basic
  • connectTimeout 连接超时:consumer连接到provider的时间阈值,主要由网络环境影响

  • readTimeout 读超时:连接成功到接收到响应的时间阈值,主要由提供者业务处理逻辑影响

直接覆盖default会对当前服务中所有的Feign客户端都起作用

配置指定Feign客户端的超时时间

spring:
	cloud:
		openfeign:
			client:
				config:
					#Feign客户端的名称,depart-provider
					feignName:
                        url: http://remote-service.com
						connectTimeout: 5000
						readTimeout: 5000
						loggerLevel: full
						errorDecoder: com.example.SimpleErrorDecoder
						retryer: com.example.SimpleRetryer
						defaultQueryParameters:
							query: queryValue
						defaultRequestHeaders:
							header: headerValue
						requestInterceptors:
							- com.example.FooRequestInterceptor
							- com.example.BarRequestInterceptor
						responseInterceptor: com.example.BazResponseInterceptor
						dismiss404: false
						encoder: com.example.SimpleEncoder
						decoder: com.example.SimpleDecoder
						contract: com.example.SimpleContract
						capabilities:
							- com.example.FooCapability
							- com.example.BarCapability
						queryMapEncoder: com.example.SimpleQueryMapEncoder
						micrometer.enabled: false

局部的配置会覆盖全局的配置

spring:  
  application:  
    name: depart-consumer  
  cloud:  
    nacos:  
      server-addr: 127.0.0.1:8848  
      username: nacos  
      password: nacos  
    openfeign:  
      client:  
        config:  
          default:  
            connect-timeout: 1  
            read-timeout: 1  
          depart-provider:  
            read-timeout: 10

请求响应压缩

spring-cloud-feign-inheritance

spring: 
	cloud: 
		openfeign:  
		  client:  
		    config:  
		      # 全局超时时间  
		      default:  
		        connect-timeout: 1  
		        read-timeout: 1  
		      # 指定客户端超时时间  
		      depart-provider:  
		        read-timeout: 10  
		  # 请求响应压缩  
		  compression:  
		    response:  
		      enabled: true  
		    request:  
		      enabled: true  
		      # 指定对哪种类型进行压缩  
		      mime-types: ["text/xml", "application/xml", "application/json","video/mp4"]  
		      # 指定触发请求压缩的请求最小大小  
		      min-request-size: 1024
  • spring.cloud.openfign.compression.response:对响应结果进行压缩

  • spring.cloud.openfign.compression.request.mime-types:对哪些类型进行压缩,该属性有默认值:

  • spring.cloud.openfign.compression.request.min-request-size:请求大小达到多少触发压缩,该属性有默认值:

@ConfigurationProperties("spring.cloud.openfeign.compression.request")  
public class FeignClientEncodingProperties {  
    private String[] mimeTypes = new String[]{"text/xml", "application/xml", "application/json"};  
    private int minRequestSize = 2048;
}

底层技术选型

HttpClient4/5

Feign的远程调用底层实现采用的是JDK自带的URLConnection,同时还支持HttpClient和OkHttp。

但是URLConnection不支持连接池,通信效率低,生产中是不会使用这个默认实现的。所以Spring Cloud OpenFeign直接将默认实现变为了HttpClient,同时也支持OkHttp。

# 默认值就是true
spring.cloud.openfeign.httpclient.enable 

HttpClient和OkHttp都支持连接池,区别是:在单例模式下HttpClient性能更好,在非单例模式下OkHttp性能更好

生产环境下一般都是单例的,所以OpenFeign默认使用的就是HttpClient4,在[官网](Starting with Spring Cloud OpenFeign 4, the Feign Apache HttpClient 4 is no longer supported. We suggest using Apache HttpClient 5 instead.)中提到:

Starting with Spring Cloud OpenFeign 4, the Feign Apache HttpClient 4 is no longer supported. We suggest using Apache HttpClient 5 instead.|

建议更换为httpclient5(需要添加依赖):

spring.cloud.openfeign.httpclient.hc5.enable=true

自定义Http客户端

官网中描述:You can customize the HTTP client used by providing a bean of either org.apache.hc.client5.http.impl.classic.CloseableHttpClient when using Apache HC5.

在使用HttpClient5的时候可以通过提供一个实现了CloseableHttpClient的bean来自定义HttpClient客户端。

HttpClient代码实现比OkHttp复杂,Spring Cloud OpenFeign仅支持自定义HttpClient,不支持自定义OkHttp。

OkHttp

pom

<!--OK http 的依赖 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>

配置

spring.cloud.openfeign.okhttp.enable=true

负载均衡

官网中描述:

Client feignClient: If Spring Cloud LoadBalancer is on the classpath, FeignBlockingLoadBalancerClient is used. If none of them is on the classpath, the default feign client is used.

如果classpath中有Spring Cloud LoadBalancer,使用FeignBlockingLoadBalancerClient进行负载均衡,如果不存在,只使用默认client

开启两个provider:8087和8088,consumer中已经有load balancer依赖,经过测试默认的负载均衡策略就是轮询

老版本使用Ribbon更换负载均衡策略很简单,但是使用LoadBalancer更换负载均衡策略就需要额外定义一个配置类:

public class LoadBalancerConfig {  
    /**  
     * @param env 环境对象,获取微服务名称  
     * @param factory  
     * @return ReactorLoadBalancer<ServiceInstance> 泛型 <ServiceInstance> 指定对ServiceInstance进行负载均衡  
     */  
    @Bean  
    public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment env, LoadBalancerClientFactory factory) {  
        //获取负载均衡客户端名称,也就是provider的服务名称  
        String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);  
        /*  
        参数一:服务提供者列表  
        参数二:当前需要的provider微服务名称  
        作用:从所有服务提供者中获取到指定名称的提供者列表,进行随机选择负载均衡  
  
        factory.getLazyProvider(name, ServiceInstanceListSupplier.class):获取指定名称的所有服务提供者列表  
        */        
        return new RandomLoadBalancer(factory.getLazyProvider(name, ServiceInstanceListSupplier.class),name);  
    }  
}

不需要添加@Confirguation注解,需要在启动类上使用@LoadBalancerClients指定这个配置类:

@LoadBalancerClients(defaultConfiguration = {LoadBalancerConfig.class})  
@EnableFeignClients  
@SpringBootApplication  
public class Consumer03Application {  
  
    public static void main(String[] args) {  
        SpringApplication.run(Consumer03Application.class, args);  
    }  
  
}

负载均衡策略实际上就是ReactorLoadBalancer接口的实现类,Spring Cloud LoadBalancer只提供了两个

  • RandomLoadBalancer:随机负载均衡
  • RoundRobinLoadBalancer:轮询负载均衡(默认)

Spring Cloud LoadBalancer提供的太少,并且更换起来非常麻烦,所以实际开发中不使用Load Balancer,使用Dubbo。

日志

OpenFeign只会在FeignClient所在的包对应的日志级别为DEBUG时输出日志,OpenFeign对应的日志级别有四个:

  • NONE:不记录任何日志信息,这是默认值
  • BASIC:只记录请求的方法、URL、响应状态码、执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的信息
  • FULL:记录请求和响应的明细,包含头信息、请求体、元数据

未指定时FeignClient的默认级别就是NONE,不会输出日志信息

定义日志级别

  1. 定义项目的日志级别(也就是FeignClient所在包的日志级别)
logging: 
	level: 
		com.euneir: debug
  1. 定义OpenFeign的日志级别
public class DefaultFeignConfig {  
    @Bean  
    public Logger.Level feignLoggerLevel() {  
        return Logger.Level.FULL;  
    }  
}

不需要加@Confirguation

配置日志生效

  • 局部配置:针对指定的FeignClient生效
@FeignClient(value = "depart-provider" ,path = "/provider/depart",configuration = DefaultFeignConfig.class)  
public interface DepartClient {  
    @PostMapping  
    public ResponseResult<Boolean> postDepart(@RequestBody Depart depart);  
  
    @DeleteMapping("/{id}")  
    public ResponseResult<Boolean> deleteHandler(@PathVariable Long id);  
  
    @GetMapping("/{id}")  
    public ResponseResult<Depart> getHandler(@PathVariable Long id);  
  
    @GetMapping("/list")  
    public ResponseResult<List<Depart>> listHandler();  
  
    @GetMapping("/discovery")  
    public ResponseResult<List<String>> discoveryHandler();  
}
  • 全局配置:对扫描到所有的FeignClient生效
@LoadBalancerClients(defaultConfiguration = LoadBalancerConfig.class)  
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)  
@SpringBootApplication  
public class Consumer03Application {  
  
    public static void main(String[] args) {  
        SpringApplication.run(Consumer03Application.class, args);  
    }  
  
}

标签:调用,depart,OpenFeign,ResponseResult,id,provider,public,第四章
From: https://www.cnblogs.com/euneirophran/p/18073897

相关文章

  • 并发支持库:多线程中的std::call_once单次调用
    std::call_once中定义template<classCallable,class...Args>voidcall_once(std::once_flag&flag,Callable&&f,Args&&...args);确保函数或者代码片段在在多线程环境下,只需要执行一次。常用的场景如Init()操作或一些系统参数的获取等。此函数在POSIX中类似p......
  • 问题记录:Vue3的watch,如何做到在监听值没变化的情况下调用相关函数去请求接口?
    一般来说Vue的watch属性只在监听到值发生变化了,才会去执行相关代码。可是最近在做项目(Vue3+TS+Vite)的时候,遇到了这种情况:标题如图所示:Modal是在父组件里写的,Modal里的折线图是在子组件里写的。点击按钮后会获取到pid和vid的值传给子组件。子组件通过watch属......
  • 第四章 python的标准库
    第四章python的标准库一、`os`1.1基本功能1.2文件和目录操作1.2.1目录操作1.2.2文件操作1.3路径操作1.4环境变量1.4.1`os.environ`1.4.2`os.pathsep`1.4.3`os.name`1.4.4`os.system()`1.4.5`os.putenv(key,value)`和`os.unsetenv(key)`1.5进程管理1.5......
  • Lua 如何在Lua中调用C/C++函数
    Lua调用C函数有两种方式程序主体在C中运行,C函数注册到Lua中。C调用Lua,Lua调用C注册的函数,C或者Lua得到函数的执行结果。程序主体在Lua中运行,C函数作为库函数供Lua使用。C++的代码如下如何在Lua脚本中调用这个C语言函数(add_function)?#include<QCoreApplication>#inclu......
  • 单据类型参数设置增加自定义参数并通过BOS标准函数调用
     1、BOS函数说明2、创建对应单据的【单据类型参数】,继承自【单据类型参数模版】。 3、在单据参数中绑定【单据类型参数对象】 4、参数设置设置对应参数 5、在BOS中调用标准函数进行使用。 ......
  • 【操作系统】执行系统调用后发生了什么?
    执行系统调用后发生了什么?什么是系统调用?系统调用是受控的内核入口,借助于这一机制,进程可以请求内核以自己的名字去执行某些动作。以应用程序编程接口(API)的形式,内核提供了一系列服务供程序访问。包括创建进程、执行I/O,以及为进程间通信创建管道等。执行系统调用后发生的事件......
  • Qt6.0开发 第四章 常用界面组件的使用
    第四章常用界面组件的使用在Qt类库中,所有界面组件类的字节或间接父类都是QWidget.QWidget的父类是QObject与QPaintDevice.所以QWidget是多重继承的类.QObject支持元对象系统,其信号与槽机制为编程中对象间通信提供了极大便利.QPaintDevice是能使用QPainter类在绘图设备上绘......
  • Scala第四章节
    Scala第四章节章节目标掌握分支结构的格式和用法掌握for循环和while循环的格式和用法掌握控制跳转语句的用法掌握循环案例理解do.while循环的格式和用法1.流程控制结构1.1概述在实际开发中,我们要编写成千上万行代码,代码的顺序不同,执行结果肯定也会受到一些影......
  • openfeign,webClient, restTemplate 忽略 ssl 证书
    0springboot版本<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.0.3</version><relativePath/><!--lookupparentfromr......
  • AIOps 智能运维:有没有比专家经验更优雅的错/慢调用分析工具?
    作者:图杨工程师小A刚刚接手他们公司最核心的电商系统的运维工作,小A发现,在生产环境中,系统明明运行得非常稳定,但是总会出现一些“诡异”的情况。比如:偶尔会一些错误调用,但是,还没来得及修,系统又莫名奇妙地恢复正常。应用的平均响应时间很短,但是总会有一些响应时间非常长的离......