学习SpringCloud Feign带你从0到1

一、什么是Feign

​ Feign是一种声明式、模板化的HTTP<font color='red'>客户端(仅在consumer中使用)</font>。

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

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

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

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

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

三、编写Feign的入门案例

1.需求

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

2.项目设计

image

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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容