1 Feign简介

  • RestTemplate实现远程调用

Feign之前常利用RestTemplate发起远程调用

  1. String url = "http://userservice/user/" + order.getUserId();
  2. User user = restTemplate.getForObject(url, User.class);

RestTemplate存在下面的问题

  • 代码可读性差,编程体验不统一
  • 参数复杂,URL难以维护

其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。

  • Feign是Spring Cloud提供的一个声明式的Http客户端,它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可
  • Nacos注册中心很好的兼容了Feign
  • Feign默认集成了Ribbon,所以在Nacos下使用Fegin默认就实现了负载均衡的效果
  • Feign与Dubbo的异同

https://www.cnblogs.com/ying-z/p/14781757.html
https://www.jianshu.com/p/45c4ebcfea82

2 使用Feign

Fegin的使用步骤如下

  1. 引入依赖

    1. <dependency>
    2. <groupId>org.springframework.cloud</groupId>
    3. <artifactId>spring-cloud-starter-openfeign</artifactId>
    4. </dependency>
  2. 添加注解

在服务启动类添加注解@EnableFeignClients开启Feign的功能

  1. 编写Feign客户端

新建一个接口,封装所有对其他服务的远程调用
如在order-service服务中新建如下接口(调用user-service服务的接口)

  1. package cn.itcast.order.client;
  2. import cn.itcast.order.pojo.User;
  3. import org.springframework.cloud.openfeign.FeignClient;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. @FeignClient("userservice")
  7. public interface UserClient {
  8. @GetMapping("/user/{id}")
  9. User findById(@PathVariable("id") Long id);
  10. }

这个接口使用@FeignClient注解加上SpringMVC的注解来声明被远程调用的服务信息,如:

  • 被调用服务名称:userservice
  • 请求方式:GET
  • 请求路径:/user/{id}
  • 请求参数:Long id
  • 返回值类型:User

这样,Feign就可以帮助我们发送http请求实现远程调用,无需自己使用RestTemplate来发送http请求了。

3 Feign自定义配置

  • Feign可以支持很多的自定义配置,如下表所示 | 类型 | 作用 | 说明 | | —- | —- | —- | | feign.Logger.Level | 修改日志级别 | Feign包含四种不同的日志级别:NONE、BASIC、HEADERS、FULL,默认为NONE(性能较好) | | feign.codec.Decoder | 响应结果的解析器 | 解析器用于对http远程调用的结果做解析,例如解析json字符串为Java对象 | | feign.codec.Encoder | 请求参数的编码器 | 编码器用于将请求参数编码,便于发送http请求 | | feign.Contract | 支持的注解格式 | 默认是SpringMVC的注解,一般不需要改变 | | feign.Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过默认会使用Ribbon的重试机制 |
  • 一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。

下面以日志为例来演示如何自定义配置

4 使用Http连接池

  • Feign并不是一个Http客户端实现,其底层依赖其他Http客户端,如下

    1. URLConnection:Feign默认选项,不支持连接池
    2. Apache HttpClient:支持连接池
    3. OKHttp:支持连接池
  • Http客户端的选择

由于默认的Http客户端URLConnection并不支持连接池,因此通常需要手动为Feign配置一个Http客户端

  • 配置Feign使用Apache HttpClient
    1. feign:
    2. httpclient:
    3. enabled: true # 开启feign对Apache HttpClient的支持
    4. max-connections: 200 # 连接池最大连接数
    5. max-connections-per-route: 50 # 每个路径的最大连接数

5 企业级Feign使用方案

  • 可以发现Feign客户端中的方法和服务提供者的Controller方法非常相似,如下

    • Feign客户端

      @FeignClient("userservice")
      public interface UserServiceClient {
      @GetMapping("/user/{id}")
      User findById(@PathVariable("id") Long id);
      }
      
    • 服务提供者userservice的Controller

      @RestController
      @RequestMapping("/user")
      public class UserController {
      
      @Autowired
      private UserService userService;
      
      @GetMapping("/{id}")
      public User queryById(@PathVariable("id") Long id) {
         return userService.queryById(id);
      }
      }
      

      企业开发中通常使用某种方式简化上述重复代码的编写

      1 继承方式

  • 一样的代码可以通过继承来共享

    1. 定义一个API接口并基于SpringMVC注解做声明。
    2. Feign客户端和Controller都集成该接口

image.png

  • 优点
    • 简单
    • 实现了代码共享
  • 缺点
    • 服务提供方、服务消费方紧耦合,Spring官方不建议该方式
    • 参数列表中的注解并不会继承,因此服务提供者的Controller中必须再次声明方法、参数列表、注解

2 抽取方式

  • 抽取前,调用同一服务的每个服务都会写重复Client,如下

image.png

  • 解决方案

将所有服务中的FeignClient抽取到一个单独的feign-api模块中,这样需要使用Feign远程调用的服务只需要依赖feign-api模块即可
image.png

  • 优点
    • 实现了代码共享,但不会造成服务提供方与消费方的耦合
  • 缺点
    • 随着远程调用的增多,feign-api模块可能包含越来越多的方法,服务调用者可能只需要远程调用几个方法,却依赖了很多没用的内容

3 抽取方式实现

  1. 服务提供者抽取api包
  • 一般来说,抽取的api包由服务提供者来定义(api中定义接口,service中实现接口)。
  • 服务提供者只需要将 向其他服务暴露的Controller方法 集中到api模块中,同时在api模块中定义需要用到的对象即可

image.png

  1. 服务消费者依赖api包
  • 对于服务消费者来说,需要用到哪个服务就依赖哪个服务定义的api模块即可
  • 这里需要注意的是,由于服务消费者需要用到api模块中Client定义的Bean,并且Client所在包并不在启动类所在包内,因此我们需要在启动类的@EnableFeignClients注解中指定basePackages属性,该属性指定扫描@FeignClient的包,从而为这些Client创建Bean

image.png