Spring-Cloud-Openfeign

5/9/2022 微服务

# OpenFeign 服务调用

# 概述

Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。

它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡

特点:Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可

官方文档地址 (opens new window)

GitHub地址 (opens new window)

# Feign和OpenFeign区别

Feign

Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端

Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。 Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务

依赖:spring-cloud-starter-feign

OpenFeign

OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。

OpenFeign的@Feignclient可以解析SpringMvc的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

依赖:spring-cloud-starter-openfeign

# 项目实例

# 1、引入依赖

<dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 2、Yaml配置

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
#设置feign客户端超时时间(OpenFeign默认支持ribbon),默认是1秒
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

logging:
  level:
    # feign日志以什么级别监控哪个接口
    com.atguigu.springcloud.service.PaymentFeignService: debug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3、启动类开启注解

@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {

    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class, args);
    }
}
1
2
3
4
5
6
7
8

# 4、业务处理类

@Component
// 指定服务名
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService
{
    // 调用 CLOUD-PAYMENT-SERVICE 服务提供的具体接口
    @GetMapping("/payment/get/{id}")
    public AjaxResult selectPaymentById(@PathVariable("id") Long id);

    @GetMapping(value = "/payment/feign/timeout")
    public String paymentFeignTimeout();
}
1
2
3
4
5
6
7
8
9
10
11
12

# 5、客户端Controller

@RestController
@Slf4j
public class OrderFeignController
{

    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping(value = "/consumer/payment/get/{id}")
    public AjaxResult getPaymentById(@PathVariable("id") Long id)
    {
        return paymentFeignService.selectPaymentById(id);
    }

    @GetMapping(value = "/consumer/payment/feign/timeout")
    public String paymentFeignTimeout()
    {
        // OpenFeign客户端一般默认等待1秒钟
        return paymentFeignService.paymentFeignTimeout();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 性能优化

# 超时控制

openFeign 默认的超时时间分别是连接超时时间10秒、读超时时间60秒,源码在feign.Request.Options#Options()方法中,但是 openFeign集成了RibbonRibbon的默认超时连接时间、读超时时间都是是1秒,源码在org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute()方法中。

所以,OpenFeign客户端等待超过1秒未响应则报错

openFeign1

# 设置超时时间控制

# 1、设置Ribbon的超时时间(不推荐)

#设置feign客户端超时时间(OpenFeign默认支持ribbon),默认是1秒
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000
1
2
3
4
5
6

# 2、设置openFeign的超时时间(推荐)

feign:
  client:
    config:
      ## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
      default:
        connectTimeout: 5000
        readTimeout: 5000
      ## 为 cloud-payment-service 这个服务单独配置超时时间:单个配置的超时时间将会覆盖全局配置。
      cloud-payment-service:
        connectTimeout: 30000
        readTimeout: 30000
1
2
3
4
5
6
7
8
9
10
11

# 服务降级配置

# hystrix版本

# 1、Yml配置开启

#开启hystrix服务降级
feign:
  hystrix:
    enabled: true
1
2
3
4

# 2、FeignClient接口服务

// FeignClient接口服务加入fallback或者fallbackFactory

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackServiceImpl.class)
// @FeignClient(fallbackFactory = RemoteUserFallbackFactory.class)
public interface PaymentHystrixService 
{
   ....
}
1
2
3
4
5
6
7
8
9

# 3、添加接口实现类

/**
 *  服务降级
 * 接口的统一的fallback处理方法
 * @author LiJunYi
 * @date 2020/08/12
 */
@Component
public class PaymentFallbackServiceImpl implements PaymentHystrixService
{
    @Override
    public String paymentInfoOk(Integer id)
    {
        return "-----PaymentFallbackService fall back-paymentInfoOk";
    }

    @Override
    public String paymentInfoTimeOut(Integer id)
    {
        return "-----PaymentFallbackService fall back-paymentInfo_TimeOut";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# sentinel版本

# 1、依赖引入

        <!--SpringCloud openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 2、Yaml配置

server:
  port: 84
spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
        
# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true
    
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 3、启动类开启注解

@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain84
{
    public static void main(String[] args) {
        SpringApplication.run(OrderNacosMain84.class, args);
    }
}
1
2
3
4
5
6
7
8
9

# 4、Feign接口业务类

/**
 * 支付服务
 *
 * @author LiJunYi
 * @date 2020/12/28
 */
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService
{
    @GetMapping(value = "/paymentSQL/{id}")
    AjaxResult paymentSQL(@PathVariable("id") Long id);
}
1
2
3
4
5
6
7
8
9
10
11
12

# 5、服务降级处理类

@Component
public class PaymentFallbackService implements PaymentService
{
    @Override
    public AjaxResult paymentSQL(Long id)
    {
        return AjaxResult.warn("服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}
1
2
3
4
5
6
7
8
9

# 日志打印功能

Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign 中 Http请求的细节。说白了就是对Feign接口的调用情况进行监控和输出

NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码及热行时间;
HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
1
2
3
4

openFeign3

# 配置日志

/**
 * @ClassName: FeignConfig
 * @Description: 日志控制
 * @author: LiJunYi
 */
@Configuration
public class FeignConfig
{
    @Bean
    Logger.Level feignLoggerLevel()
    {
        return Logger.Level.FULL;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
logging:
  level:
    # feign日志以什么级别监控哪个接口
    com.atguigu.springcloud.service.PaymentFeignService: debug
1
2
3
4

# 请求拦截器

在微服务应用中,通过feign的方式实现http的调用,可以通过实现feign.RequestInterceptor接口在feign执行后进行拦截,对请求头等信息进行修改。

例如项目中利用feign拦截器将本服务的userIduserNameauthentication传递给下游服务

/**
 * feign 请求拦截器
 */
@Component
public class FeignRequestInterceptor implements RequestInterceptor
{
    @Override
    public void apply(RequestTemplate requestTemplate)
    {
        HttpServletRequest httpServletRequest = ServletUtils.getRequest();
        if (StringUtils.isNotNull(httpServletRequest))
        {
            Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
            // 传递用户信息请求头,防止丢失
            String userId = headers.get(CacheConstants.DETAILS_USER_ID);
            if (StringUtils.isNotEmpty(userId))
            {
                requestTemplate.header(CacheConstants.DETAILS_USER_ID, userId);
            }
            String userName = headers.get(CacheConstants.DETAILS_USERNAME);
            if (StringUtils.isNotEmpty(userName))
            {
                requestTemplate.header(CacheConstants.DETAILS_USERNAME, userName);
            }
            String authentication = headers.get(CacheConstants.AUTHORIZATION_HEADER);
            if (StringUtils.isNotEmpty(authentication))
            {
                requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER, authentication);
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackServiceImpl.class,configuration = {FeignRequestInterceptor.class})
public interface PaymentHystrixService 
{
   // ....
}
1
2
3
4
5
6

# Gzip压缩

gzip是一种数据格式,采用deflate算法压缩数据。gzip大约可以帮我们减少70%以上的文件大小。

# 全局配置

server:
  compression:
    # 是否开启压缩
    enabled: true
    # 配置支持压缩的 MIME TYPE
    mime-types: text/html,text/xml,text/plain,application/xml,application/json
1
2
3
4
5
6

# 局部配置

feign:
  compression:
    request:
      # 开启请求压缩
      enabled: true
      # 配置压缩支持的 MIME TYPE
      mime-types: text/xml,application/xml,application/json 
      # 配置压缩数据大小的下限
      min-request-size: 2048   
    response:
      # 开启响应压缩
      enabled: true  
1
2
3
4
5
6
7
8
9
10
11
12

# Http连接池

两台服务器建立HTTP连接的过程涉及到多个数据包的交换,很消耗时间。采用HTTP连接池可以节约大量的时间提示吞吐量。

FeignHTTP客户端支持3种框架:HttpURLConnectionHttpClientOkHttp

默认是采用java.net.HttpURLConnection,每次请求都会建立、关闭连接,为了性能考虑,可以引入httpclientokhttp作为底层的通信框架。

例如将FeignHTTP客户端工具修改为HttpClient

# 添加依赖

<!-- feign httpclient -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
1
2
3
4
5

# 全局配置

feign:
  httpclient:
    # 开启httpclient
    enabled: true
1
2
3
4

# 测试验证

// RemoteUserService FeignClient
@GetMapping("/user/pojo")
public Object selectUser(SysUser user);


// 消费端
@Autowired
private RemoteUserService remoteUserService;

@GetMapping("/user/pojo")
public Object UserInfo(SysUser user)
{
	return remoteUserService.selectUser(user);
}

// 服务端
@GetMapping("/pojo")
public R<SysUser> selectUser(@RequestBody SysUser user)
{
	return R.ok(userService.selectUserByUserName(user.getUserName()));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21