声明式服务调用Feign

开发微服务,免不了需要服务间调用。Spring Cloud框架提供了RestTemplate和FeignClient两个方式完成服务间调用(注:早期我们用的是叫 Netflix Feign,不过这个东西的最近一次更新还停留在 2016年7月,OpenFeign 则是 Spring Cloud 团队在 Netflix Feign 基础上开发出来的声明式服务调用组件,OpenFeign也一直在维护)。

使用RestTemplate时,每次都要写请求 Url 、配置响应数据类型,最后还要组装参数,更重要的是这些都是一些重复的工作,代码高度相似,每个请求只有 Url 不同,请求方法不同、参数不同,其它东西基本都是一样的。

一、Feign基本使用

准备工作

搭建一个父工程,然后创建一个服务注册中心。服务注册中心搭建成功后,接下来我们还要再搭建一个 provider 用来提供服务。provider 搭建成功后,依然提供一个 HelloController 接口,里边配上一个 /hello的接口:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(String name) {
        return "hello " + name + " !";
    }
}

然后分别启动服务注册中心 eureka 以及服务提供者 provider ,然后在浏览器中输入http://localhost:8761 可以看到我们的实例情况。

如何使用feign

准备工作完成后,我们创建一个feign-consumer的SpringBoot工程,项目创建好后依赖如下 :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--eureka-->
<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>

创建好了后,我们在application.yml中将我们的feign -consumer 注册到服务中心 (eureka)中:

spring:
  application:
    name: feigin-consumer
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
server:
  port: 5002

最后在我们的feign-consumer中的主启动类中添加@EnableFeignClients注解,开启Feign的支持。

下面我们创建一个HelloServiceFeign接口,用来消费provider提供的接口 。

@FeignClient("service-provider")
public interface HelloServiceFeign {
    @GetMapping("/hello")
    String hello(@RequestParam("name") String name);
}

这个接口做了两件事情:

  • 使用 @FeignClient(“service-provider”) 注解将当前接口和 provider 服务绑定, provider 是服务名,可以忽略大小写;
  • 使用 SpringMVC 的 @GetMapping("/hello") 注解将 hello 方法和 provider 中的 hello 接口绑定在一起。

经过这样的步骤之后,我们就可以在一个 Controller 中注入 HelloServiceFeign 接口并使用它了,而 HelloServiceFeign 接口也会去调用相关的服务。我的 Controller 如下:

@RestController
public class HelloController {
    @Autowired
    HelloServiceFeign helloService;

    @GetMapping("/hello")
    public String hello(String name) {
        return helloService.hello(name);
    }
}

配置好了后,我们在浏览器上访问http://localhost:5002/hello?name=SpringCloud,显示的效果如下:

 

可以看到我们这样写代码 比之前用restTemplate 清爽了许多。

 

二、Feign的继承、日志、压缩

上节我们学的 Feign 还是有一些明显的缺陷,例如,当我们在 provider 中定义接口时,可能是下面这样:

@RestController
public class GirlController {
    @GetMapping("/girl")
    public String girl(String name) {
        return "love " + name + " !";
    }
}

然后在feign-consumer中定义:

@FeignClient("provider")
public interface GirlService {
    @GetMapping("/girl")
    String gril(@RequestParam("name") String name);
}

可以看到provider 和 feign-consumer代码明显重复了,而且如果调用的参数和提供的参数不一致那么就会报错,如果不细心的话,难免会发生这样的事情。那么如何避免这样的事情发生呢?那么我们可以使用feign的继承。

继承

将创建一个common模块,存放公共的接口。如下:创建一个GirlService的接口

public interface GirlService {
  @GetMapping("/girl")
  String girl(@RequestParam String name);
}

provider实现GirlService接口

@RestController
public class GirlController  implements GirlService {
    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public String girl(String name) {
        log.info("provider提供girl的服务");
        return "love" + name + "!";
    }
}

在feign-consumer中添加一个FeignGirlService 接口 并继承 commons 依赖中的GirlService接口,如下:

@FeignClient("provider")
public interface FeignGirlService extends GirlService {
}

需要注意的是,这里的 FeignGirlService 接口直接继承自 GirlSerivce ,继承之后, FeignGirlService 自动具备了 GirlSerivce 中的接口,因此可以在使用 @FeignClient(“provider”) 注解绑定服务之后就可以直接使用了。

然后我再从feign-consumer中定一个LoveGirlController,在 LoveGirlController 中使用 FeignGirlService:

@RestController
public class LoveGirlController {
    private Logger log = LoggerFactory.getLogger(this.getClass());
    @Autowired
    FeignGirlService girlService;

    @GetMapping("/girl")
    public String girl(String name) {
        log.info("consumer调用了provider提供的girl服务");
        return girlService.girl(name);
    }
}
优缺点分析
  • 使用继承特性,代码简洁明了,不易出错,不必担心接口返回值是否写对,接口地址是否写对。如果接口地址有变化,也不用 provider 和 feign-consumer 大动干戈,只需要修改 commons 模块即可,provider 和 feign-consumer 就自然变了;
  • 前面提到的在 feign-consumer 中绑定接口时,如果是 key/value 形式的参数或者放在 header 中的参数,就必须要使用 @RequestParam 注解或者 @RequestHeader 注解,这个规则在这里一样适用。即在 commons 中定义接口时,如果涉及到相关参数,该加的@RequestParam 注解或者 @RequestHeader 注解一个都不能少;
  • 当然,使用了继承特性也不是没有缺点。继承的方式将 provider 和 feign-consumer 绑定在一起,代码耦合度变高,一变俱变,此时就需要严格的设计规范,否则会牵一发而动全身,增加项目维护的难度。
日志配置

我们使用了Feign,如果想要看微服务之前的调用情况,那么就可以使用Feign的日志功能。

Feign的日志功能有四种:

  • NONE ,不开启日志记录,默认即此
  • BASIC ,记录请求方法和请求 URL ,以及响应的状态码以及执行时间
  • HEADERS ,在第2条的基础上,再增加请求头和响应头
  • FULL ,在第3条的基础上再增加 body 以及元数据

我们一般使用最强的就是 FULL了。

那么如何使用Feign的日志功能呢?非常的简单,只需要在启动类加一个bean就可以了如下:

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

这里我们选择FULL 最强的,然后在application.yml中配置:

logging:
  level:
    cn:
      com:
        scitc:
          FeignGirlService: debug

这里 logging.level 是指日志级别的前缀,cn.com.scitc.FeignGirlService.FeignGirlService表示该 class 以 debug 级别输出日志。当然,类路径也可以是一个 package ,这样就表示该 package 下的所有 class 以 debug 级别输出日志。配置完成后,重启 feign-consumer 项目,访问其中任意一个接口,就可以看到请求日志。

数据压缩

数据的压缩,主要是解决传输效率,具体配置如下:

feign:
  compression:
    request:
      enabled: true
      mime-types: text/html,application/json
      min-request-size: 2048
    response:
      enabled: true

前两行表示开启请求和响应压缩,第三行表示压缩的数据类型,默认是 text/html,application/json,application/xml, 第四行表示压缩数据的下限,即当要传输的数据大于2048时才需要对请求进行压缩。

请求重试

当Feign出现问题的时候,我们可以尝试重新连接,前面我们使用的是Spring-retry 的依赖,但是在Feign中自带了请求重试功能,直接配置就可以使用:

ribbon:
  MaxAutoRetries: 3
  MaxAutoRetriesNextServer: 1
  OkToRetryOnAllOperations: false

其中MaxAutoRetries 代表最大的请求次数。
其中MaxAutoRetriesNextServer代表最大重试的service个数。
其中OkToRetryOnAllOperations代表是否开启任何异常都重试。

那么我们也可以针对一个微服务进行请求重试的配置:

service-provider: --- 服务提供者应用名
  ribbon:
    MaxAutoRetries: 3
    MaxAutoRetriesNextServer: 1
    OkToRetryOnAllOperations: false

这个配置就是针对服务名称是service-provider的服务,注意这里的provider服务名字是spring.application.name中的名称。

我们也可以不通过配置文件来配置,直接用一个bean:

@Bean
public Retryer feignRetryer() {
	Retryer.Default retryer = new Retryer.Default();
	return retryer;
}

 

posted @ 2020-09-11 23:16  codedot  阅读(269)  评论(0编辑  收藏  举报