为了账号安全,请及时绑定邮箱和手机立即绑定

SpringCloud-05-OpenFeign

标签:
Spring Cloud

1. openFeign简介

Feign是一个声明式的Web Service客户端,简单可以理解为封装了Ribbon+RestTemplate的模板代码。
在Spring Cloud中使用Feign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求。Spring Cloud Open Feign对Feign进行增强支持Spring MVC注解,可以像Spring Web一样使用HttpMessageConverters等。

OpenFeign的功能:

  • 1.支持Hystrix和它的Fallback
  • 2.支持Ribbon的负载均衡
  • 3.支持日志
  • 4.支持错误重试

下面截图可以看到,openfeign支持了Ribbon和Hystrix的核心功能。

@EnableFeignClients和@FeignClient 注解介绍

@FeignClient 标注用于声明Feign客户端可访问的Web服务。@EnableFeignClients 声明在应用启动类上,扫描应用中声明的Feign客户端可访问的Web服务。

@FeignClient关键属性介绍

  • value/name:指定远程调用的微服务的名称
  • fallback:指定客户端降级处理的类,fallback指定的class必须实现当前 @FeignClient 声明的接口,并且是spring容器中的bean。

下面通过代码,演示openFeign的负载均衡,超时配置,fallback处理和日志配置
在工程006SpringCloud中用到了5个模块, eureka-server-7001 和 eureka-server-7002 作为注册中心;provider-payment9001和provider-payment9002作为服务提供者,两个实例用于演示负载均衡;consumer-openFeign-order8002服务消费者,也是openFeign的客户端。

006SpringCloud目前的工程结构如图所示:

2. 服务提供者paymentService提供的接口

服务提供者 paymentService 有两个服务实例,分别对应 provider-payment9001 和 provider-payment9002。

在微服务 paymentService 的两个服务实例中,都提供了3个接口:

  • “/get/{id}”:根据id查询payment实例;
  • “/timeout”:通过 TimeUnit.SECONDS.sleep(3) 模拟业务处理超时的请求。
  • “/excep”:模拟业务处理异常

PaymentController.java

@RestController
@RequestMapping("/payment")
public class PaymentController {
    private Logger logger = LoggerFactory.getLogger(PaymentController.class);

    @Value("${server.port}")
    private String serverPort;

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

        if (id != null && id % 2 == 0) {
            Payment payment = Payment.newInstance().setSerial(UUID.randomUUID().toString()).setId(id);
            return CommonResult.newInstance().setCode(200).setMessage("查询成功,serverPort:" + serverPort).setData(payment);
        } else {
            return CommonResult.newInstance().setCode(444).setMessage("没有对应记录,查询ID: " + id).setData(null);
        }
    }

    /**
     * 模拟请求超时
     *
     * @return
     * @throws InterruptedException
     */
    @GetMapping(value = "/timeout")
    public CommonResult<Payment> timeout() throws InterruptedException {
        this.logger.info("timeout-----" + LocalDateTime.now());
        //模拟业务处理时间,线程睡眠3s
        TimeUnit.SECONDS.sleep(3);
        return CommonResult.newInstance().setCode(200).setMessage("查询成功,serverPort:" + serverPort).setData(null);
    }


    /**
     * 模拟请求异常
     *
     * @return
     */
    @GetMapping(value = "/excep")
    public CommonResult<Payment> excep() {
        this.logger.info("excep-----" + LocalDateTime.now());
        //模拟业务处理异常
        int div = 1 / 0;
        return CommonResult.newInstance().setCode(200).setMessage("查询成功,serverPort:" + serverPort).setData(null);
    }
}

3. 服务消费者orderService

服务消费者orderService对应的是consumer-openFeign-order8002模块。

pom.xml,集成 spring-cloud-starter-openfeign 组件

<?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>006SpringCloud</artifactId>
        <groupId>com.xander</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>consumer-openFeign-order8002</artifactId>


    <dependencies>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--common-api-->
        <dependency>
            <groupId>com.xander</groupId>
            <artifactId>common-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>
</project>

application.yml

server:
  port: 8002


eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      # 集群版-
        defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
  instance:
    # 服务实例id
    instance-id: order8002
    #访问路径可以显示IP地址
    prefer-ip-address: true
spring:
  application:
    # 微服务名称
    name: orderService

启动类ConsumerOrder8002,添加@EnableFeignClients注解

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients // 开启 OpenFeign 功能,扫描 @FeignClient 标注的类
public class ConsumerOrder8002 {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerOrder8002.class, args);
    }

}

PaymentOpenFeignService配置@FeignClient注解,指定调用的服务名: paymentService

@FeignClient(value = "paymentService")
public interface PaymentOpenFeignService {

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

    /**
     * 模拟请求超时
     *
     * @return
     */
    @GetMapping(value = "/payment/timeout")
    CommonResult<Payment> timeout();

    /**
     * 模拟请求异常
     *
     * @return
     */
    @GetMapping(value = "/payment/excep")
    CommonResult<Payment> excep();
}

OrderController像本地调用一样直接调用PaymentOpenFeignService,不用关心底层的网络请求过程

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private PaymentOpenFeignService paymentOpenFeignService;

    @GetMapping("/getPayment/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") String id) {
        return this.paymentOpenFeignService.getPaymentById(id);
    }

    /**
     * 模拟请求超时接口
     *
     * @return
     */
    @GetMapping(value = "/timeout")
    public CommonResult<Payment> timeout() {
        return this.paymentOpenFeignService.timeout();
    }

    /**
     * 模拟请求异常接口,用于演示 openFeign 的fallback属性配置服务降级处理
     *
     * @return
     */
    @GetMapping(value = "/excep")
    public CommonResult<Payment> excep() {
        return this.paymentOpenFeignService.excep();
    }    
}

4. 测试负载均衡

分别启动Eureka注册中心,服务提供者provider-payment9001 和 provider-payment9002,服务消费者consumer-openFeign-order8002。

通过浏览器调用 http://localhost:8002/order/getPayment/2 ,可以看到openFeign默认实现了负载均衡。


5. 模拟超时请求测试和 ribbon 的超时配置

接着调用模拟超时接口 http://localhost:8002/order/timeout ,发生了超时,这是因为openFeign封装了ribbon,而ribbon的默认 ConnectTimeout 和 ReadTimeout 都是 1000ms。

paymentService上的 “/payment/timeout” 接口的业务处理时间为 3000ms,所以这里我们配置ribbon的读取超时时间 ReadTimeout 为 5000ms。

在consumer-openFeign-order8002的 application.yml中添加ribbon的配置

### 全局Ribbon 配置
ribbon:
  #启用rest client配置
  restclient:
    enabled: true
  # http建立socket超时时间,毫秒,默认是1000
  ConnectTimeout: 1000
  # http读取响应socket超时时间,默认是1000毫秒
  ReadTimeout: 5000

重启 consumer-openFeign-order8002 服务,再次请求 http://localhost:8002/order/timeout ,没有超时,读取成功。

6. fallback配置客户端的降级处理

我们发送 http://localhost:8002/order/excep 请求,发现响应异常了,这是因为服务提供者的业务接口异常,给服务消费者响应了无法处理的异常响应,紧接着服务消费者也抛出了异常。

consumer-openFeign-order8002中配置openFeign的降级处理

服务有问题(异常或超时)时,向调用方返回一个符合预期的,可处理的备选响应,这就叫做降级处理。
前面介绍了@FeignClient的fallback属性用于指定客户端降级处理的类,fallback指定的class必须实现当前 @FeignClient 声明的接口,并且是spring容器中的bean。

定义类PaymentOpenFeignServiceFallback 实现接口 PaymentOpenFeignService

@Component
public class PaymentOpenFeignServiceFallback implements PaymentOpenFeignService {

    @Override
    public CommonResult<Payment> getPaymentById(String id) {
        return CommonResult.newInstance().setCode(200).setMessage("openFeign fallback 降级处理:getPaymentById");
    }

    @Override
    public CommonResult<Payment> timeout() {
        return CommonResult.newInstance().setCode(200).setMessage("openFeign fallback 降级处理:timeout");
    }

    @Override
    public CommonResult<Payment> excep() {
        return CommonResult.newInstance().setCode(200).setMessage("openFeign fallback 降级处理:excep");
    }
}

PaymentOpenFeignService的@FeignClient注解配置fallback属性,指定为PaymentOpenFeignServiceFallback.class

@FeignClient(value = "paymentService",fallback = PaymentOpenFeignServiceFallback.class)
public interface PaymentOpenFeignService {

......
}

在application.yml中添加feign.hystrix.enabled=true,在 feign 中启动 Hystrix 支持

feign:
  hystrix:
    enabled: true # 在 feign 中启动 Hystrix 支持

重启 consumer-openFeign-order8002 服务,再次请求 http://localhost:8002/order/excep ,客户端没有抛出异常,客户端降级处理成功

7. openFeign的日志配置

openFeign日志默认输出级别是 debug 级别,所以我们得要在debug级别,才能看到openFeign相关的日志输出。

openFeign的内容打印级别又分为了四个级别,可以在 feign.Logger.Level 中看到:

    public static enum Level {
        NONE,
        BASIC,
        HEADERS,
        FULL;

        private Level() {
        }
    }
  • NONE: 不开启日志(默认)
  • BASIC: 记录请求方法、URL、响应状态、执行时间
  • HEADERS: 在BASIC基础上,加上请求头和响应头信息
  • FULL: 在HEADERS基础上,加上请求和响应的正文及元数据

配置 open feign 的日志打印级别

方式一:feign.client.config.微服务id.loggerLevel,微服务id: default代表全局默认配置。

在application.yml中添加日志配置:

logging:
  level:
    # 配置 PaymentOpenFeignService 的日志级别为 debug,这样才能打印 openFeign 相关的日志
    com.xander.order.service.PaymentOpenFeignService: debug

feign:
  client:
    config:
      default: # 微服务id: default代表全局默认配置
        loggerLevel: FULL #   配置 open feign 的日志打印级别
  hystrix:
    enabled: true # 在 feign 中启动 Hystrix 支持

发送请求 http://localhost:8002/order/getPayment/2 ,可以看到 consumer-openFeign-order8002 模块上打印处 openFeign 的详细请求日志

2021-05-21 15:58:43.919 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService    : [PaymentOpenFeignService#getPaymentById] ---> GET http://paymentService/payment/get/2 HTTP/1.1
2021-05-21 15:58:43.919 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService    : [PaymentOpenFeignService#getPaymentById] ---> END HTTP (0-byte body)
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService    : [PaymentOpenFeignService#getPaymentById] <--- HTTP/1.1 200 (5ms)
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService    : [PaymentOpenFeignService#getPaymentById] connection: keep-alive
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService    : [PaymentOpenFeignService#getPaymentById] content-type: application/json
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService    : [PaymentOpenFeignService#getPaymentById] date: Fri, 21 May 2021 07:58:43 GMT
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService    : [PaymentOpenFeignService#getPaymentById] keep-alive: timeout=60
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService    : [PaymentOpenFeignService#getPaymentById] transfer-encoding: chunked
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService    : [PaymentOpenFeignService#getPaymentById] 
2021-05-21 15:58:43.926 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService    : [PaymentOpenFeignService#getPaymentById] {"code":200,"message":"查询成功,serverPort:9001","data":{"id":2,"serial":"c9912a75-729f-4d00-af17-56d024a54c50"}}
2021-05-21 15:58:43.926 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService    : [PaymentOpenFeignService#getPaymentById] <--- END HTTP (119-byte body)

方式二:配置 feign.Logger.Level 的 Bean 实例

@Configuration
public class AppConfig {

    /**
     * 配置 open feign 的日志打印级别
     * - NONE: 不开启日志(默认)
     * - BASIC: 记录请求方法、URL、响应状态、执行时间
     * - HEADERS: 在BASIC基础上,加上请求头和响应头信息
     * - FULL: 在HEADERS基础上,加上请求和响应的正文及元数据
     *
     * @return
     */
    @Bean
    Logger.Level feignLogLervel() {
        return Logger.Level.FULL;
    }
}
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消