SpringCloud06—声明式服务调用:Spring Cloud Feign
6.声明式服务调用:Spring Cloud Feign
- 6.1 快速入门
- 6.2 参数绑定
6.3 Ribbon配置
- 6.3.1 全局配置
- 6.3.2 指定服务配置
- 6.3.3 重试机制
6.4 Hystrix配置
- 6.4.1 全局配置
- 6.4.2 禁用Hystrix
- 6.4.3 指定命令配置
- 6.4.4 服务降级配置
- 6.4.5 请求压缩
- 6.4.6 日志配置
上一篇:《SpringCloud05—服务容错保护:Spring Cloud Hystrix》
6.声明式服务调用:Spring Cloud Feign
本章我们即将介绍的Spring Cloud Feign基于Netflix Feign实现,整合了Spring Cloud Ribbon与Spring Cloud Hystrix,除了提供这两者的强大功能之外,它还提供了一种声明式的 Web服务客户端定义方式。
6.1 快速入门
在本节中,我们将通过一个简单的示例来展现Spring Cloud Feign在服务客户端定义上所带来的便利。
下面的示例将继续使用之前我们实现的hello-service服务,这里我们会通过Spring Cloud Feign提供的声明式服务绑定功能来实现对该服务接口的调用。
首先创建一个SpringBoot web项目,取名为fegin-consumer,并在pom.xml文件中引入以下依赖
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cloud</groupId>
<artifactId>fegin-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>fegin-consumer</name>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在主配置类上添加注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeginConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeginConsumerApplication.class, args);
}
}
修改application.properties
server.port=9001
spring.application.name=feign-consumer注册服务的时候使用服务的ip地址
eureka.instance.prefer-ip-address=false
关闭默认使用的Ribbon的负载均衡
spring.cloud.loadbalancer.ribbon.enabled=false
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/定义HelloService接口,通过@Feignclient注解指定服务名来绑定服务,然后再使用Spring MVC的注解来绑定具体该服务提供的REST接口。
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;@FeignClient(“hello-service”)
public interface HelloService {@RequestMapping(value = "/hello", method = RequestMethod.GET)
String hello();
}
注意:
接着创建一个controller
import com.cloud.feginconsumer.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;@RestController
public class HelloConsumerController {@Autowired
HelloService helloService;
@RequestMapping(value = "/fegin-consumer", method = RequestMethod.GET)
public String helloConsumer() {
return helloService.hello();
}
}
我们启动服务,访问 http://localhost:9001/fegin-consumer
这样的话我们就很优雅的实现了负载均衡
6.2 参数绑定
在上一节的示例中,我们使用Spring Cloud Feign实现的是一个不带参数的REST服务绑定。然而现实系统中的各种业务接口要比它复杂得多,我们会在 HTTP的各个位置传入各种不同类型的参数,并且在返回请求响应的时候也可能是一个复杂的对象结构。
在本节中我们将介绍Fegin中对几种不同形式参数的绑定用法
在开始介绍Spring Cloud Feign的参数绑定之前,我们先扩展一下服务提供方hello-service。增加下面这些接口定义,其中包含带有Request参数的请求、带有Header信息的请求、带有RequestBody 的请求以及请求响应体中是一个对象的请求。
@RestController
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String index() {
return "Hello SpringCloud-8081";
}
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
public String index(@RequestParam(value = "name") String name) {
return "Hello" + name;
}
@RequestMapping(value = "/hello2", method = RequestMethod.GET)
public User index(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age) {
User user = new User();
user.setUserName(name);
user.setAge(age);
return user;
}
@RequestMapping(value = "/hello3", method = RequestMethod.POST)
public String index(@RequestBody User user) {
return "Hello" + user.getUserName() + "," + user.getAge();
}
}
在完成对hello-service的改造之后,我们接下来继续回到feign-consumer应用中实现这些新增的请求的绑定
- 首先,在feign-consumer中创建与上面一样的User类。
然后,在 Helloservice 接口中增加对上述三个新增接口的绑定声明,修改后,完成的Helloservice接口如下所示:
@FeignClient(“hello-service”)
public interface HelloService {@RequestMapping(value = "/hello", method = RequestMethod.GET)
String hello();
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
String hello(@RequestParam(value = "name") String name);
@RequestMapping(value = "/hello2", method = RequestMethod.GET)
User index(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age);
@RequestMapping(value = "/hello3", method = RequestMethod.POST)
String index(@RequestBody User user);
}
最后,在HelloConsumerController中新增一个/feign-consumer2接口,来对本节新增的声明接口进行调用,修改后的完整代码如下所示:
import com.cloud.api.po.helloSerivce.User;
import com.cloud.feginconsumer.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HelloConsumerController {@Autowired
HelloService helloService;
@RequestMapping(value = "/fegin-consumer", method = RequestMethod.GET)
public String helloConsumer() {
return helloService.hello();
}
@RequestMapping(value = "/fegin-consumer2", method = RequestMethod.GET)
public Object helloConsumer2() {
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("hello", helloService.hello());
resultMap.put("hello1", helloService.hello("ninesun"));
resultMap.put("hello2", helloService.hello("header-ninesun", 24));
User user = new User();
user.setUserName("ninesun");
user.setAge(24);
resultMap.put("hello3", helloService.hello(user));
return resultMap;
}
}
返回结果如下:
6.3 Ribbon配置
由于Spring Cloud Feign的客户端负载均衡是通过Spring Cloud Ribbon实现的,所以我们可以直接通过配置Ribbon客户端的方式来自定义各个服务客户端调用的参数。
6.3.1 全局配置
全局配置的方法非常简单,我们可以直接使用 ribbon. < key>=< value> 的方式来设置ribbon的各项默认参数。比如,修改默认的客户端调用超时时间:
ribbon.ConnectTimeout=500
ribbon.ReadTimeOut=5000
6.3.2 指定服务配置
大多数情况下,我们对于服务调用的超时时间可能会根据实际服务的特性做一些调整,所以仅仅依靠默认的全局配置是不行的。
在使用Spring Cloud Feign的时候,针对各个服务客户端进行个性化配置的方式与使用Spring Cloud Ribbon时的配置方式是一样的,都采用 < client>.ribbon.key=value 的格式进行设置。
其中client就是我们服务实例的名称
比如:
hello-service.ribbon.ConnectTimeout=500
hello-service.ribbon.ReadTimeout=2000
# 开启重试机制
hello-service.ribbon.OkToRetryOnAllOperations=true
# 尝试更换两次实例重试,失败之后进行重试
hello-service.ribbon.MaxAutoRetriesNextServer=2
# 先尝试访问首选实例一次,失败之后才更换实例访问
hello-service.ribbon.MaxAutoRetries=1
6.3.3 重试机制
1.开启重试机制
开启重试机制
hello-service.ribbon.OkToRetryOnAllOperations=true
尝试更换两次实例重试,失败之后进行重试
hello-service.ribbon.MaxAutoRetriesNextServer=2
先尝试访问首选实例一次,失败之后才更换实例访问
hello-service.ribbon.MaxAutoRetries=1
2.在hello-service应用的/hello接口实现中,增加一些随机延迟,比如:
@RequestMapping(value = “/hello”, method = RequestMethod.GET)
public String index() throws Exception {
int sleepTime = new Random().nextInt(3000);
log.info("sleepTime:" + sleepTime);
Thread.sleep(sleepTime);
return "Hello SpringCloud-8081";
}
在feign-consumer应用中增加上文中提到的重试配置参数。
其中,由于HELLO-SERVICE.ribbon.MaxAutoRetries 设置为1,所以重试策略先尝试访问首选实例一次,失败后才更换实例访问,而更换实例访问的次数通过HELLO-SERVICE.ribbon.MaxAutoRetriesNextServer参数设置为2,所以会尝试更换两次实例进行重试。- 最后,启动这些应用,并尝试访问几次http://localhost:9001/feign-consumer接口。
当请求发生超时的时候,我们在hello-service的控制台中可能会获得如下输出内容(由于sleepTime的随机性,并不一定每次相同):
6.4 Hystrix配置
在Spring Cloud Feign中,除了引入了用于客户端负载均衡的Spring Cloud Ribbon之外,还引入了服务保护与容错的工具Hystrix。默认情况下,Spring Cloud Feign 会为将所有Feign客户端的方法都封装到Hystrix 命令中进行服务保护。在上一节末尾,我们介绍重试机制的配置时,也提到了关于Hystrix的超时时间配置。
那么在本节中,我们就来详细介绍一下,如何在使用Spring Cloud Feign时配置Hystrix属性以及如何实现服务降级。
6.4.1 全局配置
另外,在对Hystrix进行配置之前,我们需要确认feign.hystrix.enabled参数没有被设置为false,否则该参数设置会关闭Feign客户端的Hystrix支持。
使用hystrix.command.default.execution.timeout.enabled=false也可用来关闭熔断功能。
1.设置全局的超时时间
设置全局的超时时间来进行服务的熔断 默认值1000
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=500
6.4.2 禁用Hystrix
如果我们只想针对某个服务客户端关闭Hystrix支持时,需要通过使用@Scope(“prototype”)注解为指定的客户端配置Feign.Builder实例,详细实现步骤如下所示。
构建一个关闭Hystrix的配置类。
import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;@Configuration
public class DisableHystrixConfiguration {@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
在HelloService的@FeignClient注解中,通过configuration参数引入上面实现的配置。
import com.cloud.api.po.helloSerivce.User;
import com.cloud.feginconsumer.config.DisableHystrixConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;@FeignClient(name = “hello-service”, configuration = DisableHystrixConfiguration.class)
public interface HelloService {@RequestMapping(value = "/hello", method = RequestMethod.GET)
String hello();
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
String hello(@RequestParam(value = "name") String name);
@RequestMapping(value = "/hello2", method = RequestMethod.GET)
User hello(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age);
@RequestMapping(value = "/hello3", method = RequestMethod.POST)
String hello(@RequestBody User user);
}
6.4.3 指定命令配置
对于Hystrix命令的配置,在实际应用时往往也会根据实际业务情况制定出不同的配置方案。
配置方法也跟传统的Hystrix命令的参数配置相似,采用hystrix.command.< commandKey>作为前缀。而< commandKey>默认情况下会采用Feign客户端中的方法名作为标识,所以,针对上一节介绍的尝试机制中对/hello接口的熔断超时时间的配置可以通过其方法名作为< commandKey>来进行配置,具体如下:
hystrix.command.hello.execution.isolation.thread.timeoutInMilliseconds=5000
在使用指定命令配置的时候,需要注意,由于方法名很有可能重复,这个时候相同方法名的Hystrix配置会共用,所以在进行方法定义与配置的时候需要做好一定的规划。当然,也可以重写Feign. Builder的实现,并在应用主类中创建它的实例来覆盖自动化配置的HystrixFeign.Builder实现。
6.4.4 服务降级配置
Hystrix提供的服务降级是服务容错的重要功能,由于Spring Cloud Feign在定义服务客户端的时候与Spring Cloud Ribbon有很大差别,HystrixCommand定义被封装了起来,我们无法像之前介绍Spring Cloud Hystrix时,通过@HystrixCommand注解的fallback参数那样来指定具体的服务降级处理方法。
但是,Spring Cloud Feign提供了另外一种简单的定义方式,下面我们在之前创建的feign-consumer工程中进行改造。
import com.cloud.api.po.helloSerivce.User;
import com.cloud.feginconsumer.service.fallback.HelloServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
@FeignClient(name = "hello-service", fallback = HelloServiceFallback.class)
public interface HelloService {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
String hello();
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
String hello(@RequestParam(value = "name") String name);
@RequestMapping(value = "/hello2", method = RequestMethod.GET)
User hello(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age);
@RequestMapping(value = "/hello3", method = RequestMethod.POST)
String hello(@RequestBody User user);
}
唯一的变动就是
新建一个类来实现上个接口
import com.cloud.api.po.helloSerivce.User;
import com.cloud.feginconsumer.service.HelloService;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
@Component
public class HelloServiceFallback implements HelloService {
@Override
public String hello() {
return "error";
}
@Override
public String hello(@RequestParam(value = "name") String name) {
return "error-hello1";
}
@Override
public User hello(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age) {
User user = new User();
user.setUserName("未知-hello2");
user.setAge(0);
return user;
}
@Override
public String hello(@RequestBody User user) {
return "error-hello3";
}
}
接下来我们将hello-service的服务给断掉进行测试,我们会发现出现以下错误
于是我们修改一下配置:
1.修改pom.xml,添加一下依赖
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2.2.9.RELEASE
2.修改application.properties,添加配置开启熔断服务
开启hystrix
feign.circuitbreaker.enabled=true
3.修改主配置类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableDiscoveryClient
//开启Feign
@EnableFeignClients
//开启断路器
@EnableCircuitBreaker
public class FeginConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeginConsumerApplication.class, args);
}
}
重启项目,测试结果如下:
6.4.5 请求压缩
Spring Cloud Feign支持对请求与响应进行GZIP压缩,以减少通信过程中的性能损耗。我们只需通过下面两个参数设置,就能开启请求与响应的压缩功能:
# 开启请求与响应的压缩功能
feign.compression.request.enabled=true
feign.compression.response.enabled=true
同时,我们还能对请求压缩做一些更细致的设置,比如下面的配置内容指定了压缩的请求数据类型,并设置了请求压缩的大小下限,只有超过这个大小的请求才会对其进行压缩。
# 设置请求压缩的大小下限,只有超过这个大小的请求才会对其进行压缩
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 单位kB
feign.compression.request.min-request-size=2048
6.4.6 日志配置
Spring Cloud Feign在构建被@Feignclient注解修饰的服务客户端时,会为每一个客户端都创建一个feign.Logger实例,我们可以利用该日志对象的DEBUG模式来帮助分析Feign的请求细节。可以在application.properties文件中使用 logging.level.< Feignclient> 的参数配置格式来开启指定Feign客户端的DEBUG日志,其中< Feignclient>为 Feign 客户端定义接口的完整路径,比如针对本章中我们实现的Helloservice可以按如下配置开启:
1.创建一个配置类
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class FeignConfig {
//配置日志
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
实例名 | 解释 |
---|---|
NONE | 不记录任何日志(默认) |
BASIC | 仅记录请求方法、URL、响应状态代码以及执行时间 |
HEADERS | 记录BASIC级别的基础上,记录请求和响应的header |
FULL | 记录请求和响应的header,body和元数据 |
2.在需要的service上使用该配置
import com.cloud.api.po.helloSerivce.User;
import com.cloud.feginconsumer.config.FeignConfig;
import com.cloud.feginconsumer.service.fallback.HelloServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;@FeignClient(name = “hello-service”, configuration = FeignConfig.class, fallback = HelloServiceFallback.class)
public interface HelloService {@RequestMapping(value = "/hello", method = RequestMethod.GET)
String hello();
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
String hello(@RequestParam(value = "name") String name);
@RequestMapping(value = "/hello2", method = RequestMethod.GET)
User hello(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age);
@RequestMapping(value = "/hello3", method = RequestMethod.POST)
String hello(@RequestBody User user);
}
3.修改application.properties
设置feign日志级别
logging.level.com.cloud.feginconsumer.config.FeignConfig=info
Feign的日志打印只会对info以及info以上的级别做出响应
重启项目,再次访问http://localhost:9001/fegin-consumer2,查看控制台情况:
下一篇:《SpringCloud07—API网关服务:Spring Cloud Zuul》
还没有评论,来说两句吧...