SpringCloud系列——4OpenFeign简介及应用

导读:本篇文章讲解 SpringCloud系列——4OpenFeign简介及应用,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

学习目标

  1. 什么是OpenFeign以及它的作用

  2. RPC到底怎么理解

  3. OpenFeign的应用

第1章 OpenFeign简介

        在前面的内容中,我们分析了基于RestTemplate实现http远程通信的方法。并且集成了Ribbon实现了请求的负载均衡功能。但是在使用RestTemplate中,仍然可以发现它对于开发者来说并不透明。

        我们仍然需要去通过RestTemplate中的方法来发起远程通信请求,这些流程理论上来说应该都需要对开发者屏蔽,因为开发者只需要关心业务逻辑的实现即可。

其核心目的是让开发者调用本地方法一样调用远程方法。

        所以在Spring Cloud中集成了Netflix的Feign组件。

1.1 什么是Feign

        Feign是一个声明式的伪RPC(Feign 的英文表意为“假装,伪装,变形”)的REST客户端,它用了基于接口的注解方式,可以以Java接口注解的方式调用Http请求,从而将请求模版化。

        Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。

1.2 什么是OpenFeign

        Feign后来交给了OpenFeign组织维护变成了OpenFeign。

        OpenFeign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用OpenFeign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求。

OpenFeign做的事情:

  • 参数的解析和装载

  • 针对指定的feignClient,生成动态代理

  • 针对FeignClient中的方法描述进行解析

  • 组装出一个Request对象,发起请求

1.3 RPC的概念

        英文原义:Remote Procedure Call Protocol

        中文释义:(RFC-1831)远程调用协议 ,最初由RFC-1050定义。

        RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层传输层)和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

        RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息的到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用过程接收答复信息,获得进程结果,然后调用执行继续进行。

        目前,有多种RPC模式和执行。最初由Sun公司提出。IETF ONC宪章重新修订了Sun版本,使得ONC RPC协议成为IETF标准协议。现在使用最普遍的模式和执行是开放式软件基础的分布式计算环境(DCE)。

        远程过程调用(RPC)信息协议由两个不同结构组成:调用信息和答复信息。

        上面描述了这么一大段,我相信有些同学还是不太明白RPC是个什么玩意儿;这里对它做一个简单的定义:实际上RPC就是一个规范,这个规范让远程接口调用变的跟本地接口调用一样简单,不需要你再去关注远程接口的ip、端口、url了,这些事情底层RPC框架都帮你完成了,你只需要拿到要调用的接口的代理对象,然后直接调用该代理对象的方法就完事了。简不简单?E不Easy?

        我们通过RPC协议进行微服务调用变的简单了,那RPC到底要实现什么呢?如果在面试过程中面试官说让你实现一个RPC框架,你该怎么实现呢?

  1. RPC首先肯定是要解决通信问题的,因为你在调用接口是要去远程调用的,那用什么通信协议呢?所以你在设计过程中得回答出来,Http、TCP都可以,比如OpenFeign底层用的就是Http协议;Dubbo底层有多种协议实现,可以选择Http也可以选择TCP。

  2. 通信的问题解决了,那当服务端是集群的时候,还得实现负载均衡的功能吧,你们想想,既然RPC就是要让客户端无感知的去调用服务,那这个负载均衡总不能我们自己去实现吧,所以RPC框架必须给我完成,要不然我们就不用。所以第二点,RPC框架要实现负载均衡。

  3. 当负载均衡功能出现了,就必然涉及到服务注册,一个完整的RPC框架必须得实现这个功能,当然你的框架可以借助Nacos完成,也可以借助Zookeeper完成,不管是借助什么,你总得完成它。

  4. 前面三个是基本功能,除了这些以外,如果你想把你的RPC框架设计的更加完美,可能你还需要增加一些容错啊,日志啊,监控等功能吧。

        OK,以上就是RPC的一个设计思路,具体怎么实现怎么落地,我们接下来参考参考OpenFeign的底层源码。

第2章 OpenFeign使用

2.1 服务端

  1. 构建一个父工程作为服务端SpringCloud系列——4OpenFeign简介及应用SpringCloud系列——4OpenFeign简介及应用

  2. 在父工程目录下添加api子工程(Maven类型)

  3. 在父工程目录下添加service子工程(通过springboot脚手架添加)SpringCloud系列——4OpenFeign简介及应用

     勾选springweb和OpenFeign依赖

  4. 配置pom

    <!-- 父pom -->
    <?xml version="1.0" encoding="UTF-8"?>
    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <groupId>com.example</groupId>
      <artifactId>openfeign-order-service</artifactId>
      <packaging>pom</packaging>
      <version>1.0-SNAPSHOT</version>
      <modules>
        <module>order-api</module>
        <module>order-service</module>
      </modules>
    
      <name>openfeign-order-service</name>
      <!-- FIXME change it to the project's website -->
      <url>http://www.example.com</url>
    
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
        <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.11</version>
          <scope>test</scope>
        </dependency>
      </dependencies>
    
      <dependencyManagement>
        <dependencies>
          <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
          </dependency>
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
          </dependency>
        </dependencies>
      </dependencyManagement>
    </project>
    
    
    <!-- order-service pom -->
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.example</groupId>
            <artifactId>openfeign-order-service</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <groupId>com.example</groupId>
        <artifactId>order-service</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>order-service</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>1.8</java.version>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        </properties>
        <dependencies>
            <dependency>
                <artifactId>order-api</artifactId>
                <groupId>com.example</groupId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
        </dependencies>
    </project>
    
    <!-- order-api pom -->
    <?xml version="1.0" encoding="UTF-8"?>
    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <parent>
        <artifactId>openfeign-order-service</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
      </parent>
      <modelVersion>4.0.0</modelVersion>
    
      <artifactId>order-api</artifactId>
    
      <name>order-api</name>
      <!-- FIXME change it to the project's website -->
      <url>http://www.example.com</url>
    
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
      </dependencies>
    
      <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
          <plugins>
            <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
            <plugin>
              <artifactId>maven-clean-plugin</artifactId>
              <version>3.1.0</version>
            </plugin>
            <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
            <plugin>
              <artifactId>maven-resources-plugin</artifactId>
              <version>3.0.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.8.0</version>
            </plugin>
            <plugin>
              <artifactId>maven-surefire-plugin</artifactId>
              <version>2.22.1</version>
            </plugin>
            <plugin>
              <artifactId>maven-jar-plugin</artifactId>
              <version>3.0.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-install-plugin</artifactId>
              <version>2.5.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-deploy-plugin</artifactId>
              <version>2.8.2</version>
            </plugin>
            <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
            <plugin>
              <artifactId>maven-site-plugin</artifactId>
              <version>3.7.1</version>
            </plugin>
            <plugin>
              <artifactId>maven-project-info-reports-plugin</artifactId>
              <version>3.0.0</version>
            </plugin>
          </plugins>
        </pluginManagement>
      </build>
    </project>
  5. api项目中创建要远程调用的接口

    public interface OrderService {
    
        @GetMapping("/orders")
        String orders();
    
        @PostMapping("/order")
        String insert(@RequestBody OrderDto dto);
    }
  6. 创建实体类OrderDto

    public class OrderDto {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getCount() {
            return count;
        }
    
        public void setCount(int count) {
            this.count = count;
        }
    
        private int count;
    }
  7. api项目中构建OpenFeignclient

    @FeignClient("order-service")
    public interface OrderServiceClient extends OrderService {
    }
  8. 在service项目中实现具体接口

    @RestController
    public class OrderServiceImpl implements OrderService {
        @Value("${server.port}")
        private int port;
        @Override
        public String orders() {
            return "获取"+port+"端口上的Orders成功";
        }
    
        @Override
        public String insert(OrderDto dto) {
            return port+"端口上:"+dto.getName()+"插入"+dto.getCount()+"个成功";
        }
    }

2.2 客户端

  1. 创建工程SpringCloud系列——4OpenFeign简介及应用

  2. pom文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.example</groupId>
        <artifactId>openfeign-user-service</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>openfeign-user-service</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>1.8</java.version>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
        </properties>
        <dependencies>
            <!--<dependency>
                <groupId>io.github.openfeign</groupId>
                <artifactId>feign-okhttp</artifactId>
            </dependency>-->
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>order-api</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency><!--可以支持OKHTTP-->
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
                <version>2.2.6.RELEASE</version>
            </dependency>
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                  <version>2.2.6.RELEASE</version>
              </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
  3. 配置服务端地址,因为没用到Eureka,所以,服务端地址还是配置在本地

    order-service.ribbon.listOfServers=\
      localhost:8080,localhost:8081
  4. 创建controller

    @RestController
    public class TestController {
    
        @Autowired
        OrderServiceClient orderServiceClient;
    
        @GetMapping("/test")
        public String test(){
            return orderServiceClient.orders();
        }
    
        @PostMapping("/test")
        public String insert(@RequestBody OrderDto orderDto){
            return orderServiceClient.insert(orderDto);
        }
    }
  5. 启动类

    @EnableFeignClients(basePackages = "com.example.clients")//这个路径要跟api中带@FeignClient注解的接口的路径一样
    @SpringBootApplication
    public class OpenfeignUserServiceApplication {
    
        public static void main(String[] args) {
            ConfigurableApplicationContext context = SpringApplication.run(OpenfeignUserServiceApplication.class, args);
            System.out.println(context.getBean(HelloService.class));
        }
    
    }

2.3 总结

        通过引入OpenFeign这个组件后,不难发现对于开发人员来说,已经完全实现了网络通信的透明化。并且OpenFeign的整个配置过程也非常简单。

  1. 对于服务提供者,定义FeignClient接口声明,使用@FeignClient注解即可,一个@FeignClient代表一个http客户端,其中的每个方法代表一个具体的接口。

  2. 对于服务消费者,需要依赖FeignClient接口,并且使用@EnableFeignClient注解来开启@FeignClient注解的扫描。

第3章 OpenFeign高级特性

        作为一个http请求通信的代理框架来说,其实并没有太多值得研究的东西。不过OpenFeign里面有几个特性还是值得聊一聊。

  1. Gzip压缩

  2. Feign接口日志

  3. 底层http通信框架

3.1 开启Gzip压缩

        Gzip是一种流行的文件压缩算法,现在的应用十分广泛,尤其是在Linux平台。当应用Gzip压缩到一个纯文本文件时,效果是非常明显的,大约可以减少70%以上的文件大小,当然压缩效率取决于文件中的内容形式。

        在网络通信中,客户端向服务端请求一个数据时,压缩过的内容和未压缩过的内容在传输效率上,压缩后的内容传输效率一定是更高的。

        HTTP 协议支持GZIP 压缩机制,也称协议压缩。 HTTP GZIP压缩是由WEB服务器和浏览器共同遵守的协议,也就是说WEB服务器和浏览器都必须遵守。目前主流的服务器和浏览器都支持GZIP压缩技术。包括 Chrome、IE、FireFox、Opera 等;服务器有 tomcat、Apache 和 IIS 等。

        OpenFeign开启压缩的配置如下

#feign 请求与响应的压缩
feign.compression.request.enabled=true
feign.compression.response.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048 # 2048个字符

3.2 Feign接口日志

我们可以针对指定的Feign接口开启日志,可以通过以下配置来开启Feign的日志配置。

在application.properties中指定日志级别:

logging.level.com.example.clients.OrderServiceClient=DEBUG

 开启日志后,在没有任何其他配置信息的情况下,打印信息如下,比较简单。

2022-06-20 15:06:00.162 DEBUG 21008 --- [nio-8088-exec-1] com.example.clients.OrderServiceClient   : [OrderServiceClient#orders] <--- HTTP/1.1 200 (393ms)
2022-06-20 15:06:00.162 DEBUG 21008 --- [nio-8088-exec-1] com.example.clients.OrderServiceClient   : [OrderServiceClient#orders] connection: keep-alive
2022-06-20 15:06:00.162 DEBUG 21008 --- [nio-8088-exec-1] com.example.clients.OrderServiceClient   : [OrderServiceClient#orders] content-length: 34
2022-06-20 15:06:00.162 DEBUG 21008 --- [nio-8088-exec-1] com.example.clients.OrderServiceClient   : [OrderServiceClient#orders] content-type: text/plain;charset=UTF-8
2022-06-20 15:06:00.162 DEBUG 21008 --- [nio-8088-exec-1] com.example.clients.OrderServiceClient   : [OrderServiceClient#orders] date: Mon, 20 Jun 2022 07:06:00 GMT
2022-06-20 15:06:00.162 DEBUG 21008 --- [nio-8088-exec-1] com.example.clients.OrderServiceClient   : [OrderServiceClient#orders] keep-alive: timeout=60
2022-06-20 15:06:00.163 DEBUG 21008 --- [nio-8088-exec-1] com.example.clients.OrderServiceClient   : [OrderServiceClient#orders] 
2022-06-20 15:06:00.164 DEBUG 21008 --- [nio-8088-exec-1] com.example.clients.OrderServiceClient   : [OrderServiceClient#orders] 获取8080端口上的Orders成功
2022-06-20 15:06:00.164 DEBUG 21008 --- [nio-8088-exec-1] com.example.clients.OrderServiceClient   : [OrderServiceClient#orders] <--- END HTTP (34-byte body)

user-service模块下,添加FeignClientLogConfig类

@Configuration
public class FeignClientConfig {
    @Bean
    Logger.Level feignLogger(){
        return Logger.Level.FULL;
    }
}

它的作用是告诉Feign打印日志的类型

  • NONE,不记录任何日志

  • BASIC,仅记录请求方法、URL以及响应状态码和执行时间

  • HEADRES,除了BASIC以外的还会记录请求和响应的头信息

  • FULL,所有

修改好之后,重启user-service模块。

然后访问user-service模块中的接口,即可看到日志信息。

3.3 通信协议

        OpenFeign默认采用的是sun.net.www.protocol.http包下的HttpURLConnection来实现网络通信的。

        OpenFeign除了支持HttpURLConnection以外,还支持OKhttp,OkHttp是一个处理网络请求的开源项目,由移动支付Square公司贡献。它是一个高性能的HTTP通信框架。

  • 1.支持 SPDY ,共享同一个Socket来处理同一个服务器的所有请求

  • 2.如果SPDY不可用,则通过连接池来减少请求延时

  • 3.无缝的支持GZIP来减少数据流量

  • 4.缓存响应数据来减少重复的网络请求

SPDY(读作“SPeeDY”)是Google开发的基于TCP的会话层 [1] 协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP报头压缩。谷歌表示,引入SPDY协议后,在实验室测试中页面加载速度比原先快64%。

  1. user-service模块中,添加okhttp的jar依赖

    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-okhttp</artifactId>
    </dependency>
  2. 修改application.properties文件

    feign.httpclient.enabled=false
    feign.okhttp.enabled=true
  3. 验证配置是否生效,可以在OkHttpFeignLoadBalancedConfiguration中debug,可以看到如果开启了okhttp,则会通过OkHttpClient进行初始化。

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(OkHttpClient.class)
    @ConditionalOnProperty("feign.okhttp.enabled")
    @Import(OkHttpFeignConfiguration.class)
    class OkHttpFeignLoadBalancedConfiguration {
    
    	@Bean
    	@ConditionalOnMissingBean(Client.class)
    	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
    			SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
    		OkHttpClient delegate = new OkHttpClient(okHttpClient);
    		return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
    	}
    
    }
  4. 并且在请求时,会在OkHttpClient.java类中的execute()方法加断点,看到请求的执行是通过okhttp来完成的。

下文预告

  1. 为什么加一个注解就能实现远程过程调用呢?它底层的实现主流程到底是什么?

  2. OpenFeign怎么实现RPC的基本功能的

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/76722.html

(0)
小半的头像小半

相关推荐

极客之家——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!