学习SpringCloud Feign带你从0到1

3,896 阅读11分钟

一、什么是Feign

​ Feign是一种声明式、模板化的HTTP客户端(仅在consumer中使用)。

二、什么是声明式,有什么作用,解决什么问题?

​ 声明式调用就像调用本地方法一样调用远程方法,无感知远程HTTP请求。

​ 1.SpringCloud的声明式调用,可以做到使用HTTP请求远程服务时就像调用本地方法一样的体验,开发者完全感知不到这是远程方法。更感知不到这是一个HTTP请求

​ 2.它像Dubbo一样,consumr直接调用接口方法调用provider,而不需要通过常规的Http Client构造请求再解析返回数据。

​ 3.它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。

三、编写Feign的入门案例

1.需求

​ 实现电商平台的基本操作

2.项目设计

3.创建项目 Product-Service

3.1 添加坐标
<?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>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.13.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.luyi</groupId>
    <artifactId>springcloud-ego-product-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springcloud-ego-product-service</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
3.2 创建service接口
/**
 * 描述:     商品接口
 */
@RequestMapping("/product")
public interface ProductService {

    //查询所有商品
    @RequestMapping(value = "/findAll", method = RequestMethod.GET)
    public List<Product> findAll();
}
3.3 创建pojo类
/**
 * 描述:     商品实体
 */
public class Product {

    private Integer id;
    private String name;

    public Product() {
    }

    public Product(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

4. 创建Product-Provider

4.1 添加坐标
<?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>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.13.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.luyi</groupId>
    <artifactId>springcloud-ego-product-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springcloud-ego-product-provider</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <!--product service-->
        <dependency>
            <groupId>com.luyi</groupId>
            <artifactId>springcloud-ego-product-service</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
4.2 修改配置文件
spring.application.name=ego-product-provider
server.port=9001

#设置服务注册中心地址,向所有注册中心做注册
eureka.client.serviceUrl.defaultZone=http://user:123456@eureka1:8761/eureka/,http://user:123456@eureka2:8761/eureka/
4.3 编写Controller
/**
 * Product-Provider服务
 */
@RestController
public class ProductController implements ProductService {

    @Override
    public List<Product> findAll() {
        ArrayList<Product> list = new ArrayList<>();
        list.add(new Product(1, "电视"));
        list.add(new Product(2, "电脑"));
        list.add(new Product(3, "冰箱"));
        list.add(new Product(4, "手电筒"));
        return list;
    }
}
4.4 编写SpringBoot的启动类
@EnableEurekaClient
@SpringBootApplication
public class ProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }

}

5.创建Product-Consumer

5.1 添加坐标
<?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>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.13.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.luyi</groupId>
    <artifactId>springcloud-ego-product-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springcloud-ego-product-consumer</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <!--添加feign的坐标-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>

        <!--product service-->
        <dependency>
            <groupId>com.luyi</groupId>
            <artifactId>springcloud-ego-product-service</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
5.2 修改配置文件
spring.application.name=ego-product-consumer
server.port=9002

#设置服务注册中心地址,向所有注册中心做注册
eureka.client.serviceUrl.defaultZone=http://user:123456@eureka1:8761/eureka/,http://user:123456@eureka2:8761/eureka/
5.3 编写controller
/**
 * Product-Consumer服务
 */
@RestController
public class ProductController {

    @Autowired
    private ProductConsumerService consumerService;
    /**
     * Consumer中查询所有商品的方法
     * @return
     */
    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public List<Product> list(){
        return consumerService.findAll();
    }
}
5.4 编写service
//指定实现该接口的服务
@FeignClient(name = "ego-product-provider")
public interface ProductConsumerService extends ProductService {
}
5.5 修改启动类
//添加如下两个注解开启对feign的支持
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

}

四、Feign的请求参数处理

1.单个参数处理

1.1 修改Product-Service项目,添加方法
//根据商品id查询商品
@RequestMapping(value = "/getProductById", method = RequestMethod.GET)
//RequestParam必须指定参数
public Product getProductById(@RequestParam("id") Integer id);
1.2 修改Product-Provider
@Override
public Product getProductById(Integer id) {
    return new Product(id, "SpringCloud");
}
1.3 修改Product-Consumer
/**
 * Consumer中根据商品id查询商品
 */
@RequestMapping(value = "/get", method = RequestMethod.GET)
public Product getProduct(@RequestParam("id") Integer id){
    return consumerService.getProductById(id);
}

2.多个参数处理 方式1: Get提交方式

2.1 修改Product-Service
//添加商品,传递多个参数,get方式
@RequestMapping(value = "/add", method = RequestMethod.GET)
public Product addProduct(@RequestParam("id") Integer id, @RequestParam("name") String name);
2.2 修改Product-Provider
@Override
public Product addProduct(Integer id, String name) {
    return new Product(id, name);
}
2.3 修改Product-Consumer
/**
 * 商品添加,传递多个参数,get方式
 */
@RequestMapping(value = "/add", method = RequestMethod.GET)
public Product addProduct(Product product){
    return consumerService.addProduct(product.getId(), product.getName());
}

3.多个参数处理 方式2: Post提交方式

3.1 修改Product-Service
//添加商品,传递多个参数,post方式
@RequestMapping(value = "/add2", method = RequestMethod.POST)
public Product addProduct2(@RequestBody Product product);
3.2 修改Product-Provider
@Override
public Product addProduct2(@RequestBody Product product) {
    return product;
}
3.3 修改Product-Consumer
/**
 * 商品添加,传递多个参数,post方式
 */
@RequestMapping(value = "/add2", method = RequestMethod.GET)
public Product addProduct2(Product product){
    return consumerService.addProduct2(product);
}

五、Feign的性能优化

1.通过Gzip压缩算法,提高网络通讯速度

1.1 gzip介绍

​ gzip原理:gzip是一种数据格式,采用deflate算法压缩数据,gzip是一种流行的文件压缩算法,应用十分广泛,尤其是在Linux平台。

​ gzip能力:当gzip压缩到一个纯文本文件时效果是非常明显的,大约可以减少70%以上的文件大小。

​ gzip的作用:网络数据经过压缩后也就较低了网络传输的字节数,最明显的就是可以提高网页加载的速度。网页加载速度加快的好处不言而喻,除了节省流量、改善用户的浏览体验外,另一个潜在的好处就是gzip与搜索引擎的提取工具有着更好的关系。例如Google就可以直接通过读取gzip文件来比普通手工抓取更快的检索网页。

1.2 HTTP协议中关于压缩传输的规定

​ 第一:客户端向服务器请求中带有:Accept-Encoding:gzip,deflate字段,向服务器表示,客户端支持的压缩格式(gzip或者deflate),如果不发送该消息头,服务器是不会压缩的。

​ 第二:服务端在收到请求之后,如果发现请求头中含有Accept-Encoding字段,并且支持该类型的压缩,就对响应报文压缩之后返回给客户端。并且携带Content-Encoding:gzip消息头,表示响应报文是根据该格式压缩过的。

​ 第三:客户端接收请求之后,先判断是否有Content-Encoding:消息头,如果有,按改格式解压报文,否则按正常报文处理。

2.编写支持Gzip的压缩案例

2.1 创建项目
2.2 修改配置文件
  • 只配置consumer通过feign到provider的请求与响应进行压缩
#配置请求GZIP压缩
feign.compression.request.enabled=true
#配置响应GZIP压缩
feign.compression.respinse.enabled=true
#配置压缩支持MIME TYPE
feign.compression.request.mime-types=text/xml,application/xml,application/json
#配置压缩数据大小的最小阈值,默认2048
feign.compression.request.min-request-size=512
  • 对客户端浏览器的请求以及consumer对provider的请求与响应做压缩
#-------------spring boot gzip
#是否启用压缩
server.compression.enabled=true
server.compression.mime-types=application/json,application/xml,text/html,text/xml,type/plain

3.采用HTTP连接池,提升Feign的并发吞吐量

为什么http连接池能提高性能

3.1 http的背景原理

​ a.两台服务器建立http连接的过程是很复杂的过程,涉及到多个数据包的交换,并且也很消耗时间

​ b.Http连接需要三次握手四次挥手,开销很大。这样的开销对于请求比较多但信息量又比较小的请求开销更大。

3.2 优化解决方案

​ a.如果我们直接采用http连接池,节约了大量三次握手四次挥手的时间,这样能大大提升吞吐量。

​ b.feign的http客户端支持3种框架:HttpURLConnection、HttpClient、okhttp,默认是HttpURLConnection。

​ c.传统的HttpURLConnection是JDK自带的,并不支持连接池,如果要实现连接池的机制。还需要自己来管理连接对象。对于网络请求这种底层相对复杂的操作,如果有可用的其他方案,也没有必要自己去管理连接对象。

​ d.HttpClient相比于JDK自带的HttpURLConnection,它封装了访问http的请求头、参数、内容体、响应等等。它不仅使发送http请求变得容易,而且也方便开发人员测试接口(基于HTTP协议的),即提高了开发的效率,也方便提高代码的健壮性,另外高并发大量的请求的时候,还是用连接池提高吞吐量。

4.将Feign的HTTP工具修改为HttpClient

4.1 创建项目
4.2 添加坐标
<!--Apache HttpClient替换Feign原生httpURLConnection-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
<dependency>
    <groupId>com.netflix.feign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>8.17.0</version>
</dependency>
4.3 修改配置文件,开启HttpClient的使用
#启用httpclient
feign.httpclient.enabled=true

注意:如果使用HttpClient作为Feign作为Feign的客户端工具,那么在定义接口上的注解时需要注意,如果传递的是一个自定义对象(对象会使用json类型来传递),需要添加指定类型

4.4 Product-Service
/**
 * 描述:     商品接口
 */
@RequestMapping("/product")
public interface ProductService {

    //查询所有商品
    @RequestMapping(value = "/findAll", method = RequestMethod.GET)
    public List<Product> findAll();

    //根据商品id查询商品
    @RequestMapping(value = "/getProductById", method = RequestMethod.GET)
    public Product getProductById(@RequestParam("id") Integer id);

    //添加商品,传递多个参数,get方式
    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public Product addProduct(@RequestParam("id") Integer id, @RequestParam("name") String name);


    //----------------------------------HttpClient------------------------------------
    //添加商品,传递多个参数,post方式
    @RequestMapping(value = "/add2", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
    public Product addProduct2(@RequestBody Product product);

    //使用HttpClient工具添加商品,传递多个参数,基于Get方式
    @RequestMapping(value = "/add3", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE)
    public Product addProduct3(Product product);
}

六、查看微服务日志中记录每个接口URL、状态码和耗时信息

1.创建项目

2.添加logback.xml文件

​ 将输出日志级别设置为DEBUG

<!-- 日志输出级别 -->
<root level="DEBUG">
    <appender-ref ref="Stdout" />
    <appender-ref ref="RollingFile" />
</root>

3.在启动类中添加一个方法

//添加如下两个注解开启对feign的支持
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class ConsumerApplication {

    /**
     * NONE:不记录任何信息,默认值
     * BASIC:记录请求url、请求方法、状态码和用时的时候使用
     * HEADERS:在BASIC基础上再记录一些常用信息
     * FULL:记录请求和响应的所有信息
     */
    @Bean
    public Logger.Level getLog(){
        return Logger.Level.FULL;
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

}

七、配置Feign负载均衡请求超时时间

​ Feign的负载均衡底层用的就是Ribbon

1.修改配置文件,设置超时时间

1.1 全局配置
#全局配置
#请求连接的超时时间,默认为1000ms
ribbon.ConnectTimeout=5000
#处理请求的超时时间
ribbon.ReadTimeout=5000
1.2 根据服务名称进行局部超时配置
#局部配置
#对所有操作请求都进行重试
ego-product-provider.ribbon.OkToRetryOnAllOperations=true
#对当前实例的重试次数
ego-product-provider.ribbon.MaxAutoRetries=2
#切换实例的重试次数
ego-product-provider.ribbon.MaxAutoRetriesNextServer=0
#请求连接的超时时间
ego-product-provider.ribbon.ConnectTimeout=3000
#请求处理的超时时间
ego-product-provider.ribbon.ReadTimeout=3000