SpringCloud下基于OpenFeign的服务调用实践

ProjectDaedalus

共 6484字,需浏览 13分钟

 ·

2021-11-09 06:47

Feign是一个声明式的Web Service客户端,而OpenFeign则是Spring Cloud在Feign的基础上增强了对Spring MVC注解的支持。其提供了比RestTemplate更加优雅、便捷的服务调用方式

abstract.png

服务提供者

服务提供者payment的部分实现如下,可以看到其暴露了两个接口 pay/hello1、pay/hello2 用于给消费者调用

@RestController
@RequestMapping("pay")
@Slf4j
public class PaymentController {

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

    @GetMapping("/hello1")
    public String hello1(@RequestParam String name) {
        String msg = "[Payment Service-"+ serverPort +"]: " + name;
        return msg;
    }

    @PostMapping("/hello2")
    public Person hello2(@RequestBody Person person) {
        person.setServicePort( serverPort );
        String uuid = UUID.randomUUID().toString();
        person.setId( uuid );
        return person;
    }

}

服务消费者

对于服务消费者order而言,首先需要引入OpenFeign依赖

<dependencyManagement>
  <dependencies>

    
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-dependenciesartifactId>
      <version>2.2.2.RELEASEversion>
      <type>pomtype>
      <scope>importscope>
    dependency>

    
    <dependency>
      <groupId>org.springframework.cloudgroupId>
      <artifactId>spring-cloud-dependenciesartifactId>
      <version>Hoxton.SR1version>
      <type>pomtype>
      <scope>importscope>
    dependency>

  dependencies>
dependencyManagement>

<dependencies>
  
  
  <dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-openfeignartifactId>
  dependency>

dependencies>

为了调用payment服务,我们直接定义接口即可。特别地,该接口需要添加@Component注解。具体地,用@FeignClient注解的name、path属性分别定义所调用服务的服务名、url路径。对于接口中方法,可以通过@RequestMapping等注解定义所调用服务具体的url路径。对于传参,类似于Controller层参数绑定一样,通过@RequestParam、@PathVariable、@RequestBody等注解绑定参数。不同的是,对于@RequestParam、@PathVariable而言,需要通过value属性显式设置参数名。示例如下

@Component
@FeignClient(name = "payment", path = "pay" )
public interface PaymentService {

    @GetMapping("/hello1")
    // 需要通过@RequestParam注解的value属性显式指定参数名
    String method1(@RequestParam(value = "name") String name);

    @PostMapping("/hello2")
    Person method2(@RequestBody Person person);

}

然后,我们就可以直接注入该接口paymentService来进行服务调用,示例如下

@RestController
@RequestMapping("order2")
public class OrderController2 {

    @Autowired
    private PaymentService paymentService;

    @GetMapping("/test1")
    public String test1(@RequestParam String name) {
        String msg = paymentService.method1(name) ;
        String result = "[Order Service test1]: " + msg;
        return result;
    }

    @GetMapping("/test2")
    public String test2(@RequestParam String name, @RequestParam Integer age) {
        Person person = Person.builder()
            .name( name )
            .age( age )
            .build();

        person = paymentService.method2(person);
        String result = "[Order Service test2]: " + person;
        return result;
    }

}

最后,需要在启动类上添加 @EnableFeignClients 注解,如下所示

@SpringBootApplication
// 使用Consul作为注册中心
@EnableDiscoveryClient
// 启用Feign客户端
@EnableFeignClients
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.classargs);
    }
}

测试

至此,我们对于服务提供者payment分别在8004、8005、8006创建三个实例,对于服务消费者order创建一个运行在82端口的实例。并通过Consul作为注册中心。测试结果如下,符合预期。且进一步说明OpenFeign默认支持Ribbon,实现了负载均衡

figure 1.jpeg

超时配置

OpenFeign调用服务时,默认超时配置为1秒。故可通过 feign.client.config..connectTimeoutfeign.client.config..readTimeout 配置项针对各OpenFeign客户端进行配置。其中feignName即为@FeignClient注解所定义的OpenFeign客户端名。特别地,如果feignName为default表示其是对所有客户端的配置

# 配置名为payment的OpenFegin客户端超时参数
feign:
  client:
    config:
      payment:
        # 建立连接超时阈值, Unit: ms
        connectTimeout: 10000
        # 读取资源超时阈值, Unit: ms
        readTimeout: 10000

日志配置

OpenFeign特别提供了日志功能,实现了对服务接口调用的监控。具体有下述四种日志级别

  • NONE:默认的,不显示任何日志
  • BASIC:显示请求方法、URL、状态码及执行时间
  • HEADERS:除了BASIC中定义的信息之外,还会显示请求头、响应头
  • FULL:除了HEADERS中定义的信息之外,还会显示请求体、响应体及元数据

这里,我们利用Java配置类定义OpenFeign的日志级别

package com.aaron.SpringCloud1.order.config;

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

@Configuration
public class OpenFeignLogConfig {

    /**
     * 配置OpenFeign日志级别
     * @return
     */

    @Bean
    public Logger.Level openFeignLogLevel() {
        return Logger.Level.FULL;
    }

}

然后,将我们期望监控的OpenFeign接口的日志级别设为DEBUG即可,如下所示

logging:
  level:
    # 将PaymentService接口的日志级别设为DEBUG
    com.aaron.SpringCloud1.order.service.PaymentService: DEBUG

测试效果,如下所示,符合预期

figure 2.jpeg

服务降级

OpenFeign不仅支持Ribbon实现负载均衡,与此同时其还依赖了Hystrix以支持服务降级。故在服务消费者order中实现PaymentService接口,以提供该接口的降级类。实现相应方法的降级逻辑

@Service
public class PaymentFallBackService implements PaymentService {

    @Override
    public String method1(String name) {
        return "invalid";
    }

    @Override
    public Person method2(Person person) {
        person.setId( null );
        person.setServicePort("-1");
        return person;
    }

}

然后在PaymentService接口上,通过@FeignClient注解的fallback属性指定其相应的降级类

@Component
@FeignClient(name = "payment", path = "pay", fallback = PaymentFallBackService.class)
public interface PaymentService 
{

    @GetMapping("/hello1")
    String method1(@RequestParam(value = "name") String name);

    @PostMapping("/hello2")
    Person method2(@RequestBody Person person);

}

对于order服务的配置文件,做相应调整。首先调整OpenFeign中payment服务的超时配置(connectTimeout、readTimeout)为10s,然后将 feign.hystrix.enabled 设为true,使能OpenFeign的Hystrix功能。最后设置Hystrix的相关配置。具体地,我们配置全局默认的超时时间为3s。当然我们也可以针对某个方法设置单独的超时配置以覆盖全局默认,其中配置项的名称规则为 接口名#方法名(参数类型) 。例如这里设置PaymentService接口method2方法的超时时间为5s,使用的配置项名称为PaymentService#method2(Person)

feign:
  client:
    config:
      payment:
        # 建立连接超时阈值, Unit: ms
        connectTimeout: 10000
        # 读取资源超时阈值, Unit: ms
        readTimeout: 10000
  # 启用OpenFeign的Hystrix功能
  hystrix:
      enabled: true

hystrix:
  command:
    # 配置全局默认的超时时间  
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000
    # 配置PaymentService接口method2方法的超时时间
    PaymentService#method2(Person):
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000

现在我们通过调用order服务接口来进行验证,可以看到正常情况下,通过OpenFeign调用的服务均未发生降级

figure 3.png

现在我们对payment服务进行改造,通过Thread.sleep来模拟业务耗时,如下所示

@RestController
@RequestMapping("pay")
public class PaymentController {

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

    @GetMapping("/hello1")
    public String hello1(@RequestParam String name) {
        String msg = "[Payment Service-"+ serverPort +"]: " + name;

        // 模拟业务耗时
        try{ Thread.sleep(4000); } catch (Exception e) {}
        
        return msg;
    }

    @PostMapping("/hello2")
    public Person hello2(@RequestBody Person person) {
        person.setServicePort( serverPort );
        String uuid = UUID.randomUUID().toString();
        person.setId( uuid );

        // 模拟业务耗时
        try{ Thread.sleep(8000); } catch (Exception e) {}
        
        return person;
    }

}

可以看到两个方法均进行降级,测试结果符合预期,如下所示

figure 4.jpeg

参考文献

  1. Spring微服务实战 John Carnell著
浏览 22
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报