微服务-声明式接口调用OpenFeign


1. OpenFeign是什么

OpenFeign与Feign的区别→
GitHub开源链接→
官网解释→
在这里插入图片描述
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口, 然后在上面添加注解@FeignClient。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters , Feign可以与Eureka和Ribbon组合使用以支持负载均衡.

2. OpenFeign能干什么

Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplatehttp请求的封装处理,形成了一套模版化的调用方法。
但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。
所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成了Ribbon
利用Ribbon维护了服务提供方实例列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现服务的调用.

3. OpenFeign的简单实践

此处省略了服务注册与发现集群的创建过程, 本文主要讲OpenFeign的使用 ,参考请移步博客→搭建Eureka-Server集群

3.1 创建微服务父工程

创建微服务父工程[atguigu-cloud-2020], 来进行依赖的版本管理. 依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>atguigu-cloud-2020</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>cloud-api-commons</module>
        <module>cloud-consumer-order</module>
        <module>cloud-eureka-server-7001</module>
        <module>cloud-eureka-server-7002</module>
        <module>cloud-provider-payment-8001</module>
        <module>cloud-provider-payment-8002</module>
    </modules>
    <packaging>pom</packaging>

    <!-- 统一管理jar包版本 -->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
        <mysql.version>5.1.47</mysql.version>
        <druid.version>1.1.16</druid.version>
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
    </properties>

    <!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version  -->
    <dependencyManagement>
        <dependencies>
            <!--spring boot 2.2.2-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--spring cloud Hoxton.SR1-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--spring cloud alibaba 2.1.0.RELEASE-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.boot.version}</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <optional>true</optional>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3.2 创建服务提供者

新增服务提供者模块[cloud-provider-payment-8001], [cloud-provider-payment-8002]

3.2.1 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>atguigu-cloud-2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-payment-8001</artifactId>
   <!-- <artifactId>cloud-provider-payment-8002</artifactId>-->

    <dependencies>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <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>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--mysql-connector-java-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
3.2.2 编写配置

application.yml配置内容:

server:
  port: 8001
spring:
  application:
    name: cloud-provider-payment
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource  # 当前数据源操作类型
    driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
    url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    druid:
      validation-query: select 1 from dual
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities    # 所有Entity别名类所在包
# 日志打印dao层的sql
logging:
  level:
    com.atguigu.springcloud.dao: debug

# eureka客户端配置
eureka:
  client:
    #表示是否将自己注册进EurekaServer,默认为true
    register-with-eureka: true
    #是否从EurekaServer抓取已有的服务注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      #      defaultZone: http://localhost:7001/eureka
      defaultZone: http://www.eureka01.com:7001/eureka,http://www.eureka02.com:7002/eureka
  instance:
    instance-id: cloud-provider-payment-8001 # 另一个是cloud-provider-payment-8002
    prefer-ip-address: true
    #心跳检测与续约时间
    #开发时设置小些,保证服务关闭后注册中心能即使剔除服务
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2

mapper映射文件, mapper/PaymentMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.springcloud.dao.PaymentDao">

    <resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="serial" property="serial" jdbcType="VARCHAR"/>
    </resultMap>

    <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO payment(SERIAL) VALUES(#{serial});
    </insert>

    <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
        SELECT * FROM payment WHERE id=#{id};
    </select>

    <select id="findAll" resultMap="BaseResultMap">
         SELECT * FROM payment
    </select>

</mapper>
3.2.3 编写dao层

com.atguigu.springcloud.dao.PaymentDao

package com.atguigu.springcloud.dao;

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 类描述:支付dao接口
 * @Author wang_qz
 * @Date 2021/11/7 21:13
 * @Version 1.0
 */
@Mapper
public interface PaymentDao {

    int create(Payment payment);

    Payment getPaymentById(@Param("id") Long id);

    List<Payment> findAll();
}
3.2.4 编写service层

com.atguigu.springcloud.service.impl.PaymentServiceImpl

package com.atguigu.springcloud.service.impl;

import com.atguigu.springcloud.dao.PaymentDao;
import com.atguigu.springcloud.service.PaymentService;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * 类描述: 支付service实现
 * @Author wang_qz
 * @Date 2021/11/7 21:27
 * @Version 1.0
 */
@Service
public class PaymentServiceImpl implements PaymentService {

    @Resource
    private PaymentDao paymentDao;

    @Override
    public int create(Payment payment) {
        return paymentDao.create(payment);
    }

    @Override
    public Payment getPaymentById(Long id) {
        return paymentDao.getPaymentById(id);
    }

    @Override
    public List<Payment> findAll() {
        return paymentDao.findAll();
    }
}
3.2.5 编写controller层

com.atguigu.springcloud.controller.PaymentController

package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 类描述:支付的controller
 * @Author wang_qz
 * @Date 2021/11/7 21:29
 * @Version 1.0
 */
@RestController
@Slf4j
@RequestMapping(value = "/payment")
public class PaymentController {

    @Value(value = "${server.port}")
    private String serverPort;
    @Resource
    private PaymentService paymentService;

    @PostMapping(value = "/create")
    public CommonResult create(@RequestBody Payment payment) {
        int result = paymentService.create(payment);
        log.info(">>>插入结果: {}", result);
        if (result > 0) {
            return new CommonResult(200, "插入数据库成功, 服务端口: " + serverPort, payment);
        } else {
            return new CommonResult(444, "插入数据库失败", null);
        }
    }

    @GetMapping(value = "/get/{id}")
    public CommonResult get(@PathVariable(value = "id") Long id) {

        Payment payment = paymentService.getPaymentById(id);
        log.info(">>>查询结果: {}", payment);

        if (payment != null) {
            return new CommonResult(200, "查询成功, 服务端口: " + serverPort, payment);
        } else {
            return new CommonResult(444, "没有对应记录,查询ID: " + id, null);
        }
    }

    @GetMapping
    public CommonResult findAll() {

        List<Payment> all = paymentService.findAll();
        log.info(">>>查询结果: {}", all);
        if (all != null) {
            return new CommonResult(200, "查询成功, 服务端口: " + serverPort, all);
        } else {
            return new CommonResult(444, "没有记录", null);
        }
    }

    @GetMapping(value = "/lb")
    public String getPaymentLB() {
        return serverPort;
    }

    /**
     * 模拟超时场景
     * @return
     */
    @GetMapping(value = "/feign/timeout")
    public String paymentFeignTimeOut() {
        System.out.println("*****paymentFeignTimeOut from port: " + serverPort);
        //暂停几秒线程
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return serverPort;
    }
}
3.2.6 编写启动类

com.atguigu.springcloud.PaymentApplication8001

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * 类描述:支付模块启动类
 * @Author wang_qz
 * @Date 2021/11/7 21:00
 * @Version 1.0
 */
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentApplication8001 {

    public static void main(String[] args) {
        SpringApplication.run(PaymentApplication8001.class);
    }
}

3.3 创建服务消费者

新增服务消费者模块[cloud-consumer-order]

3.3.1 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>atguigu-cloud-2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-order</artifactId>

    <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>
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <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>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
3.3.2 编写配置

application.yml配置内容:

server:
  port: 80
spring:
  application:
    name: cloud-consumer-order

eureka:
  client:
    #表示是否将自己注册进EurekaServer,默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      #      defaultZone: http://localhost:7001/eureka
      defaultZone: http://www.eureka01.com:7001/eureka,http://www.eureka02.com:7002/eureka
  instance:
    instance-id: cloud-consumer-order-80
    prefer-ip-address: true
    #心跳检测与续约时间
    #开发时设置小些,保证服务关闭后注册中心能即使剔除服务
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2

#设置feign客户端超时时间(OpenFeign默认支持ribbon)
#ribbon:
#  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间, 单位ms
#  ConnectTimeout: 5000
#  #指的是建立连接后从服务器读取到可用资源所用的时间,单位ms
#  ReadTimeout: 5000
3.3.3 编写Feign接口

注解@FeignClient + 微服务调用接口方式使用Feign.
com.atguigu.springcloud.service.PaymentFeignService

package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

/**
 * 类描述:FeignClient接口
 * @Author wang_qz
 * @Date 2021/11/13 12:19
 * @Version 1.0
 */
@Component
@FeignClient(name = "CLOUD-PROVIDER-PAYMENT")
public interface PaymentFeignService {
    @PostMapping(value = "/payment/create")
    int create(Payment payment);

    @GetMapping(value = "/payment/get/{id}")
    CommonResult<Payment> getPaymentById(@PathVariable(value = "id") Long id);

    @GetMapping
    CommonResult<List<Payment>> findAll();

    @GetMapping(value = "/payment/feign/timeout")
    String paymentFeignTimeOut();
}
3.3.4 编写controller层

因为是服务消费方进行远程调用, 所以就写一个controller即可. open-feign声明式接口调用, 实现调用代码和业务逻辑的解耦.
com.atguigu.springcloud.controller.OrderController

package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.lb.LoadBalancer;
import com.atguigu.springcloud.service.PaymentFeignService;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.net.URI;
import java.util.List;

/**
 * 类描述:订单消费方法
 * @Author wang_qz
 * @Date 2021/11/8 21:59
 * @Version 1.0
 */
@RestController
@RequestMapping(value = "/consumer")
public class OrderController {
    /**
     * open-feign声明式接口调用
     */
    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping(value = "/feign/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
        return paymentFeignService.getPaymentById(id);
    }

    /**
     * open-feign远程调用超时场景的方法
     * @return
     */
    @GetMapping(value = "/payment/feign/timeout")
    public String paymentFeignTimeOut() {
        return paymentFeignService.paymentFeignTimeOut();
    }

}
3.3.5 编写配置类

com.atguigu.springcloud.config.ApplicationContextConfig

package com.atguigu.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * 类描述:配置类
 * @Author wang_qz
 * @Date 2021/11/8 21:58
 * @Version 1.0
 */
@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
3.3.6 编写启动类

com.atguigu.springcloud.ConsumerApplication, 启动类上添加注解@EnableFeignClients开启Feign的配置功能.

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;


/**
 * 类描述:订单消费者启动类
 * @Author wang_qz
 * @Date 2021/11/8 21:41
 * @Version 1.0
 */
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
//@RibbonClient(name = "CLOUD-PROVIDER-PAYMENT", configuration = {MySelfRule.class}) // 自定义负载均衡策略
@EnableFeignClients
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class);
    }
}
3.3.7 测试OpenFeign调用

依次启动各个微服务应用, 访问 http://localhost/consumer/feign/payment/get/1, 调用成功.
在这里插入图片描述

在这里插入图片描述
从上面的测试结果来看, OpenFeign自带负载均衡功能, 因为OpenFeign内置了Ribbon.
OpenFeign内置Ribbon

3.3.8 调用链总结

在这里插入图片描述

4. OpenFeign超时控制

4.1 超时场景模拟

超时设置,服务提供方8001故意写线程等待程序, 设置超时演示出错情况. com.atguigu.springcloud.controller.PaymentController#paymentFeignTimeOut

@Value(value = "${server.port}")
private String serverPort;
/**
 * 模拟超时场景
 * @return
 */
@GetMapping(value = "/feign/timeout")  // /payment/feign/timeout
public String paymentFeignTimeOut() {
    System.out.println("*****paymentFeignTimeOut from port: " + serverPort);
    //暂停3秒钟线程
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return serverPort;
}

服务消费方80的FeignClient接口类添加对应的超时方法.
com.atguigu.springcloud.service.PaymentFeignService#paymentFeignTimeOut

/**
 * 类描述:FeignClient接口
 * @Author wang_qz
 * @Date 2021/11/13 12:19
 * @Version 1.0
 */
@Component
@FeignClient(name = "CLOUD-PROVIDER-PAYMENT")
public interface PaymentFeignService {
    @PostMapping(value = "/payment/create")
    int create(Payment payment);

    @GetMapping(value = "/payment/get/{id}")
    CommonResult<Payment> getPaymentById(@PathVariable(value = "id") Long id);

    @GetMapping
    CommonResult<List<Payment>> findAll();
    // 新增的超时方法
    @GetMapping(value = "/payment/feign/timeout")
    String paymentFeignTimeOut();
}

服务消费方80的controller层添加超时方法.
com.atguigu.springcloud.controller.OrderController#paymentFeignTimeOut

/**
 * feign远程调用超时场景的方法
 * @return
 */
@GetMapping(value = "/payment/feign/timeout")  // /consumer/payment/feign/timeout
public String paymentFeignTimeOut() {
    return paymentFeignService.paymentFeignTimeOut();
}

启动各个微服务应用, 访问 http://localhost/consumer/payment/feign/timeout, 出现报错:
在这里插入图片描述
在这里插入图片描述

4.2 报错原因及解决方案

Feign客户端默认只等待一秒钟,但是服务提供方处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。
解决方案: 在配置文件中添加Feign客户端与服务提供方的连接和请求资源的超时时间设置.

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

再次测试, 访问 http://localhost/consumer/payment/feign/timeout, 等待几秒后正常返回.
在这里插入图片描述

5. OpenFeign日志打印

5.1 日志功能介绍

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

5.2 日志级别

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

5.3 最佳实践

编写Feign日志配置类 com.atguigu.springcloud.config.FeignConfig

package com.atguigu.springcloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 类描述:Feign的日志级别配置类
 * 方便对Feign接口的调用情况进行监控和输出
 * @Author wang_qz
 * @Date 2021/11/13 22:49
 * @Version 1.0
 */
@Configuration
public class FeignConfig {
    @Bean
    public Logger.Level feignLoggerLevel() {
        /**
         * NONE:默认的,不显示任何日志.
         * BASIC:仅记录请求方法、URL、响应状态码及执行时间.
         * HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息.
         * FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
         */
        return Logger.Level.FULL;
    }
}

为FeignClient接口指定日志输出配置

logging:
  level:
    # feign日志以什么级别监控哪个接口
    com.atguigu.springcloud.service.PaymentFeignService: debug

5.4 测试结果

后台日志查看.
在这里插入图片描述

6. 总结

(1) Feign和OpenFeign两者区别
在这里插入图片描述
(2) 使用: 微服务调用接口 + 注解@FeignClient
(3) OpenFeign内置Ribbon, 自带负载均衡配置.


文章作者: 王子
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 王子 !
评论
  目录