您的位置:首页 > 编程语言 > Java开发

Spring Cloud Feign 声明式服务调用

2018-10-14 15:35 1076 查看

简介

Spring Cloud Feign,基于Netflix Feign实现,整合了Spring Cloud Ribbon与Spring Cloud Hystrix,此外,它还提供了一种声明式的Web服务客户端定义方式。 Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud Feign的实现下,我们只需创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定。

快速入门

下面的示例将使用我们在上一篇文章中提供的hello-service服务,这里我们会通过Spring Cloud Feign提供的声明式服务绑定功能来实现对该服务接口的调用。

1.创建一个SpringBoot工程,这里命名为feign-consumer,然后在pom文件中引入spring-cloud-starter-eureka和spring-cloud-starter-feign依赖

<dependencies>
.....
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency></dependencies>

2.创建应用主类ConsumerApplication ,并通过@EnableFeignClients注解开启Spring Cloud Feign的支持功能。

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {

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

3.定义HelloService接口,@FeignClient注解指定服务名来绑定服务,然后再使用Spring MVC的注解来绑定具体该服务提供的REST接口(即使用controller完成对接口的实现)。

@FeignClient(value = "hello-service")
public interface HelloService{

@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello();
}

注意:这里服务名不区分大小写,所以使用hello-service和HELLO-SERVICE都是可以的。另外,在Brixton.SR5版本中,原有的serviceId属性已经被废弃,若要写属性名,可以使用name或value。

4.接着,创建一个ConsumerController来实现对Feign客户端的调用。使用@Autowired直接注入上面定义的HelloService实例,并在helloConsumer函数中调用这个绑定了hello-service服务接口的客户端来向该服务发起/hello接口的调用。

@RestController
public class ConsumerController{

@Autowired
private HelloService client;

@RequestMapping(value = "/feign-consumer", method = RequestMethod.GET)
public String hello() {
return client.hello();
}
}

5.最后,同Ribbon实现的服务消费者一样,需要在application.properties中指定服务注册中心,并定义自身的服务名为feign-consumer,为了方便本地调试与之前的Ribbon消费者区分,端口使用9001。

spring.application.name=feign-consumer
server.port=9001

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

总结: 通过上面的测试结果,我们可以看到Feign实现的消费者,依然是利用Ribbon维护了针对HELLO-SERVICE的服务列表信息,并通过轮询实现了客户端负载均衡。与Ribbon不同的是,通过Feign我们只需定义服务绑定接口,以声明式的方式,优雅而简单的实现了服务调用。

参数绑定

现实系统中的各种业务接口要比上一节复杂得多,我们会在HTTP的各个位置传入各种不同类型的参数,并且在返回响应的时候也可能是一个复杂的对象结构。在本节中,我们将详细介绍Feign中的不同形式参数的绑定方法。

在开始介绍Spring Cloud Feign的参数绑定之前,我们先扩张以下服务提供者hello-service。增加下面这些接口,其中包含带有Request参数的请求、带有Header信息的请求、带有RequestBody的请求以及请求响应体中是一个对象的请求。

@RequestMapping(value = "/hello1" method = RequestMethod.GET)
String hello(@RequestParem String name) {
return “hello” + name;
}

@RequestMapping(value = "/hello2" method = RequestMethod.GET)
User hello(@RequestHeader String name, @RequestHeader Integer age) {
return new User(name, age);
}

@RequestMapping(value = "/hello3" method = RequestMethod.POST)
String hello(@RequestBody User user) {
return "hello" +user.getName()
}

User对象必须要有默认的构造函数。不然,Spring Cloud Feign根据JSON字符串转换User时会抛出异常。

下面在Feign接口中定义好接口即可

@FeignClient("hello-service)
public interface HelloService {

@RequestMapping(value = "/hello1" method = RequestMethod.GET)
public String hello(@RequestParem("name") String name);

@RequestMapping(value = "/hello2" method = RequestMethod.GET)
public User hello(@RequestHeader("name") String name, @RequestHeader("age") Integer age);

@RequestMapping(value = "/hello3" method = RequestMethod.POST)
public String hello(@RequestBody User user);
}

这里一定要注意,再定义各参数绑定时,@RequestParam、@RequestHeader等可以指定参数名称的主角,它们的value千万不能少。 在Spring MVC程序中,这些注解会根据参数名来作为默认值,但是在Feign中绑定参数必须通过value属性来指明具体的参数名,不然会抛出IllegalStateException异常,value属性不能为空

最后只需要在需要调用该接口的类中将该feign接口注入即可。

@Autowired
HelloService  helloService;

继承特性

在上面的例子中,在服务消费方声明接口时都是将服务提供方的Controller复制过来。这么做会出现很多重复代码。在SpringCloudFeign中提供了继承特性来帮助我们解决这些复制操作。

  1. 创建建一个基础的Maven工程,命名hello-service-api,以复用DTO与接口定义。这里需要用到SpringMVC的注解,所以需要引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 将上面的User对象复制到api中,并创建UserService
  2. 创建HelloService接口
@RequestMapping(“/refactor”)
public interface HelloService {

@RequestMapping(value = "/hello4" method = RequestMethod.GET)
String hello(@RequestParem("name") String name);

@RequestMapping(value = "/hello5" method = RequestMethod.GET)
User hello(@RequestHeader("name") String name, @RequestHeader("age") Integer age);

@RequestMapping(value = "/hello6" method = RequestMethod.POST)
String hello(@RequestBody User user);
}
  1. 定义controller层实现HelloService接口
@RestController
public class RefactorHelloServiceController implements HelloService {
String hello(@RequestParem("name") String name){
return “hello” + name;
}

User hello(@RequestHeader("name") String name, @RequestHeader("age") Integer age) {
return new User(name, age);
}

String hello(@RequestBody User user) {
return "hello" +user.getName()
}
}

可以看到,我们通过继承的方式,在controller中不再包含以往会定义的请求映射注解@RequestMapping(), 而参数的注解定义在重写的时候会自动带过来。在这个类中,除了要实现接口逻辑外,只需再增加@RestController注解使该类成为一个REST接口类就大功告成了。

  1. 添加feign接口即可
@FeignClient( value  = "hello-service")
public interface RefactorHelloService extends HelloService {}
  1. 最后只需要在需要用到该service的地方注入即可
@Autowired
RefactorHelloService  refactorHelloService ;

Ribbon配置

由于Spring Cloud Feign的客户端负载均衡是通过Spring Cloud Ribbon实现的,所以我们可以直接配置Ribbon客户端的方式来自定义各个服务客户端调用参数。那么我们如何使用Spring Cloud Feign的工程中使用Ribbon的配置呢?

1.全局配置
全局配置的方法非常简单,我们可以直接使用ribbon.=的方式来设置ribbon的各项默认参数。如下:

#以下配置全局有效
ribbon.eureka.enabled=true
#建立连接超时时间,原1000
ribbon.ConnectTimeout=60000
#请求处理的超时时间,5分钟
ribbon.ReadTimeout=60000
#所有操作都重试
ribbon.OkToRetryOnAllOperations=true
#重试发生,更换节点数最大值
ribbon.MaxAutoRetriesNextServer=10
#单个节点重试最大值
ribbon.MaxAutoRetries=1

2.指定服务配置

回想一下,在定义Feign客户端的时候,我们使用了@FeignClient注解。在初始化过程中,Spring Cloud Feign会根据该注解的name属性或value属性指定的服务名,自动创建一个同名的Ribbon客户端。也就是说,在之前的示例中,使用@FeignClient(value = “hello-service”)来创建Feign客户端的时候,同时也创建了一个名为hello-service的Ribbon客户端。既然如此,我们就可以使用@FeignClient注解中的name或value属性值来设置对应的Ribbon参数。比如:

#以下配置对服务hello-service-provider有效
hello-service-provider.ribbon.eureka.enabled=true
#建立连接超时时间,原1000
hello-service-provider.ribbon.ConnectTimeout=60000
#请求处理的超时时间,5分钟
hello-service-provider.ribbon.ReadTimeout=60000
#所有操作都重试
hello-service-provider.ribbon.OkToRetryOnAllOperations=true
#重试发生,更换节点数最大值
hello-service-provider.ribbon.MaxAutoRetriesNextServer=10
#单个节点重试最大值
hello-service-provider.ribbon.MaxAutoRetries=1

上面的重试策略中,要注意Ribbon的超时与Hystrix的超时是两个概念。一般要设置Hystrix的超时时间大于Ribbon的超时时间。否则Hystrix超时后,命令直接熔断,Ribbon的重试机制就没有意义了。默认的超时时间 Ribbon=Hystrix=1000。

Hysrtrix配置

spring cloud Feign中,除了引入了用于客户端负载均衡的spring cloud Ribbon之外,还引入了服务保护了容错的工具Hystrix,spring cloud feign客户端的方法都封装到Hystrix命令中进行服务保护。本节中,将详细介绍如何在使用Spring Cloud Feign时配置Hystrix属性以及如何实现服务降级。

1.全局配置

对于Hystrix的配置同样可以在application.properties中配置,全局配置直接使用默认前缀hystrix.command.default,如

# 设置熔断超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
# 关闭Hystrix功能(不要和上面的配置一起使用)
feign.hystrix.enabled=false
# 关闭熔断功能
hystrix.command.default.execution.timeout.enabled=false

另外,在对Hystrix进行配置之前,我们需要确认feign.hystrix.enabled参数没有被设置为false,否则该参数设置会关闭Feign客户端的Hystrix支持。而对于我们之前测试重拾机制时,对于Hystrix的超时时间控制除了可以使用上面的配置来增加熔断超时时间,也可以通过feign.hystrix.enabled=false来关闭Hystrix功能,或者使用hystrix,command.default.execution.timeout.enable=false来关闭熔断功能。

2.禁用Hystrix

上面我们提到了,在Spring Cloud Fegn中,可以通过feign.hystrix.enabled=false来关闭Hystrix功能。如果不想全局的关闭Hystrix支持,而只想针对某个服务客户端关闭Hystrix支持时,需要通过使用@Scope(“prototype”)注解为指定的客户端配置Feign.Builder实例,详细实现步骤如下:

  • 构建一个关闭Hystrix的配置类
@Configuration
public class DisableHystrixConfiguration {

@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){

return Feign.builder();
}
}
  • 在@FeignClient注解中,通过configuration参数引入上面实现的配置
@FeignClient(value = "USER-SERVICE",configuration = DisableHystrixConfiguration.class)
public interface UserRafactorService extends UserService {
}
  • 指定命令配置

配制方法也跟传统的Hystrix命令的参数配置相似,采用hystrix.command.作为前缀。而默认情况下会采用Feign客户端中的方法名②作为标识,所以,针对上一节介绍的尝试机制中对/hello接口的熔断超时时间的配置可以通过其方法名作为来进行配置:

hystrix.command.hello.execution.isolation.thread.timeoutInMilliseconds=5000

需要注意的是,由于方法名有可能会重复,这个时候相同的方法名的hystrix配置会共用,所以在进行方法与配置的时候需要做好一定的规划,当然也可以重写Feign.Builder的实现,并在应用主类中创建它的实例来覆盖自动化配置的HystrixFegin.Builder实现

  • 服务降级配置

Hystrix提供的服务降级是服务容错的重要功能,由于Spring cloud feign在定义服务客户端的时候与Spring cloud Ribbon有很大的差别,HystrixCommand定义被封装起来,我们无法像之前介绍spring cloud hystrix时,通过@HystrixCommand注解的fallback参数那样来指定具体的服务降级处理方法。但是,spring cloud feign提供了另外一种简单的定义方式。

  • 服务降级逻辑的实现只需要为Feign客户端的定义接口编写一个具体的接口实现类。比如为HelloService接口实现一个服务降级类HelloServiceFallback,其中每个重写方法的实现逻辑都可以用来定义相应的服务降级逻辑:
@Component
public class HelloServiceFallback implements HelloService {

@Override
public String hello(String name) {
// TODO Auto-generated method stub
return "error";
}

@Override
public String hello(User user) {
// TODO Auto-generated method stub
return "error";
}

@Override
public String hello() {
// TODO Auto-generated method stub
return "error";
}

@Override
public String hello(String name, Integer age) {
// TODO Auto-generated method stub
return "error";
}
}
  • 在服务接口HelloService中,通过@FeignClient注解的fallback属性来制定对应的服务降级实现类。
@FeignClient(name="feign-provide",fallback=HelloServiceFallback.class) //configuration=DisableHystrixConfiguration.class,
public interface HelloService {

@RequestMapping(value="/hello")
String hello();
@RequestMapping(value="/hello1",method=RequestMethod.GET)
String hello(@RequestParam("name") String name);
@RequestMapping(value="/hello2",method=RequestMethod.GET)
String hello(@RequestHeader("name") String name,@RequestHeader("age") Integer age);
@RequestMapping(value="/hello3",method=RequestMethod.POST)
String hello(@RequestBody User user);
}

其他配置

  • 请求压缩
    spring cloud feign支持对请求与相应进行GZIP压缩,以减少通信过程中的性能损耗,我们只需要通过下面的两个参数设置,就能开启请求与相应的压缩功能。
# 配置请求GZIP压缩
feign.compression.request.enabled=true
# 配置响应GZIP压缩
feign.compression.response.enabled=true
# 配置压缩支持的MIME TYPE
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 配置压缩数据大小的下限
feign.compression.request.min-request-size=2048
  • 日志配置
    spring cloud Feign在构建被@FeignClient注解修饰的服务客户端时,会为每一个客户端都创建一个feign.Logger实例,我们可以利用改日志对象的DEBUG模式来帮助分析Feign的请求细节。可以在application.yml配置logging.level.<feignClient>的参数配置格式来开启指定feign客户端的debug日志,其中<feignClient>为feign客户端定义接口的完整路径
logging:
level:
com.zhihao.miao.pay.service.UserService: debug

但是,只添加了该配置还无法实现对debug日志的输出。这是因为feign客户端默认的Logger.Level对象定义为NONE级别,该级别不会记录任何Feign调用过程中的信息,所以我们需要调整它的级别,针对全局的日志级别,可以在应用主类中加入Logger.Level的Bean创建,

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class PayApplication {

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

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

也可以实现配置类,然后在Feign客户端来指定配置类以实现是否需要调整不同的日志级别,比如说下面的实现:

@Configuration
public class FullLogConfiguration {

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

对于Feign的Logger级别主要有下面4类,可根据实际需要进行调整使用:

  • none:不记录任何信息
  • basic:仅记录请求方法,url以及响应状态码和执行时间
  • headers:除了记录basic级别的信息之外,还会记录请求和响应的头信息。
  • FULL:记录所有请求与响应的明细,包括头信息,请求体,元数据等。
阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: