Feign声明式服务调用
1、什么是Feign
Feign是Spring Cloud Netflix组件中的一个轻量级RESTful的HTTP服务客户端,实现了负载均衡和rest调用的开源框架,封装了Ribbon和RestTemplate,实现了WebService的面向接口编程,进一步降低了项目的耦合度。
Feign本身并不支持Spring MVC的注解,它有一套自己的注解,为了更方便的使用,Spring Cloud孵化了OpenFeign。
2、Feign解决什么问题
Feign旨在使编写Java HTTP客户端变得更加容易,Feign简化了RestTemplate代码,实现了Bibbon负载均衡,使代码变得更加简洁,也减少了客户端调用的代码,使用Feign实现负载均衡是官方首选,只需要你创建一个接口,然后在上面添加注解即可。
Feign是声明式服务调用组件,其核心是:像调用本地方法一样调用远程方法,无感知远程HTTP请求。
- 它解决了让开发者调用远程接口就跟调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求,无需关注与远程交互的细节。
- 它像Dubbo一样,Consumer直接调用Provider接口方法,而不需要通过常规的HTTP Client构造请求再解析返回数据。
3、Feign VS OpenFeign
OpenFeign是Spring Cloud在Feign的基础上支持了Spring MVC的注解,如@RequestMapping、@Pathvariable等等。
OpenFeign的@FeignClient可以解析Spring MVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,在实现类中做负载均衡并调用服务。
4、Hello World
Feign的使用主要分为以下几个步骤
- 服务消费者添加Feign依赖
- 创建业务接口,添加@FeignClient注解声明需要的调用的服务
- 业务层抽象方法使用SpringMVC注解配置服务地址及参数
- 启动类添加@EnableFeignClients注解
示例代码地址:https://gitee.com/junweihu/eureka-demo
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
创建业务接口
// 第二步,声明要远程调用的微服务
@FeignClient("service-provider")
public interface ProductService {
// 第三步,使用springMVC注解实现远程调用
@GetMapping("/product/list")
List<Product> selectProductList();
}
启动类添加注释@EnableFeignClients
@EnableFeignClients
@SpringBootApplication
public class ServiceConsumerApplication
{
public static void main( String[] args )
{
SpringApplication.run(ServiceConsumerApplication.class);
}
}
5、Feign负载均衡
Feign封装了Ribbon自然的也就集成了负载均衡的功能,默认采用轮询策略。修改负载均衡策略与Ribbon配置一致。
5.1、全局
在启动类或者配置类中注入负载均衡策略对象,所有请求使用该策略。
@Configuration
public class AppConfig {
/**
* 随机负载均衡
* @return
*/
@Bean
public RandomRule randomRule() {
return new RandomRule();
}
}
5.2、局部
修改配置文件中指定服务的负载均衡策略。格式:服务应用名.ribbon.NFLoadBalancerRuleClassName
# 局部负载均衡策略
# service-provider为调用的服务名称
service-provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
6、Feign请求传参
6.1、GET
使用@PathVariable注解或@RequestParam注解接口请求参数。
6.1.1、服务提供者
ProductService.java
Product selectProductById(Integer id);
ProductServiceImpl.java
@Override
public Product selectProductById(Integer id) {
return new Product(id, "饮水机", 1, 500d);
}
ProductController.java
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/{id}")
public Product selectProductById(@PathVariable Integer id) {
return productService.selectProductById(id);
}
}
6.1.2、服务消费者
ProductService.java
@FeignClient("service-provider")
public interface ProductService {
@GetMapping("/product/{id}")
Product selectProductById(@PathVariable Integer id);
}
OrderServiceImpl.java
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private ProductService productService;
@Override
public Order selectOrderById(Integer id) {
return new Order(id, "order-001", "中国", 311990d, Arrays.asList(productService.selectProductById(id)));
}
6.2、POST
使用@RequestBody注解接受请求参数。
6.2.1、服务提供者
ProductService.java
Map addProduct(Product product);
ProductServiceImpl.java
@Override
public Map addProduct(Product product) {
return new HashMap() {
{
put("code",200);
put("message", "success");
put("data", product);
}
};
}
ProductController.java
@PostMapping("/add")
public Map addProduct(@RequestBody Product product) {
return productService.addProduct(product);
}
6.2.2、服务消费者
ProductService.java
@PostMapping("/product/add")
Map addProduct(@RequestBody Product product);
OrderController.java
@PostMapping("/addProduct")
public Map addProduct(@RequestBody Product product) {
return productService.addProduct(product);
}
7、Feign性能优化
7.1、Gzip
gzip:是一种数据格式,采用deflate算法压缩数据,应用广泛,尤其是在Linux平台。
gzip能力:当用gzip压缩一个纯文本文件时,大约可以减少70%以上的文件大小。
gzip作用:网络数据经过压缩后实际上降低了网络传输的字节数,最明显的好处就是可以加快网页的加载速度。网络加载速度加快的好处不言而喻,除了节省流量,改善用户体验外,另一个潜在的好处是gzip与搜索引擎的抓取工具有着更好的关系,例如Google就可以通过直接读取gzip文件来比普通手工抓取更快地检索网页。
7.1.1、HTTP协议关于压缩传输协议的规定
- 客户端向服务器请求中带有:Accept-Encoding: gzip, deflate字段,向服务器标识客户端支持的压缩格式,如果不发送该消息头,服务器默认是不会压缩的。
- 服务端在收到请求之后,如果发现请求头中含有Accept-Encoding字段,并且支持该类型压缩,就会对响应报文压缩之后返回给客户端,并且携带Content-Encoding: gzip消息头,表示响应报文是根据该格式进行压缩的。
- 客户端收到响应后,先判断是否有Content-Encoding消息头,如果有,按该格式解压报文。否则按正常报文处理。
7.1.2、gzip压缩案例
7.1.2.1、局部
只配置Consumer通过Feign到Provider的请求与相应的gzip压缩。
服务消费者配置
# 局部压缩
feign:
compression:
request:
enabled: true # 是否开启gzip压缩
mime-types: text/xml,application/json,application/xml # 配置压缩支持的mime type
min-request-size: 512 # 配置压缩数据大小的最小值,默认2048
response:
enabled: true # 响应是否开启gzip
7.1.2.2、全局
对客户端浏览器的请求以及Consumer对Provider的请求响应都实现gzip。
服务消费者配置
server:
port: 9090
compression:
enabled: true # 是否开启压缩
mime-types: application/json,application/xml,text/html,text/xml,text/plain # 配置压缩支持的mime type
7.2、HTTP连接池
7.2.1、为什么HTTP连接池能提升性能
- 两台服务器建立HTTP连接的过程是一个很复杂的过程,涉及到多个数据包的交换,很耗时。
- HTTP连接需要3次握手4次挥手开销很大。
7.2.2、解决方案
采用HTTP连接池,可以节约大量的3次握手4次挥手,这样能大大提升吞吐量。
Feign的HTTP客户端支持3种框架,HttpURLConnection、HttpClient、OkHttp,默认是HttpURLConnection。相关源码:org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration
- 传统的HttpURLConnection是JDK自带的,并不支持连接池,如果要实现连接池机制,还需要自己来管理连接对象。对于网络请求这种底层相对复杂的操作,如果有可用的其他方案,没有必要自己去管理连接对象。
- HttpClient相比传统JDK自带的HttpURLConnection,它封装了访问HTTP的请求头,参数,内容体,响应等等;它不仅使客户端发送HTTP请求变得容易,而且也方便了开发人员测试接口,既提高了开发效率,又提升了代码健壮性;另外在高并发的时候,可以使用“连接池”提升吞吐量。
7.2.3、使用HttpClient
将Feign的Http客户端修改为HttpClient
添加配置
feign:
httpclient:
enabled: true # 开启httpClient
添加依赖
<!-- feign和apache httpclient整合 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.7.4</version>
</dependency>
如果使用HttpClient作为Feign的客户端工具,那么在定义接口的注解时需要注意,如果传递的参数是一个自定义的对象(对象会使用JSON格式来传递),需要配置参数类型,例如:@GetMapping(value=“xxx”, consumes=MediaType.APPLICATION_JSON_VALUE)。本文中使用的Spring Cloud版本,已无需手动配置。并且使用httpClient,我们还可以通过GET请求传递对象参数。
7.3、请求超时
Feign的负载均衡底层就是Ribbon,所以这里的请求超时其实就是配置Ribbon。
分布式项目中,在服务压力比较大的情况下,可能处理服务的过程需要花费一定的时间,而默认情况下请求超时的配置是1s,所以我们需要调整该配置延长请求超时时间。
修改服务提供者
@GetMapping("/{id}")
public Product selectProductById(@PathVariable Integer id) {
try {
// 睡眠1.5秒
TimeUnit.MILLISECONDS.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return productService.selectProductById(id);
}
消费者调用

7.3.1、全局
Consumer项目中配置请求超时
ribbon:
ConnectionTimeout: 3000 # 请求连接的超时时间,默认1s
ReadTimeout: 3000 # 处理请求的超时时间
7.3.2、局部
一般根据服务的压力大小配置不同的服务超时处理,使用局部配置。
# service-provider为调用的服务名称
service-provider:
ribbon:
OkToRetryOnAllOperations: true # 对所有请求都进行重试
MaxAutoRetries: 3 # 重试次数
MaxAutoRetriesNextServer: 0 # 切换实例重试次数
ConnectionTimeout: 3000 # 请求连接的超时时间
ReadTimeout: 3000 # 处理请求的超时时间
7.4、日志打印
Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节,对Feign的调用情况进行监控和输出。
日志级别:
- NONE:默认的,不显示任何日志
- BASIC:仅记录请求方法、url、响应状态码以及执行时间
- HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
- FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
添加配置信息
logging:
level:
org.example.service.ProductService: debug #feign日志以什么级别监控哪个接口
添加配置类
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
日志