远程调用Feign

Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,返回给调用者。

1 Feign基础使用

1.1 导入依赖

在消费者工程service-consumer中导入Feign依赖信息:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.2 客户端接口实现

在消费者工程service-consumer中实现Feign客户端接口:

    package com.giser.consumer.feign;import com.giser.consumer.pojo.User;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@FeignClient(value="user-service")public interface UserFeignClient {@GetMapping("/user/{id}")User queryById(@PathVariable("id") Long id);}
  • 编写FeignClient,作为一个接口,Feign会通过动态代理生成实现类;
  • 通过@FeignClient标识当前接口为Feign客户端,同时通过value属性指定服务名称;
  • 接口中定义的方法可以使用SpringMVC中的注解,实现方法映射,Feign会根据注解拼接访问地址,并请求服务,将响应结果返回。
1.3 编写控制器
    package com.giser.consumer.controller;import com.giser.consumer.feign.UserFeignClient;import com.giser.consumer.pojo.User;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("cf")public class ConsumerFeignController {@Autowiredprivate UserFeignClient userFeignClient;@GetMapping("/{id}")public User queryById(@PathVariable Long id){return userFeignClient.queryById(id);}}
1.4 开启Feign
    package com.giser.consumer;import org.springframework.boot.SpringApplication;import org.springframework.cloud.client.SpringCloudApplication;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.cloud.openfeign.EnableFeignClients;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@SpringCloudApplication@EnableFeignClients //	开启Feignpublic class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}}
1.5 服务测试

启动EurekaServer、UserService、ConsumerService服务,访问http://localhost:8081/cf/1进行测试。

2 负载均衡

在Feign中已经集成了Ribbon依赖和自动配置。
Feign内置的Ribbon默认了请求超时时长为1000,在服务消费工程service-consumer中,可以通过以下配置修改:

    ribbon:ConnectTimeout: 1000 # 连接超时时长ReadTimeout: 2000 # 数据通信超时时长MaxAutoRetries: 0 # 当前服务器的重试次数MaxAutoRetriesNextServer: 0 # 重试多少次服务OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试

Ribbon内部有重试机制,如果不希望重试,则可以添加ribbon.OkToRetryOnAllOperations=false配置。

3 支持Hystrix

Feign默认也集成了Hystrix,不过默认时关闭的,如需开启可以通过以下配置:

    feign:hystrix:enabled: true

此时对于Feign中的fallback需要通过类来实现:

  • 定义fallback处理类
    package com.giser.consumer.fallback;import com.giser.consumer.feign.UserClient;import com.giser.consumer.pojo.User;import org.springframework.stereotype.Component;@Componentpublic class UserClientFallback implements UserClient {@Overridepublic User queryById(Long id) {User user = new User();user.setId(id);user.setUsername("用户异常");return user;}}
  • 在FeignClient中配置Fallback类
    package com.giser.consumer.feign;import com.giser.consumer.fallback.UserClientFallback;import com.giser.consumer.pojo.User;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@FeignClient(value="user-service", fallback = UserClientFallback.class)public interface UserClient {@GetMapping("/user/{id}")User queryById(@PathVariable("id") Long id);}
  • 启动测试

4 请求压缩

SpringCloud Feign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。

  • 开启请求与响应的压缩功能
    feign:compression:request:enabled: true # 开启请求压缩response:enabled: true
  • 设置请求的数据类型及触发压缩的大小下限
    feign:compression:request:enabled: true # 开启请求压缩mime-types: text/html,application/xml,application/json # 设置压缩的数据类型min-request-size: 2048 # 设置触发压缩的大小下限

5 日志级别

由于@FeignClient 注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例,因此通过logging.level.xx=debug 来设置日志级别对Fegin客户端而言不会产生效果,需要额外指定这个日志的级别。

在service-consumer中的yml中配置日志级别:

    logging:level:com.giser: debug

在service-consumer中编写FeignLogConfig类,定义日志级别:

    package com.giser.consumer.config;import feign.Logger;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class FeignLogConfig {@BeanLogger.Level feignLoggerLevel(){return Logger.Level.FULL;}}

Feign支持四种Level级别,包括:

  • NONE:不记录任何日志信息,这是默认值。

  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间

  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息

  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

    在@FeignClient注解中指定配置类:

    package com.giser.consumer.feign;import com.giser.consumer.config.FeignLogConfig;import com.giser.consumer.fallback.UserClientFallback;import com.giser.consumer.pojo.User;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@FeignClient(value="user-service", fallback = UserClientFallback.class, configuration = FeignLogConfig.class)public interface UserClient {@GetMapping("/user/{id}")User queryById(@PathVariable("id") Long id);}

重启项目,进行服务访问,可以看到类似如下日志信息:

   [UserClient#queryById] ---> GET http://user-service/user/3 HTTP/1.1[UserClient#queryById] Accept-Encoding: deflate[UserClient#queryById] Accept-Encoding: gzip[UserClient#queryById] ---> END HTTP (0-byte body)[UserClient#queryById] <--- HTTP/1.1 200 (888ms)[UserClient#queryById] content-type: application/json;charset=UTF-8[UserClient#queryById] date: Sat, 16 Oct 2021 05:51:53 GMT[UserClient#queryById] transfer-encoding: chunked[UserClient#queryById] [UserClient#queryById] {"id":3,"username":"wangwu","password":"666"}[UserClient#queryById] <--- END HTTP (45-byte body)