SpringCloud:Feign远程调用

1,301 阅读5分钟

Feign 远程调用

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

1. Feign替代RestTemplate

1.1 Feign的介绍

  • RestTemplate存在的问题

    我们之前使用的RestTemplate远程调用存在着代码可读性差,编程体验不统一,参数复杂URL难以维护。如果再工作中遇见了类似百度搜索的URL将难以维护

    https://www.baidu.com/s?wd=feign&rsv_spt=1&rsv_iqid=0xe4369c8d00722920&issp=1&f=8&rsv_bp=1&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&rsv_dl=tb&rsv_sug3=11&rsv_sug1=7&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=3165&rsv_sug4=3281当我们面对这么长的URL的时候,当然是希望能够少些甚至不写,最低的要求就是不要再service层看到这种东西。所以我们就使用了Feign

  • Feign的介绍

    Feign是一个声明式的http客户端,官方地址:github.com/OpenFeign/f…

    其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。 Feign我理解是常用于微服务相互调用的。

1.2 SpringCloud使用Feign

  1. 添加依赖

    <!-- Feign客户端依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  2. 引导类开启功能

    在引导类上添加@EnableFeignClients注解

    @SpringBootApplication
    @EnableFeignClients
    public class OrderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
        
        ...
    }
    
  3. 编写Feign客户端

    为同一个提供者的服务创建一个接口,里面就替代了所有需要用RestTemplate远程调用的url

    @FeignClient("userservice")
    public interface UserClient {
        @GetMapping("/user/{id}")
        User queryOrderByUserId(@PathVariable Long id);
    }
    

    这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:

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

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

  4. 在对应service层中使用

    首先将Feign客户端自动装配,再调用其中的方法。

    @Service
    public class OrderService {
    
        @Autowired
        private OrderMapper orderMapper;
    
        @Autowired
        private UserClient userClient;
    
        public Order queryOrderById(Long orderId) {
            // 1.查询订单
            Order order = orderMapper.findById(orderId);
            // 2. 利用 Feign 发送 http 请求到对应的 微服务中
            User user = userClient.queryOrderById(orderId);
            // 3. 封装 User 到 Order 中。
            order.setUser(user);
            // 4.返回
            return order;
        }
    }
    

2. 自定义配置

2.1 Feign 自定义配置

Feign可以支持很多的自定义配置,如下表所示:

类型作用说明
feign.Logger.Level修改日志级别包含四种不同的级别:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder响应结果的解析器http远程调用的结果做解析,例如解析json字符串为java对象
feign.codec.Encoder请求参数编码将请求参数编码,便于通过http请求发送
feign. Contract支持的注解格式默认是SpringMVC的注解
feign. Retryer失败重试机制请求失败的重试机制,默认是没有,不过会使用Ribbon的重试

一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。

2.2 配置文件方式

  • 全局配置

    feign:
      client:
        config:
          # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
          default:
            # 日志级别
            loggerLevel: FULL 
    
  • 单个服务配置

    feign:
      client:
        config:
          # 服务名称
          userservice:
            # 日志级别
            loggerLevel: FULL 
    

而日志的级别分为四种:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

2.3 Java代码方式

先创建Feign日志级别配置类

public class DefaultFeignConfiguration {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.HEADERS;
    }
    
    ...
}
  • 全局配置

    在引导类开启Feign并传入日志级别配置类@EnableFeignClients(defaultConfiguration = 日志级别配置类.class)

    @SpringBootApplication
    @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
    public class OrderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
        
        ...
    }
    
  • 单个服务配置

    将配置文件放到对应的Feign客户端的@FeignClient(value = "服务名称", configuration = 日志级别配置类.class)

    @FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class)
    public interface UserClient {
        ...
    }
    

2.4 配置优先级

  • 当我们在一个项目在Feign对相同的服务有着不同的配置,配置文件方式 > Java代码方式
  • 同一个Java方式下有不同的配置,根据先后顺序后面的配置覆盖前面的配置

image-20220522185409379

3. Feign使用优化

Feign作为一个网关,连接使用TCP/IP协议发起http请求,依赖于其它的框架。如果频繁发送请求就会有多次的“三次握手四次挥手”。其底层客户端实现包括:

  • URLConnection:默认实现,不支持连接池

  • Apache HttpClient :支持连接池

  • OKHttp:支持连接池

为了减少频繁请求的情况下资源的消耗,所以在优化方面可以着力于客户端底层使用连接池代替默认的URLConnection。下面我们就用Apache HttpClient来演示。

3.1 Apache HttpClient使用

  1. 引入依赖

    <!--httpClient的依赖 -->
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
    </dependency>
    
  2. 配置

    feign:
      httpclient:
      	# 开启 httpClient
        enabled: true
        # 连接池最大连接数
        max-connections: 200
        # 单个服务最大占用连接数
        max-connections-per-route: 50
    

4. 最佳实践

4.1 统一父接口标准

我们可以发现,Feign的客户端和提供者的controller层是非常相似的

image-20220524131757940

所以为了让代码简化,我们可以将Feign客户端和提供者controller层提作为接口放在一个微服务中,通过引入微服务将对应的接口导入。

4.2 抽取Feign独立模块

FeiginClient抽取成独立的模块,并且将接口有关的pojo、默认的Feign配置都放到这个模块中,提供给所有消费者使用。

  1. 创建Feign-api模块

  2. 引入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  3. 将对应的FeignClient和接口相关的pojo以及默认的配置都放在项目中

    image-20220525201818818

  4. 在使用Feign的项目引导类上,开启扫描Feign-api模块对应的位置

    1. 我们可以将Feign-api模块内某个包下的所有FeignClient都扫描进去。通过@EnableFeignClients(clients = basePackages = "包路径")完成。
    2. 我们可以将Feign-api模块下指定的FeignClient字节码扫描。通过@EnableFeignClients(clients = {clients.class, clients.class})
  5. 启动服务,就成功了。