前言
例如:为什么用Feign?
一、Feign 概述
1.1 为什么要用Feign
当我们通过RestTemplate调用其它服务的API时,所需要的参数须在请求的URL中进行拼接,如果参数少的话或许我们还可以忍受,一旦有多个参数的话,这时拼接请求字符串就会效率低下,并且显得好傻。 所有Netflix为我们提供了 Feign
1.2 什么是feign
Feign是Spring Cloud 提供的声明式,模块化的HTTP客户端 , 它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。
Spring Cloud集成Feign并对其进行了增强,使Feign支持了Spring MVC注解;Feign默认集成了Ribbon,所以Fegin默认就实现了负载均衡的效果。
二、Feign 入门
2.1 入门案例
创建工程:
2.2.1 feign_parent
pom.xml 依赖
<dependencies>
<!--Spring Boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Netflix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud 阿里巴巴-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
2.2.2 feign_provider 服务提供者
pom.xml 依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--nacos客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.bjpowernode</groupId>
<artifactId>feign_interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
application.yml 配置文件
server: port: 9090 spring: cloud: nacos: discovery: server-addr: 192.168.184.131 #nacos服务地址 application: name: feign-provider #向注册中心注册的名字
2.2.3 创建feign接口 feign_interface
pom依赖
<dependencies>
<dependency>
<groupId>com.bjpowernode</groupId>
<artifactId>springcloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--Spring Cloud OpenFeign Starter -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
feign接口类
import com.bjpowernode.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "feign-provider") //要调用的服务
@RequestMapping("/provider")
public interface UserFeign {
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable(value="id")Integer id);
}
2.2.4 创建 消费者feign_consumer
pom依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.bjpowernode</groupId>
<artifactId>springcloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--feign接口-->
<dependency>
<groupId>com.bjpowernode</groupId>
<artifactId>feign_interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
controller层
import com.bjpowernode.feign.UserFeign;
import com.bjpowernode.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
// 注入feign接口
@Autowired
private UserFeign userFeign;
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
return userFeign.getUserById(id);
}
}
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient //向注册中心注册该服务,并可以获取到其他服务的调用地址
@EnableFeignClients//开启feign接口扫描
public class FeignConsumerApp {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApp.class,args);
}
}
测试
三、Feign原理
3.1.将Feign接口注入到Spring容器中
@EnableFeignClients注解开启Feign扫描,先调FeignClientsRegistrar.registerFeignClients()方法扫描@FeignClient注解的接口,再将这些接口注入到Spring IOC容器中,方便后续被调用。
3.2 RequestTemplate封装请求信息
SynchronousMethodHandler.invoke():
当定义的的Feign接口中的方法被调用时,通过JDK的代理方式为Feign接口生成了一个动态代理类,当生成代理时,Feign会为每个接口方法创建一个RequestTemplate。该对象封装了HTTP请求需要的全部信息,如请url、参数,请求方式等信息都是在这个过程中确定的。
3.3 发起请求
SynchronousMethodHandler.executeAndDecode():
发出请求:
代理类会通过RequestTemplate创建Request,然后client(URLConnetct、HttpClient、OkHttp)使用Request发送请求
RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求
四、Feign参数传递
服务消费者
import com.bjpowernode.feign.UserFeign;
import com.bjpowernode.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private UserFeign userFeign;
//restful
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
return userFeign.getUserById(id);
}
// refin 路径拼接
@RequestMapping("/deleteUserById")
public User deleteUserById( Integer id ){
return userFeign.deleteUserById(id);
}
//pojo参数
@RequestMapping("/addUser")
public User addUser(User user){
return userFeign.addUser(user);
}
}
feign接口
import com.bjpowernode.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "feign-provider")
@RequestMapping("/provider")
public interface UserFeign {
@RequestMapping("/getUserById/{id}") //restful风格 路径传参
public User getUserById(@PathVariable(value="id")Integer id);
@RequestMapping("/deleteUserById") // feign接口 路径拼接传参
public User deleteUserById(@RequestParam("id") Integer id);
@RequestMapping("/addUser")
User addUser(@RequestBody User user); //pojo --->json
}
服务提供者:
controller层
import com.bjpowernode.pojo.User;
import com.bjpowernode.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/provider")
public class ProviderController {
@Autowired
private UserService userService;
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
return userService.getUserById(id);
}
@RequestMapping("/deleteUserById")
public User deleteUserById(@RequestParam Integer id){
return userService.deleteUserById(id);
}
@RequestMapping("/addUser")
public User addUser(@RequestBody User user){ // json--->pojo
return userService.addUser(user);
}
}
service层
import com.bjpowernode.pojo.User;
public interface UserService {
User getUserById(Integer id);
User deleteUserById(Integer id);
User addUser(User user);
}
import com.bjpowernode.pojo.User;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public User getUserById(Integer id) {
return new User(id,"大傻子---1",18);
}
@Override
public User deleteUserById(Integer id) {
return new User(id,"删除了大傻子---1",18);
}
@Override
public User addUser(User user) {
user.setName("新增了大傻子");
return user;
}
}
-
restful风格:
feign接口:@PathVarible
【拼接restful形式的url】
-
? feign接口 :@RequestParam【拼接feign形式的url】
-
pojo参数
provider: @RequestBody User user
【获取请求体中的json串】
五、feign优化
feign为什么要优化,feign用的是HTTP请求,没有dubbo快
原因是feign可以优化
5.1 开启fegin日志
-
NONE:不记录任何日志信息,这是默认值。
-
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
-
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
-
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
修改服务消费者配置文件:
server: port: 80 spring: cloud: nacos: discovery: server-addr: 192.168.184.131:8848 #注册服务的端口号 application: name: feign-consumer #向注册中心注册的地址 feign: client: config: default: loggerLevel: full # 记录所有请求和响应的明细,包括头信息、请求体、元数据。 logging: level: com.bjpowernode.feign: debug #日志级别
日志输出
5.2 http连接池
两台服务器建立HTTP连接的过程涉及到多个数据包的交换,很消耗时间。采用HTTP连接池可以节约大量的时间提示吞吐量。
Feign的HTTP客户端支持3种框架:HttpURLConnection、HttpClient、OkHttp。
观察源码可以发现,Feign 默认是采用java.net.HttpURLConnection 的,每次请求都会建立、关闭连接。
为了性能考虑,我们可以引入httpclient
、okhttp
作为底层的通信框架。
例如将 Feign
的 HTTP
客户端工具修改为 HttpClient
。
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
feign: client: config: default: loggerLevel: full # 记录所有请求和响应的明细,包括头信息、请求体、元数据。 httpclient: enabled: true # 开启HTTP客户端HTTPClient 默认是开启的
修改之前
修改之后
5.3 gzip 压缩
5.3.1什么是gzip压缩
gzip 是一种数据格式,采用用 deflate 算法压缩 data;gzip 是一种流行的文件 压缩算法,应用十分广泛,尤其是在 Linux 平台。
5.3.2 gzip压缩有什么功能
当 Gzip 压缩到一个纯文本文件时,效果是非常明显的,大约可以减少 70% 以上的文件大小。
5.3.3怎么使用gzip压缩
在服务提供者配置文件配置
server: port: 80 compression: enabled: true # 开启gzip压缩 mime-types: text/html, text/xml, text/plain, text/css, text/javascript,application/javascript, application/json, application/xml # 默认有配置,可以不配
效果
5.4 fegin 超时
Feign 底层内置了 Ribbon 框架,并且使用了 Ribbon 的请求连接超时时间和请求处理超时时间作为其超时时间,而 Ribbon 默认的请求连接超时时间和请求处理超时时间都是 1s,如下源码所示:
当我们调用Feign接口超过一秒就会报一下错误:
有两种方式可以解决超时:
第一种:是设置Ribbon超时时间
ribbon: # 设置超时的第一种方式 ConnectTimeout: 5000 # 设置请求连接的超时时间 ReadTimeout: 5000 #设置请求响应的超时方式
第二种:是设置Feign超时时间
feign: client: config: feign-provider: # 超时的第二种方式 ConnectTimeout: 5000 # 设置请求连接的超时时间 ReadTimeout: 5000 #设置请求响应的超时方式