Spring Cloud Feign 声明式服务调用
- 一、Feign是什么?
- 二、Feign的快速搭建
- 三、Feign的几种姿态
- 参数绑定
- 继承特性
- 四、其他配置
- Ribbon 配置
- Hystrix 配置
一、Feign是什么?
? 通过对前面Spring Cloud Ribbon
和 Spring Cloud Hystrix
,我们已经掌握了开发微服务应用时的两个重磅武器,学会了如何在微服务框架中进行服务间的调用
和如何使用断路器
来保护我们的服务,这两者被作为基础工具类框架广泛的应用在各个微服务框架中。既然这两个组件这么重要,那么有没有更高层次的封装来整合这两个工具以简化开发呢?Spring Cloud Feign
就是这样的一个工具,它整合了Spring Cloud Ribbon 和 Spring Cloud Hystrix 来达到简化开发的目的。
? 我们在使用Spring Cloud Ribbon
时,通常都会使用RestTemplate
的请求拦截来实现对依赖服务的接口调用,而RestTemplate
已经实现了对Http请求
的封装,形成了一套模板化的调用方法。在之前Ribbon
的例子中,我们都是一个接口对应一个服务调用的url,那么在实际项目开发过程中,一个url可能会被复用,也就是说,一个接口可能会被多次调用,所以有必要把复用的接口封装起来公共调用。Spring Cloud Feign
在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务的接口定义。
二、Feign的快速搭建
? 我们通过一个示例来看一下Feign的调用过程,下面的示例将继续使用之前的server-provider
服务,这里我们通过Spring Cloud Feign
提供的声明式服务绑定功能来实现对该服务接口的调用
- 首先,搭建一个SpringBoot项目,取名为
feign-consumer
,并在pom.xml
文件中引入spring-cloud-starter-eureka
和spring-cloud-starter-feignn
依赖,具体内容如下:
4.0.0
org.springframework.boot
spring-boot-starter-parent
1.3.7.RELEASE
com.feign.consumer
feign-consumer
0.0.1-SNAPSHOT
feign-consumer
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-feign
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
Brixton.SR5
pom
import
org.springframework.boot
spring-boot-maven-plugin
- 搭建完成pom.xml之后,我们在
feign-consumer
的启动类上添加如下注解
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
@EnableDiscoveryClient : 这个注解和@EnableEurekaClient 用法相同,表明这是一个Eureka客户端
@EnableFeignClients : 这个注解表明这个服务是一个Feign服务,能够使用@FeignClient 实现远程调用
- 新建一个
HelloService
接口,在接口上加上__@FeignClient__注解,表明这个接口是可以进行远程访问的,也表明这个接口可以实现复用的接口,它提供了一些远程调用的方法,也相当于制定了一些规则。
// 此处填写的是服务的名称
@FeignClient(value = "server-provider")
public interface HelloService {
@RequestMapping(value = "hello")
String hello();
}
@FeignClient 后面的value值指向的是提供服务的服务名,这样就能够对spring.application.name = server.provider 的服务发起服务调用
- 新建一个Controller,提供外界访问的入口,调用HelloService,完成一系列的服务请求-服务分发-服务调用
@RestController
public class ConsumerController {
@Autowired
HelloService helloService;
@RequestMapping(value = "/feign-consumer", method = RequestMethod.GET)
public String helloConsumer(){
return helloService.hello();
}
}
- 最后,为
feign-consumer
指定服务的端口号,服务的名称,并向注册中心注册自己
spring.application.name=feign-consumer
server.port=9001
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
测试验证:
? 像之前一样,启动四个服务: eureka-server
, server-provider(8081,8082)
, feign-consumer
,启动http://localhost:9000/eureka/ 主页,发现主页上注册了四个服务
访问http://localhost:9001/feign-consumer 端口,发现 "Hello World" 能够返回
三、Feign的几种姿态
参数绑定
? 在上一节的事例中,我们使用Spring Cloud Feign搭建了一个简单的服务调用的示例,但是实际的业务场景中要比它复杂很多,我们会在HTTP的各个位置传入不同类型的参数,并且返回的也是一个复杂的对象结构,下面就来看一下不同的参数绑定方法
- 首先扩展一下
server-provider
中HelloController的内容
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
public String hello1(@RequestParam String name){
return "Hello " + name;
}
@RequestMapping(value = "/hello2", method = RequestMethod.GET)
public User hello2(@RequestHeader Integer id,@RequestHeader String name){
return new User(id,name);
}
@RequestMapping(value = "/hello3",method = RequestMethod.POST)
public String hello3(@RequestBody User user){
return "Hello " + user.getId() + ", " + user.getName();
}
- User 对象的定义入下,省略了get和set方法,需要注意的是,这里必须要有User的默认构造函数,否则反序列化的时候,会报Json解析异常
public class User {
private Integer id;
private String name;
public User(){}
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
get and set...
}
- 在
feign-consumer
中的HelloService中声明对服务提供者的调用
@FeignClient(value = "server-provider")
public interface HelloService {
@RequestMapping(value = "hello")
String hello();
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
String hello1(@RequestParam("name") String name);
@RequestMapping(value = "/hello2", method = RequestMethod.GET)
User hello2(@RequestHeader("id") Integer id,@RequestHeader("name") String name);
@RequestMapping(value = "/hello3", method = RequestMethod.POST)
String hello3(@RequestBody User user);
}
hello1 方法传递了一个参数为name的请求参数,它对应远程调用server-provider服务中的hello1方法
hello2 方法传递了一个请求头尾id 和 name的参数,对应远程调用server-provider服务中的hello2方法
hello3 方法传递了一个请求体为user的参数,对应远程调用呢server-provider服务中的hello3方法
- 下面在ConsumerController类中定义一个helloConsumer1的方法,分别对hello1,hello2,hello3方法进行服务调用
@RestController
public class ConsumerController {
@Autowired
HelloService helloService;
@RequestMapping(value = "/feign-consumer", method = RequestMethod.GET)
public String helloConsumer(){
return helloService.hello();
}
@RequestMapping(value = "/feign-consumer2", method = RequestMethod.GET)
public String helloConsumer1(String name){
StringBuilder builder = new StringBuilder();
builder.append(helloService.hello()).append("\n");
builder.append(helloService.hello1("lx")).append("\n");
builder.append(helloService.hello2(23,"lx")).append("\n");
builder.append(helloService.hello3(new User(24,"lx"))).append("\n");
return builder.toString();
}
}
上面的helloConsumer1方法,分别调用了HelloServcie接口中的hello、hello1、hello2、hello3方法,传递对应的参数,然后对每一个方法进行换行
测试验证
在完成上述的改造之后,启动服务注册中心
、两个 server-provider
服务以及我们改造过的feign-consumer
。通 过发送GET请求到 http://localhost:9001/feign-consumer3( 使用Postman 访问),发现能够显示出来如下内容
Hello lx
Hello 24, lx
com.feignservice.api.User@5865261
优点和缺点
? 使用Spring Cloud Feign
的优点很多,可以将接口的定义从Controller 中剥离,同时配合Maven 构建就能轻易的实现接口的复用,实现在构建期的接口绑定,从而有效的减少服务客户端的绑定配置。但是这种配置使用不当也会带来副作用就是:你不能忽略频繁变更接口带来的影响。所以,如果团队打算采用这种方式来构建项目的话,最好在开发期间就严格遵守面向对象的开闭原则。避免牵一发而动全身,造成不必要的维护量。
四、其他配置
Ribbon 配置
? 由于Spring Cloud Feign
的客户端负载均衡是通过Spring Cloud Ribbon
实现的,所以我们可以通过配置Spring Cloud Feign 从而配置 Spring Cloud Ribbon 。
全局配置
? 全局配置的方法很简单,我们可以使用如下配置来设置全局参数
ribbon.ConnectTimeout=5000
ribbon.ReadTimeout=5000
指定服务配置
? 大多数情况下,我们对于服务的调用时间可能会根据实际服务特性来做一些调整,所以仅仅依靠全局的配置是不行的,因为Feign 这个组件是整合了 Ribbon和 Hystrix的,所以通过设置Feign的属性来达到属性传递的目的。在定义Feign 客户端的时候,我们使用了@FeignClient()
注解,其实在创建@FeignClient(value = server-provider
)的时候,同时也创建了一个名为server-provider
的ribbon 客户端,所以我们就可以使用@FeignClient中的nane 和value 值来设置对应的Ribbon 参数。
# 使用feign-clients 中的注解的value值设置如下参数
# HttpClient 的连接超时时间
server-provider.ribbon.ConnectTimeout=500
# HttpClient 的读取超时时间
server-provider.ribbon.ReadTimeout=2000
# 是否可以为此客户端重试所有操作
server-provider.ribbon.OkToRetryOnAllOperations=true
# 要重试的下一个服务器的最大数量(不包括第一个服务器)
server-provider.ribbon.MaxAutoRetriesNextServer=2
# 同一个服务器上的最大尝试次数(不包括第一个)
server-provider.ribbon.MaxAutoRetries=1
重试机制
? Spring Cloud Feign 中实现了默认的请求重试机制,我们可以通过修改server-provider
中的示例做一些验证:
- 在
server-provider
应用中的/hello
接口实现中,增加一些随机延迟,比如
@RequestMapping(value = "hello", method = RequestMethod.GET)
public String hello() throws Exception{
ServiceInstance instance = discoveryClient.getLocalServiceInstance();
log.info("instance.host = " + instance.getHost() + "instance.service = " + instance.getServiceId()
+ "instance.port = " + instance.getPort());
log.info("Thread sleep ... ");
int sleepTime = new Random().nextInt(3000);
log.info("sleepTime = " + sleepTime);
Thread.sleep(sleepTime);
System.out.println("Thread awake");
return "Hello World";
}
- 在
feign-consumer
应用中增加上文提到的重试配置参数,来解释一下上面的配置
MaxAutoRetriesNextServer 设置为2 表示的是下一个服务器的最大数量,也就是说如果调用失败,会更换两次实例进行重试,MaxAutoRetries设置为1 表示的是每一个实例会进行一次调用,失败了再换为其他实例。OKToRetryOnAllOperations的意义是无论是请求超时或者socket read timeout都进行重试,
这里需要注意一点,Ribbon超时和Hystrix超时是两个概念,为了让上述实现有效,我们需要 让Hystrix的超时时间大于Ribbon的超时时间, 否则Hystrix命令超时后, 该命令直接熔断,重试机制就没有任何意义了。
Hystrix 配置
? 在Spring Cloud Feign
中,除了引入Spring Cloud Ribbon
外,还引入了服务保护工具Spring Cloud Hystrix
,下面就来介绍一下如何使用Spring Cloud Feign配置Hystrix属性实现服务降级。
全局配置
? 对于Hystrix全局配置同Spring Cloud Ribbon 的全局配置一样,直接使用默认前缀 hystrix.command.default 就可以进行配置,比如设置全局的超时
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
? 另外,在对Hystrix进行配置之前,我们需要确认feign.hystrix.enable
参数没有设置为false,否则该参数设置会关闭Feign客户端的Hystrix支持。
// 关闭Hystrix 功能(全局关闭)
feign.hystrix.enabled=false
// 关闭熔断功能
hystrix.command.default.execution.timeout.enabled=false
禁用hystrix
? 如果不想全局地关闭Hystrix支持,而只想针对某个服务客户端关闭Hystrix支持,需要通过使用@Scope("prototype")
注解为指定的客户端配置Feign.Builder 实例
- 构建一个关闭Hystrix的配置类
@Configuration
public class DisableHystrixConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder builder(){
return new Feign.Builder();
}
}
- 在
HelloService
的@FeignClient注解中,通过Configuration参数引入上面实现的配置
@FeignClient(value = "server-provider", fallback = DisableHystrixConfiguration.class)
public interface RefactorHelloService extends HelloService {}
服务降级配置
? Hystrix 提供的服务降级是服务容错的重要功能,之前我们开启Ribbon
的服务降级是通过使用 @HystrixCommand(fallbackMethod = "hystrixCallBack")
开启的,Feign
对Ribbon
进行了封装,所以Feign 也提供了一种服务降级策略。下面我们就来看一下Feign 如何使用服务降级策略。我们在feign-consumer
中进行改造
- 服务降级逻辑的实现只需要为Feign客户端的定义接口编写一个具体的接口实现类,比如为
server-provider
接口实现一个服务降级类HelloServiceFallback
,其中每个重写方法的逻辑都可以用来定义相应的服务降级逻辑,具体代码如下
@Component
public class FeignServiceCallback implements FeignService{
@Override
public String hello() {
return "error";
}
@Override
public String hello(@RequestParam("name") String name) {
return "error";
}
@Override
public User hello(@RequestHeader("id") Integer id, @RequestHeader("name") String name) {
return new User(0,"未知");
}
@Override
public String hello(@RequestBody User user) {
return "error";
}
}
- 在服务绑定接口中,通过
@FeignClient
注解的fallback 属性来指定对应的服务降级类
@FeignClient(value = "server-provider",fallback = FeignServiceCallback.class)
public interface FeignService {
@RequestMapping(value = "/hello")
String hello();
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
String hello(@RequestParam("name") String name);
@RequestMapping(value = "/hello2", method = RequestMethod.GET)
User hello(@RequestHeader("id") Integer id,@RequestHeader("name") String name);
@RequestMapping(value = "/hello3", method = RequestMethod.POST)
String hello(@RequestBody User user);
}
测试验证
? 下面我们来验证一下服务降级逻辑,启动注册中心Eureka-server
,服务消费者feign-consumer
,不启动server-provider
,发送GET 请求到http://localhost:9001/feign-consumer2,该接口会分别调用FeignService中的四个接口,因为feign-consumer
没有启动,会直接触发服务降级,使用Postman调用接口的返回值如下
error
error
error
com.feign.consumer.pojo.User@5ac0702f
后记: Spring Cloud Feign 声明式服务调用就先介绍到这里,下一篇介绍Spring Cloud Zuul服务网关
文章参考:
《Spring Cloud 微服务实战》